@mparticle/web-sdk
Version:
mParticle core SDK for web applications
893 lines (770 loc) • 30.2 kB
text/typescript
import { Batch, Context } from '@mparticle/event-models';
import {
DataPlanConfig,
MPID,
IdentifyRequest,
SDKEventCustomFlags,
ConsentState,
UserIdentities,
} from '@mparticle/web-sdk';
import { IKitConfigs } from './configAPIClient';
import Constants from './constants';
import {
DataPlanResult,
KitBlockerOptions,
LogLevelType,
SDKDataPlan,
SDKEvent,
SDKGeoLocation,
SDKInitConfig,
SDKProduct,
} from './sdkRuntimeModels';
import {
Dictionary,
isDataPlanSlug,
isEmpty,
isNumber,
isObject,
moveElementToEnd,
parseNumber,
returnConvertedBoolean,
isValidAttributeValue,
findKeyInObject,
AttributeValue,
} from './utils';
import { IMinifiedConsentJSONObject, SDKConsentState } from './consent';
import { ConfiguredKit, MPForwarder, UnregisteredKit } from './forwarders.interfaces';
import { IdentityCallback, UserAttributes } from './identity-user-interfaces';
import {
IGlobalStoreV2MinifiedKeys,
IPersistenceMinified,
} from './persistence.interfaces';
import { CookieSyncDates, IPixelConfiguration } from './cookieSyncManager';
import { IMParticleWebSDKInstance } from './mp-instance';
import ForegroundTimer from './foregroundTimeTracker';
const { Messages } = Constants;
// This represents the runtime configuration of the SDK AFTER
// initialization has been complete and all settings and
// configurations have been stitched together.
export interface SDKConfig {
isDevelopmentMode?: boolean;
logger: {
error?(msg);
warning?(msg);
verbose?(msg);
};
onCreateBatch(batch: Batch): Batch;
customFlags?: SDKEventCustomFlags;
dataPlan: SDKDataPlan;
dataPlanOptions: KitBlockerOptions; // when the user provides their own data plan
dataPlanResult?: DataPlanResult; // when the data plan comes from the server via /config
appName?: string;
appVersion?: string;
package?: string;
flags?: IFeatureFlags;
kitConfigs: IKitConfigs[];
kits: Dictionary<UnregisteredKit>;
logLevel?: LogLevelType;
cookieDomain?: string;
maxCookieSize?: number | undefined;
minWebviewBridgeVersion: 1 | 2 | undefined;
identifyRequest: IdentifyRequest;
identityCallback: IdentityCallback;
integrationDelayTimeout: number;
sideloadedKits: MPForwarder[];
aliasMaxWindow: number;
deviceId?: string;
forceHttps?: boolean;
aliasUrl?: string;
configUrl?: string;
identityUrl?: string;
userAudienceUrl?: string;
isIOS?: boolean;
maxAliasWindow: number;
maxProducts: number;
requestConfig?: boolean;
sessionTimeout?: number;
useNativeSdk?: boolean;
useCookieStorage?: boolean;
v1SecureServiceUrl?: string;
// https://go.mparticle.com/work/SQDSDKS-6618
v2SecureServiceUrl?: string;
v3SecureServiceUrl?: string;
webviewBridgeName?: string;
workspaceToken?: string;
requiredWebviewBridgeName?: string;
}
function createSDKConfig(config: SDKInitConfig): SDKConfig {
// TODO: Refactor to create a default config object
const sdkConfig = {} as SDKConfig;
for (const prop in Constants.DefaultConfig) {
if (Constants.DefaultConfig.hasOwnProperty(prop)) {
sdkConfig[prop] = Constants.DefaultConfig[prop];
}
}
if (config) {
for (const prop in config) {
if (config.hasOwnProperty(prop)) {
sdkConfig[prop] = config[prop];
}
}
}
for (const prop in Constants.DefaultBaseUrls) {
sdkConfig[prop] = Constants.DefaultBaseUrls[prop];
}
return sdkConfig;
}
// TODO: Placeholder Types to be filled in as we migrate more modules
// to TypeScript
export type ServerSettings = Dictionary;
export type SessionAttributes = Dictionary;
export type LocalSessionAttributes = Dictionary;
export type IntegrationAttribute = Dictionary<string>;
export type IntegrationAttributes = Dictionary<IntegrationAttribute>;
export type WrapperSDKTypes = 'flutter' | 'none';
export interface WrapperSDKInfo {
name: WrapperSDKTypes;
version: string | null;
isInfoSet: boolean;
}
// https://go.mparticle.com/work/SQDSDKS-5954
export interface IFeatureFlags {
reportBatching?: boolean;
eventBatchingIntervalMillis?: number;
offlineStorage?: string;
directURLRouting?: boolean;
cacheIdentity?: boolean;
captureIntegrationSpecificIds?: boolean;
captureIntegrationSpecificIdsV2?: string;
astBackgroundEvents?: boolean;
}
// Temporary Interface until Store can be refactored as a class
export interface IStore {
isEnabled: boolean;
isInitialized: boolean;
noFunctional: boolean;
noTargeting: boolean;
// Session Attributes are persistent attributes that are tied to the current session and
// are uploaded then cleared when the session ends.
sessionAttributes: SessionAttributes;
// Local Session Attributes are persistent session attributes that are cleared when the
// session ends, but are NOT uploaded to the server when the session ends.
localSessionAttributes: LocalSessionAttributes;
currentSessionMPIDs: MPID[];
consentState: SDKConsentState | null;
sessionId: string | null;
isFirstRun: boolean;
clientId: string;
deviceId: string;
devToken: string | null;
serverSettings: ServerSettings;
dateLastEventSent: Date;
sessionStartDate: Date;
currentPosition: SDKGeoLocation | null;
isTracking: boolean;
watchPositionId: number | null;
cartProducts: SDKProduct[];
eventQueue: SDKEvent[];
currencyCode: string | null;
globalTimer: number | null;
context: Context | null;
configurationLoaded: boolean;
identityCallInFlight: boolean;
SDKConfig: Partial<SDKConfig>;
nonCurrentUserMPIDs: Record<MPID, Dictionary>;
identifyCalled: boolean;
isLoggedIn: boolean;
sideloadedKitsCount?: number;
cookieSyncDates: CookieSyncDates;
integrationAttributes: IntegrationAttributes;
requireDelay: boolean;
isLocalStorageAvailable: boolean | null;
storageName: string | null;
prodStorageName: string | null;
activeForwarders: ConfiguredKit[];
kits: Dictionary<MPForwarder>;
sideloadedKits: MPForwarder[];
configuredForwarders: MPForwarder[];
pixelConfigurations: IPixelConfiguration[];
integrationDelayTimeoutStart: number; // UNIX Timestamp
webviewBridgeEnabled?: boolean;
wrapperSDKInfo: WrapperSDKInfo;
persistenceData?: IPersistenceMinified;
getConsentState?(mpid: MPID): ConsentState | null;
setConsentState?(mpid: MPID, consentState: ConsentState): void;
_getFromPersistence?<T>(mpid: MPID, key: string): T;
_setPersistence?<T>(mpid: MPID, key: string, value: T): void;
getNoFunctional?(): boolean;
setNoFunctional?(noFunctional: boolean): void;
getNoTargeting?(): boolean;
setNoTargeting?(noTargeting: boolean): void;
getDeviceId?(): string;
setDeviceId?(deviceId: string): void;
getFirstSeenTime?(mpid: MPID): number;
setFirstSeenTime?(mpid: MPID, time?: number): void;
getLastSeenTime?(mpid: MPID): number;
setLastSeenTime?(mpid: MPID, time?: number): void;
getLocalSessionAttributes?(): LocalSessionAttributes;
setLocalSessionAttribute?(key: string, value: AttributeValue): void;
getUserAttributes?(mpid: MPID): UserAttributes;
setUserAttributes?(mpid: MPID, attributes: UserAttributes): void;
getUserIdentities?(mpid: MPID): UserIdentities;
setUserIdentities?(mpid: MPID, userIdentities: UserIdentities): void;
addMpidToSessionHistory?(mpid: MPID, previousMpid?: MPID): void;
hasInvalidIdentifyRequest?: () => boolean;
nullifySession?: () => void;
processConfig(config: SDKInitConfig): void;
syncPersistenceData?: () => void;
}
// TODO: Merge this with SDKStoreApi in sdkRuntimeModels
export default function Store(
this: IStore,
config: SDKInitConfig,
mpInstance: IMParticleWebSDKInstance,
apiKey?: string
) {
const {
createMainStorageName,
createProductStorageName,
} = mpInstance._Helpers;
const { isWebviewEnabled } = mpInstance._NativeSdkHelpers;
const defaultStore: Partial<IStore> = {
isEnabled: true,
noFunctional: false,
noTargeting: false,
sessionAttributes: {},
localSessionAttributes: {},
currentSessionMPIDs: [],
consentState: null,
sessionId: null,
isFirstRun: null,
clientId: null,
deviceId: null,
devToken: null,
serverSettings: {},
dateLastEventSent: null,
sessionStartDate: null,
currentPosition: null,
isTracking: false,
watchPositionId: null,
cartProducts: [],
eventQueue: [],
currencyCode: null,
globalTimer: null,
context: null,
configurationLoaded: false,
identityCallInFlight: false,
SDKConfig: {},
nonCurrentUserMPIDs: {},
identifyCalled: false,
isLoggedIn: false,
cookieSyncDates: {},
integrationAttributes: {},
requireDelay: true,
isLocalStorageAvailable: null,
storageName: null,
prodStorageName: null,
activeForwarders: [],
kits: {},
sideloadedKits: [],
configuredForwarders: [],
pixelConfigurations: [],
wrapperSDKInfo: {
name: 'none',
version: null,
isInfoSet: false,
},
// Placeholder for in-memory persistence model
persistenceData: {
gs: {} as IGlobalStoreV2MinifiedKeys,
} as IPersistenceMinified,
};
for (var key in defaultStore) {
this[key] = defaultStore[key];
}
this.devToken = apiKey || null;
this.integrationDelayTimeoutStart = Date.now();
// Set configuration to default settings
this.SDKConfig = createSDKConfig(config);
if (config) {
if (!config.hasOwnProperty('flags')) {
this.SDKConfig.flags = {};
}
// We process the initial config that is passed via the SDK init
// and then we will reprocess the config within the processConfig
// function when the config is updated from the server
// https://go.mparticle.com/work/SQDSDKS-6317
this.SDKConfig.flags = processFlags(config);
if (config.deviceId) {
this.deviceId = config.deviceId;
}
if (config.hasOwnProperty('isDevelopmentMode')) {
this.SDKConfig.isDevelopmentMode = returnConvertedBoolean(
config.isDevelopmentMode
);
} else {
this.SDKConfig.isDevelopmentMode = false;
}
const baseUrls: Dictionary<string> = processBaseUrls(
config,
this.SDKConfig.flags,
apiKey
);
for (const baseUrlKeys in baseUrls) {
this.SDKConfig[baseUrlKeys] = baseUrls[baseUrlKeys];
}
if (config.hasOwnProperty('logLevel')) {
this.SDKConfig.logLevel = config.logLevel;
}
this.SDKConfig.useNativeSdk = !!config.useNativeSdk;
this.SDKConfig.kits = config.kits || {};
this.SDKConfig.sideloadedKits = config.sideloadedKits || [];
if (config.hasOwnProperty('isIOS')) {
this.SDKConfig.isIOS = config.isIOS;
} else {
this.SDKConfig.isIOS =
window.mParticle && window.mParticle.isIOS
? window.mParticle.isIOS
: false;
}
if (config.hasOwnProperty('useCookieStorage')) {
this.SDKConfig.useCookieStorage = config.useCookieStorage;
} else {
this.SDKConfig.useCookieStorage = false;
}
if (config.hasOwnProperty('maxProducts')) {
this.SDKConfig.maxProducts = config.maxProducts;
} else {
this.SDKConfig.maxProducts = Constants.DefaultConfig.maxProducts;
}
if (config.hasOwnProperty('maxCookieSize')) {
this.SDKConfig.maxCookieSize = config.maxCookieSize;
} else {
this.SDKConfig.maxCookieSize =
Constants.DefaultConfig.maxCookieSize;
}
if (config.hasOwnProperty('appName')) {
this.SDKConfig.appName = config.appName;
}
if (config.hasOwnProperty('package')) {
this.SDKConfig.package = config.package;
}
if (config.hasOwnProperty('integrationDelayTimeout')) {
this.SDKConfig.integrationDelayTimeout =
config.integrationDelayTimeout;
} else {
this.SDKConfig.integrationDelayTimeout =
Constants.DefaultConfig.integrationDelayTimeout;
}
if (config.hasOwnProperty('identifyRequest')) {
this.SDKConfig.identifyRequest = config.identifyRequest;
}
if (config.hasOwnProperty('identityCallback')) {
var callback = config.identityCallback;
if (mpInstance._Helpers.Validators.isFunction(callback)) {
this.SDKConfig.identityCallback = config.identityCallback;
} else {
mpInstance.Logger.warning(
'The optional callback must be a function. You tried entering a(n) ' +
typeof callback +
' . Callback not set. Please set your callback again.'
);
}
}
if (config.hasOwnProperty('appVersion')) {
this.SDKConfig.appVersion = config.appVersion;
}
if (config.hasOwnProperty('appName')) {
this.SDKConfig.appName = config.appName;
}
if (config.hasOwnProperty('sessionTimeout')) {
this.SDKConfig.sessionTimeout = config.sessionTimeout;
}
if (config.hasOwnProperty('dataPlan')) {
this.SDKConfig.dataPlan = {
PlanVersion: null,
PlanId: null,
};
const dataPlan = config.dataPlan as DataPlanConfig;
if (dataPlan.planId) {
if (isDataPlanSlug(dataPlan.planId)) {
this.SDKConfig.dataPlan.PlanId = dataPlan.planId;
} else {
mpInstance.Logger.error(
'Your data plan id must be a string and match the data plan slug format (i.e. under_case_slug)'
);
}
}
if (dataPlan.planVersion) {
if (isNumber(dataPlan.planVersion)) {
this.SDKConfig.dataPlan.PlanVersion = dataPlan.planVersion;
} else {
mpInstance.Logger.error(
'Your data plan version must be a number'
);
}
}
} else {
this.SDKConfig.dataPlan = {};
}
if (config.hasOwnProperty('forceHttps')) {
this.SDKConfig.forceHttps = config.forceHttps;
} else {
this.SDKConfig.forceHttps = true;
}
// Some forwarders require custom flags on initialization, so allow them to be set using config object
this.SDKConfig.customFlags = config.customFlags || {};
if (config.hasOwnProperty('minWebviewBridgeVersion')) {
this.SDKConfig.minWebviewBridgeVersion =
config.minWebviewBridgeVersion;
} else {
this.SDKConfig.minWebviewBridgeVersion = 1;
}
if (config.hasOwnProperty('aliasMaxWindow')) {
this.SDKConfig.aliasMaxWindow = config.aliasMaxWindow;
} else {
this.SDKConfig.aliasMaxWindow =
Constants.DefaultConfig.aliasMaxWindow;
}
if (config.hasOwnProperty('dataPlanOptions')) {
const dataPlanOptions = config.dataPlanOptions;
if (
!dataPlanOptions.hasOwnProperty('dataPlanVersion') ||
!dataPlanOptions.hasOwnProperty('blockUserAttributes') ||
!dataPlanOptions.hasOwnProperty('blockEventAttributes') ||
!dataPlanOptions.hasOwnProperty('blockEvents') ||
!dataPlanOptions.hasOwnProperty('blockUserIdentities')
) {
mpInstance.Logger.error(
'Ensure your config.dataPlanOptions object has the following keys: a "dataPlanVersion" object, and "blockUserAttributes", "blockEventAttributes", "blockEvents", "blockUserIdentities" booleans'
);
}
}
if (config.hasOwnProperty('onCreateBatch')) {
if (typeof config.onCreateBatch === 'function') {
this.SDKConfig.onCreateBatch = config.onCreateBatch;
} else {
mpInstance.Logger.error(
'config.onCreateBatch must be a function'
);
// set to undefined because all items are set on createSDKConfig
this.SDKConfig.onCreateBatch = undefined;
}
}
}
this._getFromPersistence = <T>(mpid: MPID, key: string): T | null => {
if (!mpid) {
return null;
}
this.syncPersistenceData();
if (
this.persistenceData &&
this.persistenceData[mpid] &&
this.persistenceData[mpid][key]
) {
return this.persistenceData[mpid][key] as T;
} else {
return null;
}
};
this._setPersistence = <T>(mpid: MPID, key: string, value: T): void => {
if (!mpid) {
return;
}
this.syncPersistenceData();
if (this.persistenceData) {
if (this.persistenceData[mpid]) {
this.persistenceData[mpid][key] = value;
} else {
this.persistenceData[mpid] = {
[key]: value,
};
}
// Clear out persistence attributes that are empty
// so that we don't upload empty or undefined values
if (
isObject(this.persistenceData[mpid][key]) &&
isEmpty(this.persistenceData[mpid][key])
) {
delete this.persistenceData[mpid][key];
}
mpInstance._Persistence.savePersistence(this.persistenceData);
}
};
this.hasInvalidIdentifyRequest = (): boolean => {
const { identifyRequest } = this.SDKConfig;
return (
(isObject(identifyRequest) &&
isObject(identifyRequest.userIdentities) &&
isEmpty(identifyRequest.userIdentities)) ||
!identifyRequest
);
};
this.getConsentState = (mpid: MPID): ConsentState => {
const {
fromMinifiedJsonObject,
} = mpInstance._Consent.ConsentSerialization;
const serializedConsentState = this._getFromPersistence<
IMinifiedConsentJSONObject
>(mpid, 'con');
if (!isEmpty(serializedConsentState)) {
return fromMinifiedJsonObject(serializedConsentState);
}
return null;
};
this.setConsentState = (mpid: MPID, consentState: ConsentState) => {
const {
toMinifiedJsonObject,
} = mpInstance._Consent.ConsentSerialization;
// If ConsentState is null, we assume the intent is to clear out the consent state
if (consentState || consentState === null) {
this._setPersistence(
mpid,
'con',
toMinifiedJsonObject(consentState)
);
}
};
this.getNoFunctional = (): boolean => this.noFunctional;
this.setNoFunctional = (noFunctional: boolean): void => {
this.noFunctional = noFunctional;
};
this.getNoTargeting = (): boolean => this.noTargeting;
this.setNoTargeting = (noTargeting: boolean): void => {
this.noTargeting = noTargeting;
};
this.getDeviceId = () => this.deviceId;
this.setDeviceId = (deviceId: string) => {
this.deviceId = deviceId;
this.persistenceData.gs.das = deviceId;
mpInstance._Persistence.update();
};
this.getFirstSeenTime = (mpid: MPID) =>
this._getFromPersistence<number>(mpid, 'fst');
this.setFirstSeenTime = (mpid: MPID, _time?: number) => {
if (!mpid) {
return;
}
const time = _time || new Date().getTime();
this._setPersistence(mpid, 'fst', time);
};
this.getLastSeenTime = (mpid: MPID): number => {
if (!mpid) {
return null;
}
// https://go.mparticle.com/work/SQDSDKS-6315
const currentUser = mpInstance.Identity.getCurrentUser();
if (mpid === currentUser?.getMPID()) {
// if the mpid is the current user, its last seen time is the current time
return new Date().getTime();
}
return this._getFromPersistence<number>(mpid, 'lst');
};
this.setLastSeenTime = (mpid: MPID, _time?: number) => {
if (!mpid) {
return;
}
const time = _time || new Date().getTime();
this._setPersistence(mpid, 'lst', time);
};
this.getLocalSessionAttributes = (): LocalSessionAttributes =>
this.localSessionAttributes || {};
this.setLocalSessionAttribute = (key: string, value: AttributeValue) => {
this.localSessionAttributes[key] = value;
this.persistenceData.gs.lsa = { ...(this.persistenceData.gs.lsa || {}), [key]: value };
mpInstance._Persistence.savePersistence(this.persistenceData);
}
this.syncPersistenceData = () => {
const persistenceData = mpInstance._Persistence.getPersistence();
this.persistenceData = mpInstance._Helpers.extend(
{},
this.persistenceData,
persistenceData,
);
};
this.getUserAttributes = (mpid: MPID): UserAttributes =>
this._getFromPersistence(mpid, 'ua') || {};
this.setUserAttributes = (
mpid: MPID,
userAttributes: UserAttributes
): void => this._setPersistence(mpid, 'ua', userAttributes);
this.getUserIdentities = (mpid: MPID): UserIdentities =>
this._getFromPersistence(mpid, 'ui') || {};
this.setUserIdentities = (mpid: MPID, userIdentities: UserIdentities) => {
this._setPersistence(mpid, 'ui', userIdentities);
};
this.addMpidToSessionHistory = (mpid: MPID, previousMPID?: MPID): void => {
const indexOfMPID = this.currentSessionMPIDs.indexOf(mpid);
if (mpid && previousMPID !== mpid && indexOfMPID < 0) {
this.currentSessionMPIDs.push(mpid);
return;
}
if (indexOfMPID >= 0) {
this.currentSessionMPIDs = moveElementToEnd(
this.currentSessionMPIDs,
indexOfMPID
);
}
};
this.nullifySession = (): void => {
this.sessionId = null;
this.dateLastEventSent = null;
this.sessionAttributes = {};
this.localSessionAttributes = {};
mpInstance._Persistence.update();
};
this.processConfig = (config: SDKInitConfig) => {
const { workspaceToken, requiredWebviewBridgeName } = config;
// We should reprocess the flags and baseUrls in case they have changed when we request an updated config
// such as if the SDK is being self-hosted and the flags are different on the server config
// https://go.mparticle.com/work/SQDSDKS-6317
this.SDKConfig.flags = processFlags(config);
const baseUrls: Dictionary<string> = processBaseUrls(
config,
this.SDKConfig.flags,
apiKey
);
for (const baseUrlKeys in baseUrls) {
this.SDKConfig[baseUrlKeys] = baseUrls[baseUrlKeys];
}
if (workspaceToken) {
this.SDKConfig.workspaceToken = workspaceToken;
mpInstance._timeOnSiteTimer = new ForegroundTimer(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);
// Extract privacy flags directly from config into Store
if (config.hasOwnProperty('noFunctional')) {
this.setNoFunctional(config.noFunctional);
}
if (config.hasOwnProperty('noTargeting')) {
this.setNoTargeting(config.noTargeting);
}
this.SDKConfig.requiredWebviewBridgeName =
requiredWebviewBridgeName || workspaceToken;
this.webviewBridgeEnabled = isWebviewEnabled(
this.SDKConfig.requiredWebviewBridgeName,
this.SDKConfig.minWebviewBridgeVersion
);
this.configurationLoaded = true;
};
}
// https://go.mparticle.com/work/SQDSDKS-6317
export function processFlags(config: SDKInitConfig): IFeatureFlags {
const flags: IFeatureFlags = {};
const {
ReportBatching,
EventBatchingIntervalMillis,
OfflineStorage,
DirectUrlRouting,
CacheIdentity,
AudienceAPI,
CaptureIntegrationSpecificIds,
CaptureIntegrationSpecificIdsV2,
AstBackgroundEvents
} = Constants.FeatureFlags;
if (!config.flags) {
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
flags[EventBatchingIntervalMillis] =
parseNumber(config.flags[EventBatchingIntervalMillis]) ||
Constants.DefaultConfig.uploadInterval;
flags[OfflineStorage] = config.flags[OfflineStorage] || '0';
flags[DirectUrlRouting] = config.flags[DirectUrlRouting] === 'True';
flags[CacheIdentity] = config.flags[CacheIdentity] === 'True';
flags[AudienceAPI] = config.flags[AudienceAPI] === 'True';
flags[CaptureIntegrationSpecificIds] = config.flags[CaptureIntegrationSpecificIds] === 'True';
flags[CaptureIntegrationSpecificIdsV2] = (config.flags[CaptureIntegrationSpecificIdsV2] || 'none');
flags[AstBackgroundEvents] = config.flags[AstBackgroundEvents] === 'True';
return flags;
}
export function processBaseUrls(
config: SDKInitConfig,
flags: IFeatureFlags,
apiKey?: string
): Dictionary<string> {
// 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<string>;
// 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<string> {
const defaultBaseUrls: Dictionary<string> = Constants.DefaultBaseUrls;
const CNAMEUrlPaths: Dictionary<string> = Constants.CNAMEUrlPaths;
// newBaseUrls are default if the customer is not using a CNAME
// If a customer passes either config.domain or config.v3SecureServiceUrl,
// config.identityUrl, etc, the customer is using a CNAME.
// config.domain will take priority if a customer passes both.
const newBaseUrls: Dictionary<string> = {};
// If config.domain exists, the customer is using a CNAME. We append the url paths to the provided domain.
// This flag is set on the Rokt/MP snippet (starting at version 2.6), meaning config.domain will alwys be empty
// if a customer is using a snippet prior to 2.6.
if (!isEmpty(config.domain)) {
for (let pathKey in CNAMEUrlPaths) {
newBaseUrls[pathKey] = `${config.domain}${CNAMEUrlPaths[pathKey]}`;
}
return newBaseUrls;
}
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<string> = {};
// 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<string> = 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;
}