diff --git a/.changeset/hip-lands-call.md b/.changeset/hip-lands-call.md new file mode 100644 index 0000000000..6148cff2be --- /dev/null +++ b/.changeset/hip-lands-call.md @@ -0,0 +1,5 @@ +--- +'@forgerock/journey-client': patch +--- + +Removed duplicate middleware config and added support for typed middleware diff --git a/e2e/journey-app/main.ts b/e2e/journey-app/main.ts index 30ab90fd1f..b78f814df3 100644 --- a/e2e/journey-app/main.ts +++ b/e2e/journey-app/main.ts @@ -21,7 +21,8 @@ const searchParams = new URLSearchParams(qs); const config = serverConfigs[searchParams.get('clientId') || 'basic']; const journeyName = searchParams.get('journey') ?? 'UsernamePassword'; -let requestMiddleware: RequestMiddleware[] = []; +let requestMiddleware: RequestMiddleware<'JOURNEY_START' | 'JOURNEY_NEXT' | 'JOURNEY_TERMINATE'>[] = + []; if (searchParams.get('middleware') === 'true') { requestMiddleware = [ @@ -30,10 +31,12 @@ if (searchParams.get('middleware') === 'true') { case 'JOURNEY_START': req.url.searchParams.set('start-authenticate-middleware', 'start-authentication'); req.headers.append('x-start-authenticate-middleware', 'start-authentication'); + req.headers?.set('Accept-Language', 'xx-XX'); break; case 'JOURNEY_NEXT': req.url.searchParams.set('authenticate-middleware', 'authentication'); req.headers.append('x-authenticate-middleware', 'authentication'); + req.headers?.set('Accept-Language', 'yy-YY'); break; } next(); @@ -43,6 +46,7 @@ if (searchParams.get('middleware') === 'true') { case 'JOURNEY_TERMINATE': req.url.searchParams.set('end-session-middleware', 'end-session'); req.headers.append('x-end-session-middleware', 'end-session'); + req.headers?.set('Accept-Language', 'zz-ZZ'); break; } next(); diff --git a/e2e/journey-suites/src/request-middleware.test.ts b/e2e/journey-suites/src/request-middleware.test.ts index be0e269113..0e33d0b989 100644 --- a/e2e/journey-suites/src/request-middleware.test.ts +++ b/e2e/journey-suites/src/request-middleware.test.ts @@ -9,7 +9,7 @@ import { expect, test } from '@playwright/test'; import { asyncEvents } from './utils/async-events.js'; import { username, password } from './utils/demo-user.js'; -test.skip('Test happy paths on test page', async ({ page }) => { +test('Test middleware on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); await navigate('/?middleware=true&journey=Login'); @@ -24,7 +24,7 @@ test.skip('Test happy paths on test page', async ({ page }) => { }); page.on('request', async (req) => { - networkArray.push(`${new URL(req.url()).pathname}, ${req.resourceType()}`); + networkArray.push(req.url().toString()); }); page.on('request', async (req) => { @@ -46,17 +46,33 @@ test.skip('Test happy paths on test page', async ({ page }) => { await clickButton('Logout', '/authenticate'); // Test assertions - // test URL query parameters added to URL on networkArray - expect(networkArray).toContain('start-authenticate-middleware, fetch'); - expect(networkArray).toContain('authenticate-middleware, fetch'); - expect(networkArray).toContain('end-session-middleware, fetch'); + // Test URL query parameters added to URL on networkArray + const startRequest = networkArray.find((url) => url.includes('start-authenticate-middleware')); + const nextRequest = networkArray.find((url) => url.includes('authenticate-middleware')); + const endRequest = networkArray.find((url) => url.includes('end-session-middleware')); - expect( - headerArray.find((headers) => headers.get('x-start-authenticate-middleware')), - ).toBeTruthy(); - expect(headerArray.find((headers) => headers.get('x-authenticate-middleware'))).toBeTruthy(); - expect(headerArray.find((headers) => headers.get('x-end-session-middleware'))).toBeTruthy(); + expect(startRequest?.includes('start-authenticate-middleware=start-authentication')).toBeTruthy(); + expect(nextRequest?.includes('authenticate-middleware=authentication')).toBeTruthy(); + expect(endRequest?.includes('end-session-middleware=end-session')).toBeTruthy(); + + // Check for addition of custom headers + const startHeader = headerArray.find((headers) => headers.get('x-start-authenticate-middleware')); + const nextHeader = headerArray.find((headers) => headers.get('x-authenticate-middleware')); + const endHeader = headerArray.find((headers) => headers.get('x-end-session-middleware')); + + expect(startHeader?.get('x-start-authenticate-middleware')).toBe('start-authentication'); + expect(nextHeader?.get('x-authenticate-middleware')).toBe('authentication'); + expect(endHeader?.get('x-end-session-middleware')).toBe('end-session'); + + // Check that Accept-Language header was modified from default en-US locale and set to correct value in each middleware + expect(startHeader?.get('Accept-Language')).not.toContain('en-US'); + expect(nextHeader?.get('Accept-Language')).not.toContain('en-US'); + expect(endHeader?.get('Accept-Language')).not.toContain('en-US'); + + expect(startHeader?.get('Accept-Language')).toBe('xx-XX'); + expect(nextHeader?.get('Accept-Language')).toBe('yy-YY'); + expect(endHeader?.get('Accept-Language')).toBe('zz-ZZ'); expect(messageArray.includes('Journey completed successfully')).toBe(true); expect(messageArray.includes('Logout successful')).toBe(true); diff --git a/packages/journey-client/src/lib/client.store.ts b/packages/journey-client/src/lib/client.store.ts index 9e7b0b40b4..36f8e460f2 100644 --- a/packages/journey-client/src/lib/client.store.ts +++ b/packages/journey-client/src/lib/client.store.ts @@ -14,8 +14,7 @@ import { } from '@forgerock/sdk-utilities'; import type { GenericError } from '@forgerock/sdk-types'; - -import type { RequestMiddleware } from '@forgerock/sdk-request-middleware'; +import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware'; import type { Step } from '@forgerock/sdk-types'; import { createJourneyStore } from './client.store.utils.js'; @@ -28,7 +27,7 @@ import { wellknownApi } from './wellknown.api.js'; import type { JourneyStep } from './step.utils.js'; import type { JourneyClientConfig } from './config.types.js'; import type { RedirectCallback } from './callbacks/redirect-callback.js'; -import { NextOptions, StartParam, ResumeOptions } from './interfaces.js'; +import type { NextOptions, StartParam, ResumeOptions } from './interfaces.js'; import type { JourneyLoginFailure } from './login-failure.utils.js'; import type { JourneyLoginSuccess } from './login-success.utils.js'; @@ -78,13 +77,13 @@ export interface JourneyClient { * } * ``` */ -export async function journey({ +export async function journey({ config, requestMiddleware, logger, }: { config: JourneyClientConfig; - requestMiddleware?: RequestMiddleware[]; + requestMiddleware?: RequestMiddleware[]; logger?: { level: LogLevel; custom?: CustomLogger; @@ -116,7 +115,6 @@ export async function journey({ store.dispatch( configSlice.actions.set({ wellknownResponse: wellknownResponse, - middleware: config.middleware ?? requestMiddleware, }), ); diff --git a/packages/journey-client/src/lib/client.store.utils.ts b/packages/journey-client/src/lib/client.store.utils.ts index 4a81a3c771..0e0b05c794 100644 --- a/packages/journey-client/src/lib/client.store.utils.ts +++ b/packages/journey-client/src/lib/client.store.utils.ts @@ -6,7 +6,7 @@ */ import { logger as loggerFn } from '@forgerock/sdk-logger'; -import { RequestMiddleware } from '@forgerock/sdk-request-middleware'; +import { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware'; import { combineReducers, configureStore } from '@reduxjs/toolkit'; import { configSlice } from './config.slice.js'; @@ -19,11 +19,11 @@ const rootReducer = combineReducers({ [wellknownApi.reducerPath]: wellknownApi.reducer, }); -export const createJourneyStore = ({ +export const createJourneyStore = ({ requestMiddleware, logger, }: { - requestMiddleware?: RequestMiddleware[]; + requestMiddleware?: RequestMiddleware[]; logger?: ReturnType; }) => { return configureStore({ diff --git a/packages/journey-client/src/lib/config.slice.test.ts b/packages/journey-client/src/lib/config.slice.test.ts index a4a79df186..b4b0afa075 100644 --- a/packages/journey-client/src/lib/config.slice.test.ts +++ b/packages/journey-client/src/lib/config.slice.test.ts @@ -9,7 +9,6 @@ import { describe, it, expect } from 'vitest'; import { configSlice } from './config.slice.js'; import type { WellknownResponse } from '@forgerock/sdk-types'; -import type { RequestMiddleware } from '@forgerock/sdk-request-middleware'; import type { ResolvedConfig } from './config.slice.js'; function createMockWellknown(overrides: Partial = {}): WellknownResponse { @@ -86,21 +85,4 @@ describe('journey-client config.slice', () => { expect(state.error?.message).toContain('authorization_endpoint'); }); }); - - describe('configSlice_Middleware_StoresMiddleware', () => { - it('should store the provided middleware array', () => { - // Arrange - const mockMiddleware: RequestMiddleware[] = [(_req, _action, next) => next()]; - const payload: ResolvedConfig = { - wellknownResponse: createMockWellknown(), - middleware: mockMiddleware, - }; - - // Act - const state = configSlice.reducer(undefined, configSlice.actions.set(payload)); - - // Assert - expect(state.middleware).toEqual(mockMiddleware); - }); - }); }); diff --git a/packages/journey-client/src/lib/config.slice.ts b/packages/journey-client/src/lib/config.slice.ts index c24d243983..02cac72102 100644 --- a/packages/journey-client/src/lib/config.slice.ts +++ b/packages/journey-client/src/lib/config.slice.ts @@ -6,10 +6,7 @@ */ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - import type { WellknownResponse } from '@forgerock/sdk-types'; -import type { RequestMiddleware } from '@forgerock/sdk-request-middleware'; - import type { InternalJourneyClientConfig } from './config.types.js'; import { convertWellknown } from './wellknown.utils.js'; @@ -20,12 +17,10 @@ import { convertWellknown } from './wellknown.utils.js'; */ export interface ResolvedConfig { wellknownResponse: WellknownResponse; - middleware?: Array; } const initialState: InternalJourneyClientConfig = { serverConfig: { baseUrl: '', paths: { authenticate: '', sessions: '' } }, - middleware: [], }; /** @@ -48,7 +43,6 @@ export const configSlice = createSlice({ state.serverConfig = wellknown; state.error = undefined; } - state.middleware = action.payload.middleware; }, }, }); diff --git a/packages/journey-client/src/lib/config.types.ts b/packages/journey-client/src/lib/config.types.ts index 49273ad733..cecb97ea07 100644 --- a/packages/journey-client/src/lib/config.types.ts +++ b/packages/journey-client/src/lib/config.types.ts @@ -6,7 +6,6 @@ */ import type { GenericError } from '@forgerock/sdk-types'; -import type { RequestMiddleware } from '@forgerock/sdk-request-middleware'; import type { ResolvedServerConfig } from './wellknown.utils.js'; /** @@ -34,8 +33,6 @@ export interface JourneyServerConfig { */ export interface JourneyClientConfig { serverConfig: JourneyServerConfig; - /** Optional middleware for request customization */ - middleware?: Array; } /** @@ -46,9 +43,5 @@ export interface JourneyClientConfig { */ export interface InternalJourneyClientConfig { serverConfig: ResolvedServerConfig; - /** Optional middleware for request customization */ - middleware?: Array; error?: GenericError; } - -export type { RequestMiddleware }; diff --git a/packages/journey-client/src/types.ts b/packages/journey-client/src/types.ts index 2d44b7c550..b5a8021659 100644 --- a/packages/journey-client/src/types.ts +++ b/packages/journey-client/src/types.ts @@ -7,7 +7,7 @@ // Re-export types from internal packages that consumers need export type { LogLevel, CustomLogger } from '@forgerock/sdk-logger'; -export type { RequestMiddleware } from '@forgerock/sdk-request-middleware'; +export type { RequestMiddleware, ActionTypes } from '@forgerock/sdk-request-middleware'; export type { Step, Callback,