diff --git a/src/facade/contentFetcherFacade.ts b/src/facade/contentFetcherFacade.ts index 075652f7..6d1a7834 100644 --- a/src/facade/contentFetcherFacade.ts +++ b/src/facade/contentFetcherFacade.ts @@ -21,7 +21,11 @@ function validate(options: unknown): asserts options is FetchOptions { } } -export type Configuration = { +type Options = { + preferredLocale?: string, +}; + +export type Configuration = Options & { contentFetcher: ContentFetcher, contextFactory: ContextFactory, previewTokenProvider: TokenProvider, @@ -40,12 +44,17 @@ export class ContentFetcherFacade { private readonly cidAssigner: CidAssigner; + private readonly options: Options; + public constructor(configuration: Configuration) { this.fetcher = configuration.contentFetcher; this.previewTokenProvider = configuration.previewTokenProvider; this.userTokenProvider = configuration.userTokenProvider; this.cidAssigner = configuration.cidAssigner; this.contextFactory = configuration.contextFactory; + this.options = { + preferredLocale: configuration.preferredLocale, + }; } public async fetch

(slotId: string, options: FetchOptions = {}): Promise> { @@ -61,9 +70,9 @@ export class ContentFetcherFacade { userToken: this.userTokenProvider.getToken() ?? undefined, previewToken: this.previewTokenProvider.getToken() ?? undefined, version: options.version, - preferredLocale: options.preferredLocale, context: this.contextFactory.createContext(options.attributes), timeout: options.timeout, + preferredLocale: options.preferredLocale ?? this.options.preferredLocale, }); } } diff --git a/src/facade/sdkFacade.ts b/src/facade/sdkFacade.ts index 009c1312..e260e5fc 100644 --- a/src/facade/sdkFacade.ts +++ b/src/facade/sdkFacade.ts @@ -15,7 +15,11 @@ import {PartialTrackingEvent} from '../trackingEvents'; import {UrlSanitizer} from '../tab'; import {ContentFetcherFacade} from './contentFetcherFacade'; -export type Configuration = { +type Options = { + preferredLocale?: string, +}; + +export type Configuration = Options & { appId: string, tokenScope?: TokenScope, debug?: boolean, @@ -53,14 +57,17 @@ export class SdkFacade { private contentFetcherFacade?: ContentFetcherFacade; - private constructor(sdk: Sdk) { + private readonly options: Options; + + private constructor(sdk: Sdk, options: Options = {}) { this.sdk = sdk; + this.options = options; } public static init(configuration: Configuration): SdkFacade { validateConfiguration(configuration); - const {track = true, userId, token, ...containerConfiguration} = configuration; + const {track = true, userId, token, preferredLocale, ...containerConfiguration} = configuration; if (userId !== undefined && token !== undefined) { throw new Error('Either the user ID or token can be specified, but not both.'); @@ -74,6 +81,9 @@ export class SdkFacade { test: containerConfiguration.test ?? false, disableCidMirroring: containerConfiguration.disableCidMirroring ?? false, }), + { + preferredLocale: preferredLocale, + }, ); if (userId !== undefined) { @@ -166,6 +176,7 @@ export class SdkFacade { cidAssigner: this.sdk.cidAssigner, previewTokenProvider: this.sdk.previewTokenStore, userTokenProvider: this.sdk.userTokenStore, + preferredLocale: this.options.preferredLocale, }); } diff --git a/src/schema/sdkFacadeSchemas.ts b/src/schema/sdkFacadeSchemas.ts index 130430ff..13bc5dd5 100644 --- a/src/schema/sdkFacadeSchemas.ts +++ b/src/schema/sdkFacadeSchemas.ts @@ -38,5 +38,8 @@ export const sdkFacadeConfigurationSchema = new ObjectType({ cidAssignerEndpointUrl: new StringType({ format: 'url', }), + preferredLocale: new StringType({ + pattern: /^[a-z]{2,3}([-_][a-z]{2,3})?$/i, + }), }, }); diff --git a/test/facade/contentFecherFacade.test.ts b/test/facade/contentFecherFacade.test.ts index eea66eb9..0de8da2a 100644 --- a/test/facade/contentFecherFacade.test.ts +++ b/test/facade/contentFecherFacade.test.ts @@ -94,12 +94,13 @@ describe('A content fetcher facade', () => { const userToken = Token.issue('00000000-0000-0000-0000-000000000000', 'foo', Date.now()); const previewToken = Token.issue('11111111-1111-1111-1111-111111111111', 'bar', Date.now()); - const evaluationFacade = new ContentFetcherFacade({ + const fetcherFacade = new ContentFetcherFacade({ contentFetcher: fetcher, cidAssigner: new FixedAssigner(clientId), userTokenProvider: new FixedTokenProvider(userToken), previewTokenProvider: new FixedTokenProvider(previewToken), contextFactory: new TabContextFactory(tab), + preferredLocale: 'pt-br', }); const options: FetchOptions = { @@ -109,7 +110,6 @@ describe('A content fetcher facade', () => { previewToken: previewToken, timeout: 5, version: 1, - preferredLocale: 'en-US', context: { attributes: { foo: 'bar', @@ -125,13 +125,27 @@ describe('A content fetcher facade', () => { const slotId = 'home-banner'; - await evaluationFacade.fetch(slotId, { + await fetcherFacade.fetch(slotId, { timeout: options.timeout, version: options.version, - preferredLocale: options.preferredLocale, attributes: options?.context?.attributes, }); - expect(fetcher.fetch).toHaveBeenNthCalledWith(1, slotId, options); + expect(fetcher.fetch).toHaveBeenNthCalledWith(1, slotId, { + ...options, + preferredLocale: 'pt-br', + }); + + await fetcherFacade.fetch(slotId, { + timeout: options.timeout, + version: options.version, + attributes: options?.context?.attributes, + preferredLocale: 'en-us', + }); + + expect(fetcher.fetch).toHaveBeenNthCalledWith(2, slotId, { + ...options, + preferredLocale: 'en-us', + }); }); }); diff --git a/test/facade/sdkFacade.test.ts b/test/facade/sdkFacade.test.ts index 40a87d01..ce42b95d 100644 --- a/test/facade/sdkFacade.test.ts +++ b/test/facade/sdkFacade.test.ts @@ -381,17 +381,27 @@ describe('A SDK facade', () => { return sdk; }); + const slotId = 'home-banner'; + const preferredLocale = 'en-us'; + const sdkFacade = SdkFacade.init({ appId: appId, track: false, + preferredLocale: preferredLocale, }); - const slotId = 'home-banner'; - const options: FetchOptions = {timeout: 5}; + const options: FetchOptions = { + timeout: 5, + }; + + const expectedOptions: FetchOptions = { + ...options, + preferredLocale: preferredLocale, + }; await expect(sdkFacade.contentFetcher.fetch(slotId, options)).resolves.toBe(result); - expect(fetcher.fetch).toHaveBeenCalledWith(slotId, expect.objectContaining(options)); + expect(fetcher.fetch).toHaveBeenCalledWith(slotId, expect.objectContaining(expectedOptions)); expect(fetcher.fetch).toHaveBeenCalledTimes(1); }); diff --git a/test/schemas/sdkFacadeSchemas.test.ts b/test/schemas/sdkFacadeSchemas.test.ts index d2322e28..6ec084bf 100644 --- a/test/schemas/sdkFacadeSchemas.test.ts +++ b/test/schemas/sdkFacadeSchemas.test.ts @@ -22,6 +22,30 @@ describe('The SDK facade configuration schema', () => { appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', userId: null, }], + [{ + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'pt', + }], + [{ + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'pt_br', + }], + [{ + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'pt_BR', + }], + [{ + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'pt-br', + }], + [{ + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'pt-BR', + }], + [{ + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'abc_cde', + }], [{ appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', baseEndpointUrl: 'https://api.croct.io/', @@ -54,57 +78,124 @@ describe('The SDK facade configuration schema', () => { "Missing property '/appId'.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', clientId: '7e9d59a9'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + clientId: '7e9d59a9', + }, "Invalid format at path '/clientId'.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', cidAssignerEndpointUrl: 'x'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + cidAssignerEndpointUrl: 'x', + }, "Invalid url format at path '/cidAssignerEndpointUrl'.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', baseEndpointUrl: 'x'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + baseEndpointUrl: 'x', + }, "Invalid url format at path '/baseEndpointUrl'.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', tokenScope: 'x'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + tokenScope: 'x', + }, "Unexpected value at path '/tokenScope', expecting 'global', 'contextual' or 'isolated', found 'x'.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', userId: ''}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + userId: '', + }, "Expected at least 1 character at path '/userId', actual 0.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', userId: 1}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + userId: 1, + }, "Expected value of type string or null at path '/userId', actual integer.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', token: 'foo'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + token: 'foo', + }, "Invalid format at path '/token'.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', token: 1}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + token: 1, + }, "Expected value of type string or null at path '/token', actual integer.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', debug: 'foo'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + debug: 'foo', + }, "Expected value of type boolean at path '/debug', actual string.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', test: 'foo'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + test: 'foo', + }, "Expected value of type boolean at path '/test', actual string.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', track: 'foo'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + track: 'foo', + }, "Expected value of type boolean at path '/track', actual string.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', disableCidMirroring: 'foo'}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + disableCidMirroring: 'foo', + }, "Expected value of type boolean at path '/disableCidMirroring', actual string.", ], [ - {appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', logger: null}, + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + logger: null, + }, "Expected value of type object at path '/logger', actual null.", ], + [ + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: '', + }, + "Invalid format at path '/preferredLocale'.", + ], + [ + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'fooo', + }, + 'Invalid format at path \'/preferredLocale\'.', + ], + [ + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'foo-baar', + }, + 'Invalid format at path \'/preferredLocale\'.', + ], + [ + { + appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', + preferredLocale: 'foo_baar', + }, + 'Invalid format at path \'/preferredLocale\'.', + ], ])('should not allow %s', (value: Record, message: string) => { function validate(): void { sdkFacadeConfigurationSchema.validate(value);