From 5c270d66b61e9199a3a8255e6cfc95521c21f312 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Mon, 13 Nov 2023 17:18:06 -0500 Subject: [PATCH] feat: Support direct url routing from config (#797) --- src/apiClient.ts | 6 +- src/constants.ts | 5 +- src/mp-instance.js | 3 +- src/store.ts | 190 ++++++++++++++++++++++++---------- test/src/tests-core-sdk.js | 202 +++++++++++++++++++++++++++++++++++-- test/src/tests-store.ts | 169 +++++++++++++++++++++++++++++-- 6 files changed, 496 insertions(+), 79 deletions(-) diff --git a/src/apiClient.ts b/src/apiClient.ts index 5f620764..9acc2c1e 100644 --- a/src/apiClient.ts +++ b/src/apiClient.ts @@ -8,7 +8,7 @@ import { SDKEvent, } from './sdkRuntimeModels'; import KitBlocker from './kitBlocking'; -import { Dictionary, getRampNumber, isEmpty } from './utils'; +import { Dictionary, getRampNumber, isEmpty, parseNumber } from './utils'; import { IUploadObject } from './serverModel'; export type ForwardingStatsData = Dictionary; @@ -42,9 +42,9 @@ export default function APIClient( const self = this; this.queueEventForBatchUpload = function(event: SDKEvent) { if (!this.uploader) { - const millis = mpInstance._Helpers.getFeatureFlag( + const millis: number = parseNumber(mpInstance._Helpers.getFeatureFlag( Constants.FeatureFlags.EventBatchingIntervalMillis - ); + )); this.uploader = new BatchUploader(mpInstance, millis); } this.uploader.queueEvent(event); diff --git a/src/constants.ts b/src/constants.ts index e49d5e2c..1f02a9bb 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -126,9 +126,9 @@ const Constants = { integrationDelayTimeout: 5000, // Milliseconds for forcing the integration delay to un-suspend event queueing due to integration partner errors maxCookieSize: 3000, // Number of bytes for cookie size to not exceed aliasMaxWindow: 90, // Max age of Alias request startTime, in days - uploadInterval: 0, // Maximum milliseconds in between batch uploads, below 500 will mean immediate upload + uploadInterval: 0, // Maximum milliseconds in between batch uploads, below 500 will mean immediate upload. The server returns this as a string, but we are using it as a number internally }, - DefaultUrls: { + DefaultBaseUrls: { v1SecureServiceUrl: 'jssdks.mparticle.com/v1/JS/', v2SecureServiceUrl: 'jssdks.mparticle.com/v2/JS/', v3SecureServiceUrl: 'jssdks.mparticle.com/v3/JS/', @@ -166,6 +166,7 @@ const Constants = { ReportBatching: 'reportBatching', EventBatchingIntervalMillis: 'eventBatchingIntervalMillis', OfflineStorage: 'offlineStorage', + DirectUrlRouting: 'directURLRouting', }, DefaultInstance: 'default_instance', CCPAPurpose: 'data_sale_opt_out', diff --git a/src/mp-instance.js b/src/mp-instance.js index a719dbe0..92d15549 100644 --- a/src/mp-instance.js +++ b/src/mp-instance.js @@ -1523,9 +1523,8 @@ function createKitBlocker(config, mpInstance) { function runPreConfigFetchInitialization(mpInstance, apiKey, config) { mpInstance.Logger = new Logger(config); - mpInstance._Store = new Store(config, mpInstance); + mpInstance._Store = new Store(config, mpInstance, apiKey); window.mParticle.Store = mpInstance._Store; - mpInstance._Store.devToken = apiKey || null; mpInstance.Logger.verbose( Messages.InformationMessages.StartingInitialization ); diff --git a/src/store.ts b/src/store.ts index 2ee1d91d..343773fd 100644 --- a/src/store.ts +++ b/src/store.ts @@ -22,7 +22,7 @@ import { SDKInitConfig, SDKProduct, } from './sdkRuntimeModels'; -import { isNumber, isDataPlanSlug, Dictionary } from './utils'; +import { isNumber, isDataPlanSlug, Dictionary, parseNumber } from './utils'; import { SDKConsentState } from './consent'; // This represents the runtime configuration of the SDK AFTER @@ -44,7 +44,7 @@ export interface SDKConfig { appName?: string; appVersion?: string; package?: string; - flags?: { [key: string]: string | number | boolean }; + flags?: IFeatureFlags; kitConfigs: IKitConfigs[]; kits: Dictionary; logLevel?: LogLevelType; @@ -90,8 +90,8 @@ function createSDKConfig(config: SDKInitConfig): SDKConfig { } } - for (const prop in Constants.DefaultUrls) { - sdkConfig[prop] = Constants.DefaultUrls[prop]; + for (const prop in Constants.DefaultBaseUrls) { + sdkConfig[prop] = Constants.DefaultBaseUrls[prop]; } return sdkConfig; @@ -111,6 +111,14 @@ interface WrapperSDKInfo { isInfoSet: boolean; } +// https://go.mparticle.com/work/SQDSDKS-5954 +export interface IFeatureFlags { + reportBatching?: string; + eventBatchingIntervalMillis?: number; + offlineStorage?: string; + directURLRouting?: boolean; +} + // Temporary Interface until Store can be refactored as a class export interface IStore { isEnabled: boolean; @@ -160,7 +168,8 @@ export interface IStore { export default function Store( this: IStore, config: SDKInitConfig, - mpInstance: MParticleWebSDK + mpInstance: MParticleWebSDK, + apiKey?: string ) { const defaultStore: Partial = { isEnabled: true, @@ -210,12 +219,21 @@ export default function Store( for (var key in defaultStore) { this[key] = defaultStore[key]; } + this.devToken = apiKey || null; this.integrationDelayTimeoutStart = Date.now(); - this.SDKConfig = createSDKConfig(config); // Set configuration to default settings + this.SDKConfig = createSDKConfig(config); + if (config) { + if (!config.hasOwnProperty('flags')) { + this.SDKConfig.flags = {}; + } + + this.SDKConfig.flags = processFlags(config, this + .SDKConfig as SDKConfig); + if (config.deviceId) { this.deviceId = config.deviceId; } @@ -227,28 +245,14 @@ export default function Store( this.SDKConfig.isDevelopmentMode = false; } - if (config.hasOwnProperty('v1SecureServiceUrl')) { - this.SDKConfig.v1SecureServiceUrl = config.v1SecureServiceUrl; - } - - if (config.hasOwnProperty('v2SecureServiceUrl')) { - this.SDKConfig.v2SecureServiceUrl = config.v2SecureServiceUrl; - } + const baseUrls: Dictionary = processBaseUrls( + config, + this.SDKConfig.flags, + apiKey + ); - if (config.hasOwnProperty('v3SecureServiceUrl')) { - this.SDKConfig.v3SecureServiceUrl = config.v3SecureServiceUrl; - } - - if (config.hasOwnProperty('identityUrl')) { - this.SDKConfig.identityUrl = config.identityUrl; - } - - if (config.hasOwnProperty('aliasUrl')) { - this.SDKConfig.aliasUrl = config.aliasUrl; - } - - if (config.hasOwnProperty('configUrl')) { - this.SDKConfig.configUrl = config.configUrl; + for (const baseUrlKeys in baseUrls) { + this.SDKConfig[baseUrlKeys] = baseUrls[baseUrlKeys]; } if (config.hasOwnProperty('logLevel')) { @@ -413,32 +417,112 @@ export default function Store( this.SDKConfig.onCreateBatch = undefined; } } + } +} - if (!config.hasOwnProperty('flags')) { - this.SDKConfig.flags = {}; - } - if ( - !this.SDKConfig.flags.hasOwnProperty( - Constants.FeatureFlags.EventBatchingIntervalMillis - ) - ) { - this.SDKConfig.flags[ - Constants.FeatureFlags.EventBatchingIntervalMillis - ] = Constants.DefaultConfig.uploadInterval; - } - if ( - !this.SDKConfig.flags.hasOwnProperty( - Constants.FeatureFlags.ReportBatching - ) - ) { - this.SDKConfig.flags[Constants.FeatureFlags.ReportBatching] = false; - } - if ( - !this.SDKConfig.flags.hasOwnProperty( - Constants.FeatureFlags.OfflineStorage - ) - ) { - this.SDKConfig.flags[Constants.FeatureFlags.OfflineStorage] = 0; - } +export function processFlags( + config: SDKInitConfig, + SDKConfig: SDKConfig +): IFeatureFlags { + const flags: IFeatureFlags = {}; + const { + ReportBatching, + EventBatchingIntervalMillis, + OfflineStorage, + DirectUrlRouting, + } = Constants.FeatureFlags; + + if (!config.flags) { + return {}; + } + + // Passed in config flags take priority over defaults + flags[ReportBatching] = config.flags[ReportBatching] || false; + // The server returns stringified numbers, sowe need to parse + flags[EventBatchingIntervalMillis] = + parseNumber(config.flags[EventBatchingIntervalMillis]) || + Constants.DefaultConfig.uploadInterval; + flags[OfflineStorage] = config.flags[OfflineStorage] || '0'; + flags[DirectUrlRouting] = config.flags[DirectUrlRouting] === 'True'; + + return flags; +} + +export function processBaseUrls( + config: SDKInitConfig, + flags: IFeatureFlags, + apiKey?: string +): Dictionary { + // an API key is not present in a webview only mode. In this case, no baseUrls are needed + if (!apiKey) { + return {}; + } + + // Set default baseUrls + let baseUrls: Dictionary; + + // When direct URL routing is false, update baseUrls based custom urls + // passed to the config + if (flags.directURLRouting) { + return processDirectBaseUrls(config, apiKey); + } else { + return processCustomBaseUrls(config); } } + +function processCustomBaseUrls(config: SDKInitConfig): Dictionary { + const defaultBaseUrls: Dictionary = Constants.DefaultBaseUrls; + const newBaseUrls: Dictionary = {}; + + // If there is no custo base url, we use the default base url + for (let baseUrlKey in defaultBaseUrls) { + newBaseUrls[baseUrlKey] = + config[baseUrlKey] || defaultBaseUrls[baseUrlKey]; + } + + return newBaseUrls; +} + +function processDirectBaseUrls( + config: SDKInitConfig, + apiKey: string +): Dictionary { + const defaultBaseUrls = Constants.DefaultBaseUrls; + const directBaseUrls: Dictionary = {}; + // When Direct URL Routing is true, we create a new set of baseUrls that + // include the silo in the urls. mParticle API keys are prefixed with the + // silo and a hyphen (ex. "us1-", "us2-", "eu1-"). us1 was the first silo, + // and before other silos existed, there were no prefixes and all apiKeys + // were us1. As such, if we split on a '-' and the resulting array length + // is 1, then it is an older APIkey that should route to us1. + // When splitKey.length is greater than 1, then splitKey[0] will be + // us1, us2, eu1, au1, or st1, etc as new silos are added + const DEFAULT_SILO = 'us1'; + const splitKey: Array = apiKey.split('-'); + const routingPrefix: string = + splitKey.length <= 1 ? DEFAULT_SILO : splitKey[0]; + + for (let baseUrlKey in defaultBaseUrls) { + // Any custom endpoints passed to mpConfig will take priority over direct + // mapping to the silo. The most common use case is a customer provided CNAME. + if (baseUrlKey === 'configUrl') { + directBaseUrls[baseUrlKey] = + config[baseUrlKey] || defaultBaseUrls[baseUrlKey]; + continue; + } + + if (config.hasOwnProperty(baseUrlKey)) { + directBaseUrls[baseUrlKey] = config[baseUrlKey]; + } else { + const urlparts = defaultBaseUrls[baseUrlKey].split('.'); + + directBaseUrls[baseUrlKey] = [ + urlparts[0], + routingPrefix, + ...urlparts.slice(1), + ].join('.'); + } + } + + return directBaseUrls; +} \ No newline at end of file diff --git a/test/src/tests-core-sdk.js b/test/src/tests-core-sdk.js index e667fff6..49135dd1 100644 --- a/test/src/tests-core-sdk.js +++ b/test/src/tests-core-sdk.js @@ -568,7 +568,7 @@ describe('core SDK', function() { }); it('should have default options as well as configured options on configuration object, overwriting when appropriate', function(done) { - const defaults = new Store({}, mParticle.getInstance()); + const defaults = new Store({}, mParticle.getInstance(), apiKey); // all items here should be the default values for (const key in DefaultConfig) { defaults.SDKConfig.should.have.property(key, DefaultConfig[key]); @@ -786,12 +786,12 @@ describe('core SDK', function() { mParticle.init(apiKey, window.mParticle.config); - mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(Constants.DefaultUrls.v1SecureServiceUrl); - mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(Constants.DefaultUrls.v2SecureServiceUrl) - mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(Constants.DefaultUrls.v3SecureServiceUrl) - mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultUrls.configUrl) - mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(Constants.DefaultUrls.identityUrl) - mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(Constants.DefaultUrls.aliasUrl) + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(Constants.DefaultBaseUrls.v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(Constants.DefaultBaseUrls.v2SecureServiceUrl) + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(Constants.DefaultBaseUrls.v3SecureServiceUrl) + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl) + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(Constants.DefaultBaseUrls.identityUrl) + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(Constants.DefaultBaseUrls.aliasUrl) done(); }); @@ -876,7 +876,6 @@ describe('core SDK', function() { mockServer.requests = []; mParticle.init(apiKey, window.mParticle.config); - window.mParticle.logEvent('Test Event'); fetchMock.lastOptions().body.should.be.ok() @@ -1134,4 +1133,191 @@ describe('core SDK', function() { done(); }); + + describe('pod feature flag', function() { + const endpoints = Constants.DefaultBaseUrls; + // set up URLs object for each silo + const URLs = { + us1: {}, + us2: {}, + eu1: {}, + au1: {}, + st1: {}, + xy1: {} // this is a fake silo used to show that there is no logic that is based on a pre-determined set of silos + }; + + // The below function builds out the above URLs object to have silo-specific urls, ie: + // URLs.us1.aliasUrl = 'jssdks.us1.mparticle.com/v1/identity/'; + // URLs.us2.aliasUrl = 'jssdks.us2.mparticle.com/v1/identity/'; + // etc, etc for each silo, and each endpoint + Object.keys(URLs).forEach((key) => { + for (let endpointKey in endpoints) { + if (endpointKey === 'configUrl') { + // Do not route config url to silo, use the default instead + URLs[key][endpointKey] = endpoints[endpointKey]; + } + const endpointParts = endpoints[endpointKey].split('.'); + URLs[key][endpointKey] = [endpointParts[0], key, ...endpointParts.slice(1)].join('.') + } + }); + + beforeEach(function() { + window.mParticle.config.flags = { + directURLRouting: 'True' + }; + }); + + it('should use US1 endpoints for apiKeys that do not start with a prefix', function(done) { + const silo = 'us1'; + const apiKey = 'noSiloPrefixApiKey'; + const eventsEndpoint = `https://${URLs[silo].v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200); + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(URLs[silo].aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(URLs[silo].identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(URLs[silo].v3SecureServiceUrl); + + done(); + }); + + it('should use US1 endpoints for apiKeys with prefix `us1`', function(done) { + const silo = 'us1'; + const apiKey = 'us1-apiKey'; + const eventsEndpoint = `https://${URLs.us1.v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200); + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(URLs[silo].aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(URLs[silo].identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(URLs[silo].v3SecureServiceUrl); + + done(); + }); + + it('should use US2 endpoints for apiKeys with prefix `us2`', function(done) { + const silo = 'us2'; + const apiKey = 'us2-apiKey'; + const eventsEndpoint = `https://${URLs[silo].v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200); + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(URLs[silo].aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(URLs[silo].identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(URLs[silo].v3SecureServiceUrl); + + done(); + }); + + it('should use EU1 endpoints for apiKeys with prefix `eu1`', function(done) { + const silo = 'eu1'; + const apiKey = 'eu1-apiKey'; + const eventsEndpoint = `https://${URLs[silo].v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200); + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(URLs[silo].aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(URLs[silo].identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(URLs[silo].v3SecureServiceUrl); + + done(); + }); + + it('should use AU1 endpoints for apiKeys with prefix `au1`', function(done) { + const silo = 'au1'; + const apiKey = 'au1-apiKey'; + const eventsEndpoint = `https://${URLs[silo].v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200); + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(URLs[silo].aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(URLs[silo].identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(URLs[silo].v3SecureServiceUrl); + + done(); + }); + + it('should use ST1 endpoints for apiKeys with prefix `st1`', function(done) { + const silo = 'st1'; + const apiKey = 'st1-apiKey'; + const eventsEndpoint = `https://${URLs[silo].v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200); + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(URLs[silo].aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(URLs[silo].identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(URLs[silo].v3SecureServiceUrl); + + done(); + }); + + it('should use xy1 endpoints for apiKeys with prefix `xy1`', function(done) { + const silo = 'xy1'; + const apiKey = 'xy1-apiKey'; + const eventsEndpoint = `https://${URLs[silo].v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200); + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(URLs[silo].aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(Constants.DefaultBaseUrls.configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(URLs[silo].identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(URLs[silo].v3SecureServiceUrl); + + done(); + }); + + it('should prioritize configured URLs over direct URL mapping', function(done) { + window.mParticle.config.v3SecureServiceUrl = 'testtesttest-custom-v3secureserviceurl/v3/JS/'; + window.mParticle.config.configUrl ='foo-custom-configUrl/v2/JS/'; + window.mParticle.config.identityUrl = 'custom-identityUrl/'; + window.mParticle.config.aliasUrl = 'custom-aliasUrl/'; + + const {configUrl, v3SecureServiceUrl, identityUrl, aliasUrl} = window.mParticle.config + + const silo = 'us1'; + const apiKey = 'noSiloPrefixApiKey'; + const eventsEndpoint = `https://${v3SecureServiceUrl}${apiKey}/events`; + + fetchMock.post(eventsEndpoint, 200) + + mParticle.init(apiKey, window.mParticle.config); + mParticle.getInstance()._Store.SDKConfig.aliasUrl.should.equal(aliasUrl); + mParticle.getInstance()._Store.SDKConfig.configUrl.should.equal(configUrl); + mParticle.getInstance()._Store.SDKConfig.identityUrl.should.equal(identityUrl); + mParticle.getInstance()._Store.SDKConfig.v1SecureServiceUrl.should.equal(URLs[silo].v1SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v2SecureServiceUrl.should.equal(URLs[silo].v2SecureServiceUrl); + mParticle.getInstance()._Store.SDKConfig.v3SecureServiceUrl.should.equal(v3SecureServiceUrl); + + done(); + }); + + + }); }); \ No newline at end of file diff --git a/test/src/tests-store.ts b/test/src/tests-store.ts index b9a5d079..9efaf469 100644 --- a/test/src/tests-store.ts +++ b/test/src/tests-store.ts @@ -1,9 +1,12 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { SDKInitConfig } from '../../src/sdkRuntimeModels'; -import Store, { IStore } from '../../src/store'; +import Store, { IStore, SDKConfig, processFlags, processBaseUrls, IFeatureFlags } from '../../src/store'; import { MPConfig, apiKey } from './config'; import Utils from './utils'; +import { Dictionary } from '../../src/utils'; +import Constants from '../../src/constants'; + const MockSideloadedKit = Utils.MockSideloadedKit; describe('Store', () => { @@ -15,6 +18,7 @@ describe('Store', () => { appName: 'Store Test', appVersion: '1.x', package: 'com.mparticle.test', + flags: {} } as SDKInitConfig; beforeEach(function() { @@ -32,8 +36,7 @@ describe('Store', () => { it('should initialize Store with defaults', () => { // Use sample config to make sure our types are safe - const store: IStore = new Store(sampleConfig, window.mParticle); - + const store: IStore = new Store(sampleConfig, window.mParticle.getInstance()); expect(store).to.be.ok; expect(store.isEnabled, 'isEnabled').to.eq(true); expect(store.sessionAttributes, 'sessionAttributes').to.be.ok; @@ -82,7 +85,7 @@ describe('Store', () => { }); it('should initialize store.SDKConfig with valid defaults', () => { - const store: IStore = new Store(sampleConfig, window.mParticle); + const store: IStore = new Store(sampleConfig, window.mParticle.getInstance()); expect(store.SDKConfig.aliasMaxWindow, 'aliasMaxWindow').to.eq(90); expect(store.SDKConfig.aliasUrl, 'aliasUrl').to.eq( @@ -104,13 +107,9 @@ describe('Store', () => { .undefined; expect( - store.SDKConfig.flags.eventBatchingIntervalMillis, + store.SDKConfig.flags?.eventBatchingIntervalMillis, 'flags.eventBatchingIntervalMillis' ).to.eq(0); - expect( - store.SDKConfig.flags.reportBatching, - 'flags.reportBatching' - ).to.eq(false); expect(store.SDKConfig.forceHttps, 'forceHttps').to.eq(true); expect(store.SDKConfig.identityCallback, 'identityCallback').to.be @@ -168,7 +167,7 @@ describe('Store', () => { planVersion: 3, }, }; - const store: IStore = new Store(dataPlanConfig, window.mParticle); + const store: IStore = new Store(dataPlanConfig, window.mParticle.getInstance()); expect(store.SDKConfig.dataPlan, 'dataPlan').to.deep.equal({ PlanId: 'test_data_plan', @@ -185,10 +184,158 @@ describe('Store', () => { ...sampleConfig, sideloadedKits, }; - const store: IStore = new Store(config, window.mParticle); + const store: IStore = new Store(config, window.mParticle.getInstance()); expect(store.SDKConfig.sideloadedKits.length, 'side loaded kits').to.equal(sideloadedKits.length); expect(store.SDKConfig.sideloadedKits[0], 'side loaded kits').to.deep.equal(sideloadedKit1); expect(store.SDKConfig.sideloadedKits[1], 'side loaded kits').to.deep.equal(sideloadedKit2); }); + + it('should assign apiKey to devToken property', () => { + const config = { + ...sampleConfig + }; + + const store: IStore = new Store(config, window.mParticle.getInstance(), apiKey); + + expect(store.devToken, 'devToken').to.equal(apiKey); + }); + + describe('#processFlags', () => { + it('should return an empty object if no featureFlags are passed', () => { + const flags = processFlags({} as SDKInitConfig, {} as SDKConfig); + 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 expectedResult = { + reportBatching: false, + eventBatchingIntervalMillis: 0, + offlineStorage: '0', + directURLRouting: false, + }; + + expect(flags).to.deep.equal(expectedResult); + }); + + it('should return featureFlags if featureFlags are passed in', () => { + const cutomizedFlags = { + reportBatching: true, + eventBatchingIntervalMillis: 5000, + offlineStorage: '100', + directURLRouting: 'True', + }; + + const flags = processFlags({flags: cutomizedFlags} as unknown as SDKInitConfig, {} as SDKConfig); + + const expectedResult = { + reportBatching: true, + eventBatchingIntervalMillis: 5000, + offlineStorage: '100', + directURLRouting: true, + } + + expect(flags).to.deep.equal(expectedResult); + }); + }); + + describe('#processBaseUrls', () => { + describe('directURLRouting === false', () => { + const featureFlags = { directURLRouting: false }; + + it('should return default baseUrls if no baseUrls are passed', () => { + const baseUrls: Dictionary = Constants.DefaultBaseUrls; + const result = processBaseUrls( + {} as unknown as SDKInitConfig, + featureFlags as unknown as IFeatureFlags, + 'apikey' + ); + + expect(result).to.deep.equal(baseUrls) + }); + + it('should return non-default baseUrls for custom baseUrls that are passed', () => { + const config = { + v3SecureServiceUrl: 'foo.customer.mp.com/v3/JS/', + configUrl: 'foo-configUrl.customer.mp.com/v2/JS/', + identityUrl: 'foo-identity.customer.mp.com/', + aliasUrl: 'foo-alias.customer.mp.com/', + }; + + const result = processBaseUrls( + config as unknown as SDKInitConfig, + featureFlags as unknown as IFeatureFlags, + 'apikey' + ); + + const expectedResult = { + v3SecureServiceUrl: 'foo.customer.mp.com/v3/JS/', + configUrl: 'foo-configUrl.customer.mp.com/v2/JS/', + identityUrl: 'foo-identity.customer.mp.com/', + aliasUrl: 'foo-alias.customer.mp.com/', + v1SecureServiceUrl: "jssdks.mparticle.com/v1/JS/", + v2SecureServiceUrl: "jssdks.mparticle.com/v2/JS/", + } + + expect(result).to.deep.equal(expectedResult) + }); + }); + + describe('directURLRouting === true', () => { + it('should return direct urls when no baseUrls are passed ', () => { + const featureFlags = {directURLRouting: true}; + + const result = processBaseUrls( + {} as unknown as SDKInitConfig, + featureFlags as unknown as IFeatureFlags, + 'apikey' + ); + + const expectedResult = { + configUrl: Constants.DefaultBaseUrls.configUrl, + aliasUrl: "jssdks.us1.mparticle.com/v1/identity/", + identityUrl: "identity.us1.mparticle.com/v1/", + v1SecureServiceUrl: "jssdks.us1.mparticle.com/v1/JS/", + v2SecureServiceUrl: "jssdks.us1.mparticle.com/v2/JS/", + v3SecureServiceUrl: "jssdks.us1.mparticle.com/v3/JS/", + }; + + expect(result.aliasUrl).to.equal(expectedResult.aliasUrl) + expect(result.configUrl).to.equal(expectedResult.configUrl) + expect(result.identityUrl).to.equal(expectedResult.identityUrl) + expect(result.v1SecureServiceUrl).to.equal(expectedResult.v1SecureServiceUrl) + expect(result.v2SecureServiceUrl).to.equal(expectedResult.v2SecureServiceUrl) + expect(result.v3SecureServiceUrl).to.equal(expectedResult.v3SecureServiceUrl) + }); + + it('should prioritize passed in baseUrls over direct urls', () => { + const featureFlags = {directURLRouting: true}; + + const config = { + v3SecureServiceUrl: 'foo.customer.mp.com/v3/JS/', + configUrl: 'foo-configUrl.customer.mp.com/v2/JS/', + identityUrl: 'foo-identity.customer.mp.com/', + aliasUrl: 'foo-alias.customer.mp.com/', + }; + + const result = processBaseUrls( + config as unknown as SDKInitConfig, + featureFlags as unknown as IFeatureFlags, + 'apikey' + ); + + const expectedResult = { + v3SecureServiceUrl: 'foo.customer.mp.com/v3/JS/', + configUrl: 'foo-configUrl.customer.mp.com/v2/JS/', + identityUrl: 'foo-identity.customer.mp.com/', + aliasUrl: 'foo-alias.customer.mp.com/', + v1SecureServiceUrl: "jssdks.us1.mparticle.com/v1/JS/", + v2SecureServiceUrl: "jssdks.us1.mparticle.com/v2/JS/", + }; + + expect(result).to.deep.equal(expectedResult) + }); + }); + }); });