From a34f3d51f8fe78f366acbae08872814b8a7ec1ee Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Fri, 5 Apr 2024 13:45:13 -0400 Subject: [PATCH] Break out Store and config processing --- src/mp-instance.js | 49 +----------- src/sdkRuntimeModels.ts | 2 + src/store.ts | 49 ++++++++++-- test/src/tests-store.ts | 170 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 207 insertions(+), 63 deletions(-) diff --git a/src/mp-instance.js b/src/mp-instance.js index eed6fa9e..90655852 100644 --- a/src/mp-instance.js +++ b/src/mp-instance.js @@ -1289,33 +1289,8 @@ function completeSDKInitialization(apiKey, config, mpInstance) { mpInstance._APIClient = new APIClient(mpInstance, kitBlocker); mpInstance._Forwarders = new Forwarders(mpInstance, kitBlocker); - if (config.flags) { - if ( - config.flags.hasOwnProperty( - Constants.FeatureFlags.EventBatchingIntervalMillis - ) - ) { - mpInstance._Store.SDKConfig.flags[ - Constants.FeatureFlags.EventBatchingIntervalMillis - ] = - config.flags[ - Constants.FeatureFlags.EventBatchingIntervalMillis - ]; - } - } + mpInstance._Store.processConfig(config); - // add a new function to apply items to the store that require config to be returned - mpInstance._Store.storageName = mpInstance._Helpers.createMainStorageName( - config.workspaceToken - ); - mpInstance._Store.prodStorageName = mpInstance._Helpers.createProductStorageName( - config.workspaceToken - ); - - // idCache is instantiated here as opposed to when _Identity is instantiated - // because it depends on _Store.storageName, which is not sent until above - // because it is a setting on config which returns asyncronously - // in self hosted mode mpInstance._Identity.idCache = new LocalStorageVault( `${mpInstance._Store.storageName}-id-cache`, { @@ -1325,28 +1300,6 @@ function completeSDKInitialization(apiKey, config, mpInstance) { removeExpiredIdentityCacheDates(mpInstance._Identity.idCache); - if (config.hasOwnProperty('workspaceToken')) { - mpInstance._Store.SDKConfig.workspaceToken = config.workspaceToken; - } else { - mpInstance.Logger.warning( - 'You should have a workspaceToken on your config object for security purposes.' - ); - } - - if (config.hasOwnProperty('requiredWebviewBridgeName')) { - mpInstance._Store.SDKConfig.requiredWebviewBridgeName = - config.requiredWebviewBridgeName; - } else if (config.hasOwnProperty('workspaceToken')) { - mpInstance._Store.SDKConfig.requiredWebviewBridgeName = - config.workspaceToken; - } - mpInstance._Store.webviewBridgeEnabled = mpInstance._NativeSdkHelpers.isWebviewEnabled( - mpInstance._Store.SDKConfig.requiredWebviewBridgeName, - mpInstance._Store.SDKConfig.minWebviewBridgeVersion - ); - - mpInstance._Store.configurationLoaded = true; - // https://go.mparticle.com/work/SQDSDKS-6044 if (!mpInstance._Store.webviewBridgeEnabled) { // Load any settings/identities/attributes from cookie or localStorage diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index c0251e81..7f8bf9c7 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -246,6 +246,8 @@ export interface SDKIdentityApi { export interface SDKHelpersApi { canLog?(): boolean; + createMainStorageName?(workspaceToken: string): string; + createProductStorageName?(workspaceToken: string): string; createServiceUrl(arg0: string, arg1: string): void; createXHR?(cb: () => void): XMLHttpRequest; extend?(...args: any[]); diff --git a/src/store.ts b/src/store.ts index f0c8de4e..aa9d4808 100644 --- a/src/store.ts +++ b/src/store.ts @@ -69,6 +69,9 @@ export interface SDKConfig { v1SecureServiceUrl?: string; v2SecureServiceUrl?: string; v3SecureServiceUrl?: string; + webviewBridgeName?: string; + workspaceToken?: string; + requiredWebviewBridgeName?: string; } function createSDKConfig(config: SDKInitConfig): SDKConfig { @@ -112,7 +115,7 @@ interface WrapperSDKInfo { // https://go.mparticle.com/work/SQDSDKS-5954 export interface IFeatureFlags { - reportBatching?: string; + reportBatching?: boolean; eventBatchingIntervalMillis?: number; offlineStorage?: string; directURLRouting?: boolean; @@ -164,6 +167,7 @@ export interface IStore { wrapperSDKInfo: WrapperSDKInfo; nullifySession?: () => void; + processConfig(config: SDKInitConfig): void; } // TODO: Merge this with SDKStoreApi in sdkRuntimeModels @@ -173,6 +177,11 @@ export default function Store( mpInstance: MParticleWebSDK, apiKey?: string ) { + const { createMainStorageName, createProductStorageName } = + mpInstance._Helpers; + + const { isWebviewEnabled } = mpInstance._NativeSdkHelpers; + const defaultStore: Partial = { isEnabled: true, sessionAttributes: {}, @@ -233,8 +242,7 @@ export default function Store( this.SDKConfig.flags = {}; } - this.SDKConfig.flags = processFlags(config, this - .SDKConfig as SDKConfig); + this.SDKConfig.flags = processFlags(config); if (config.deviceId) { this.deviceId = config.deviceId; @@ -427,12 +435,38 @@ export default function Store( this.sessionAttributes = {}; mpInstance._Persistence.update(); }; + + this.processConfig = (config: SDKInitConfig) => { + const { workspaceToken, requiredWebviewBridgeName } = config; + + // TODO: refactor to use flags directly + this.SDKConfig.flags = processFlags(config); + + if (workspaceToken) { + this.SDKConfig.workspaceToken = workspaceToken; + } else { + mpInstance.Logger.warning( + 'You should have a workspaceToken on your config object for security purposes.' + ); + } + // add a new function to apply items to the store that require config to be returned + this.storageName = createMainStorageName(workspaceToken); + this.prodStorageName = createProductStorageName(workspaceToken); + + this.SDKConfig.requiredWebviewBridgeName = + requiredWebviewBridgeName || workspaceToken; + + this.webviewBridgeEnabled = isWebviewEnabled( + this.SDKConfig.requiredWebviewBridgeName, + this.SDKConfig.minWebviewBridgeVersion + ); + + this.configurationLoaded = true; + }; } -export function processFlags( - config: SDKInitConfig, - SDKConfig: SDKConfig -): IFeatureFlags { +// TODO: Refactor to use flags directly +export function processFlags(config: SDKInitConfig): IFeatureFlags { const flags: IFeatureFlags = {}; const { ReportBatching, @@ -446,6 +480,7 @@ export function processFlags( return {}; } + // https://go.mparticle.com/work/SQDSDKS-6317 // Passed in config flags take priority over defaults flags[ReportBatching] = config.flags[ReportBatching] || false; // The server returns stringified numbers, sowe need to parse diff --git a/test/src/tests-store.ts b/test/src/tests-store.ts index ad230f8a..889ec4e3 100644 --- a/test/src/tests-store.ts +++ b/test/src/tests-store.ts @@ -341,19 +341,173 @@ describe('Store', () => { }); }); + describe('#processConfig', () => { + it('should process feature flags', () => { + const config = { + ...sampleConfig, + flags: { + reportBatching: false, // This should be a string + eventBatchingIntervalMillis: '42000', + offlineStorage: '42', + directURLRouting: 'False', + cacheIdentity: 'False', + }, + }; + + const store: IStore = new Store( + config, + window.mParticle.getInstance() + ); + + store.processConfig(config); + + const expectedResult = { + reportBatching: false, + eventBatchingIntervalMillis: 42000, + offlineStorage: '42', + directURLRouting: false, + cacheIdentity: false, + }; + + // TODO: This passes even though we're only doing this in the constructor. + // Should we move the processFlags call into this method? + expect(store.SDKConfig.flags).to.deep.equal(expectedResult); + }); + + it('should process storage names', () => { + const config = { + ...sampleConfig, + workspaceToken: 'foo', + }; + + const store: IStore = new Store( + config, + window.mParticle.getInstance() + ); + + store.processConfig(config); + + expect(store.storageName, 'storageName').to.equal('mprtcl-v4_foo'); + expect(store.prodStorageName, 'prodStorageName').to.equal( + 'mprtcl-prodv4_foo' + ); + expect(store.SDKConfig.workspaceToken, 'workspace token').to.equal( + 'foo' + ); + }); + + it('should warn if workspace token is missing', () => { + const config = { + ...sampleConfig, + }; + + const store: IStore = new Store( + config, + window.mParticle.getInstance() + ); + + const warnSpy = sinon.spy( + window.mParticle.getInstance().Logger, + 'warning' + ); + + store.processConfig(config); + + expect(warnSpy.calledOnce, 'should call Logger.warn').to.be.true; + expect(warnSpy.getCall(0).firstArg).to.equal( + 'You should have a workspaceToken on your config object for security purposes.' + ); + }); + + it('should use a Web View Bridge Name if requiredWebviewBridgeName is present', () => { + const config = { + ...sampleConfig, + requiredWebviewBridgeName: 'my-webview-bridge-name', + workspaceToken: 'my-workspace-token', + }; + + const store: IStore = new Store( + config, + window.mParticle.getInstance() + ); + + debugger; + + store.processConfig(config); + + expect( + store.SDKConfig.requiredWebviewBridgeName, + 'webviewBridgeName' + ).to.equal('my-webview-bridge-name'); + }); + + it('should use a workspace token as the Web View Bridge Name if requiredWebviewBridgeName is not present ', () => { + const config = { + ...sampleConfig, + workspaceToken: 'my-workspace-token', + }; + + const store: IStore = new Store( + config, + window.mParticle.getInstance() + ); + + store.processConfig(config); + + expect( + store.SDKConfig.requiredWebviewBridgeName, + 'webviewBridgeName' + ).to.equal('my-workspace-token'); + }); + + it('should enable WebviewBridge if requiredWebviewBridgeName is present', () => { + const config = { + ...sampleConfig, + requiredWebviewBridgeName: 'my-webview-bridge-name', + }; + + const store: IStore = new Store( + config, + window.mParticle.getInstance() + ); + + // Webview bridge requires a bridge name set on the global mParticle object + // @ts-ignore + window.mParticle.uiwebviewBridgeName = + 'mParticle_my-webview-bridge-name_v2'; + + store.processConfig(config); + + expect(store.webviewBridgeEnabled, 'webviewBridgeEnabled').to.be + .true; + }); + + it('should set configurationLoaded to true if config is successfully processed', () => { + const config = { + ...sampleConfig, + }; + + const store: IStore = new Store( + config, + window.mParticle.getInstance() + ); + + store.processConfig(config); + + expect(store.configurationLoaded, 'configurationLoaded').to.be.true; + }); + }); + describe('#processFlags', () => { it('should return an empty object if no featureFlags are passed', () => { - const flags = processFlags({} as SDKInitConfig, {} as SDKConfig); + const flags = processFlags({} as SDKInitConfig); expect(Object.keys(flags).length).to.equal(0); }); it('should return default featureFlags if no featureFlags are passed', () => { - const flags = processFlags( - { flags: {} } as SDKInitConfig, - {} as SDKConfig - ); + const flags = processFlags({ flags: {} } as SDKInitConfig); const expectedResult = { - reportBatching: false, + reportBatching: false, // This should be a string eventBatchingIntervalMillis: 0, offlineStorage: '0', directURLRouting: false, @@ -365,8 +519,8 @@ describe('Store', () => { it('should return featureFlags if featureFlags are passed in', () => { const cutomizedFlags = { - reportBatching: true, - eventBatchingIntervalMillis: 5000, + reportBatching: true, // This should be a string + eventBatchingIntervalMillis: '5000', offlineStorage: '100', directURLRouting: 'True', cacheIdentity: 'True',