diff --git a/src/mp-instance.ts b/src/mp-instance.ts index a7688542c..ff74a52d9 100644 --- a/src/mp-instance.ts +++ b/src/mp-instance.ts @@ -49,6 +49,7 @@ import { IECommerce } from './ecommerce.interfaces'; import { INativeSdkHelpers } from './nativeSdkHelpers.interfaces'; import { IPersistence } from './persistence.interfaces'; import ForegroundTimer from './foregroundTimeTracker'; +import RoktManager from './roktManager'; export interface IErrorLogMessage { message?: string; @@ -80,6 +81,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK { _IntegrationCapture: IntegrationCapture; _NativeSdkHelpers: INativeSdkHelpers; _Persistence: IPersistence; + _RoktManager: RoktManager; _SessionManager: ISessionManager; _ServerModel: IServerModel; _Store: IStore; @@ -126,6 +128,7 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan forwarderConstructors: [], }; this._IntegrationCapture = new IntegrationCapture(); + this._RoktManager = new RoktManager(); // required for forwarders once they reference the mparticle instance this.IdentityType = IdentityType; diff --git a/src/mparticle-instance-manager.ts b/src/mparticle-instance-manager.ts index 777baa1eb..d69007ec2 100644 --- a/src/mparticle-instance-manager.ts +++ b/src/mparticle-instance-manager.ts @@ -103,6 +103,9 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) { return client; } }; + + this.Rokt = self.getInstance()._RoktManager; + this.getDeviceId = function() { return self.getInstance().getDeviceId(); }; diff --git a/src/roktManager.ts b/src/roktManager.ts new file mode 100644 index 000000000..a00db28a8 --- /dev/null +++ b/src/roktManager.ts @@ -0,0 +1,76 @@ +// https://docs.rokt.com/developers/integration-guides/web/library/attributes +export interface IRoktPartnerAttributes { + [key: string]: string | number | boolean | undefined | null; +} + +// https://docs.rokt.com/developers/integration-guides/web/library/select-placements-options +export interface IRoktSelectPlacementsOptions { + attributes: IRoktPartnerAttributes; + identifier?: string; +} + +interface IRoktPlacement {} + +export interface IRoktSelection { + close: () => void; + getPlacements: () => Promise; +} + +export interface IRoktLauncher { + selectPlacements: (options: IRoktSelectPlacementsOptions) => Promise; +} + +export interface IRoktMessage { + methodName: string; + payload: any; +} + +export default class RoktManager { + public launcher: IRoktLauncher | null = null; + private messageQueue: IRoktMessage[] = []; + + constructor() { + this.launcher = null; + } + + public attachLauncher(launcher: IRoktLauncher): void { + this.setLauncher(launcher); + this.processMessageQueue(); + } + + public selectPlacements(options: IRoktSelectPlacementsOptions): Promise { + if (!this.launcher) { + this.queueMessage({ + methodName: 'selectPlacements', + payload: options, + }); + return Promise.resolve({} as IRoktSelection); + } + + try { + return this.launcher.selectPlacements(options); + } catch (error) { + return Promise.reject(error instanceof Error ? error : new Error('Unknown error occurred')); + } + } + + private processMessageQueue(): void { + if (this.messageQueue.length > 0) { + this.messageQueue.forEach(async (message) => { + if (this.launcher && message.methodName in this.launcher) { + await (this.launcher[message.methodName] as Function)(message.payload); + } + }); + this.messageQueue = []; + } + } + + + private queueMessage(message: IRoktMessage): void { + this.messageQueue.push(message); + } + + private setLauncher(launcher: IRoktLauncher): void { + this.launcher = launcher; + } +} diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index 8145bdc27..822364ed1 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -39,6 +39,7 @@ import _BatchValidator from './mockBatchCreator'; import { SDKECommerceAPI } from './ecommerce.interfaces'; import { IErrorLogMessage, IMParticleWebSDKInstance, IntegrationDelays } from './mp-instance'; import Constants from './constants'; +import RoktManager from './roktManager'; // TODO: Resolve this with version in @mparticle/web-sdk export type SDKEventCustomFlags = Dictionary; @@ -242,6 +243,7 @@ export interface IMParticleInstanceManager extends MParticleWebSDK { config: SDKInitConfig; isIOS?: boolean; MPSideloadedKit: typeof MPSideloadedKit; + Rokt: RoktManager; // https://go.mparticle.com/work/SQDSDKS-7060 sessionManager: Pick; Store: IStore; diff --git a/test/jest/roktManager.spec.ts b/test/jest/roktManager.spec.ts new file mode 100644 index 000000000..24bbb6d5d --- /dev/null +++ b/test/jest/roktManager.spec.ts @@ -0,0 +1,115 @@ +import RoktManager, { IRoktLauncher, IRoktSelectPlacementsOptions } from "../../src/roktManager"; + +describe('RoktManager', () => { + let roktManager: RoktManager; + + beforeEach(() => { + roktManager = new RoktManager(); + }); + + describe('constructor', () => { + it('should be initialized', () => { + expect(roktManager).toBeDefined(); + }); + + it('should have a null launcher', () => { + expect(roktManager['launcher']).toBeNull(); + }); + }); + + describe('#attachLauncher', () => { + it('should attach a launcher', () => { + const launcher = {} as IRoktLauncher; + roktManager.attachLauncher(launcher); + expect(roktManager['launcher']).not.toBeNull(); + }); + + it('should process the message queue if a launcher is attached', () => { + const launcher: IRoktLauncher = { + selectPlacements: jest.fn() + }; + + roktManager.selectPlacements({} as IRoktSelectPlacementsOptions); + roktManager.selectPlacements({} as IRoktSelectPlacementsOptions); + roktManager.selectPlacements({} as IRoktSelectPlacementsOptions); + + expect(roktManager['messageQueue'].length).toBe(3); + + roktManager.attachLauncher(launcher); + expect(roktManager['launcher']).not.toBeNull(); + expect(roktManager['messageQueue'].length).toBe(0); + expect(launcher.selectPlacements).toHaveBeenCalledTimes(3); + }); + }); + + describe('#selectPlacements', () => { + it('should call launcher.selectPlacements with empty attributes', () => { + const launcher: IRoktLauncher = { + selectPlacements: jest.fn() + }; + + roktManager.attachLauncher(launcher); + const options = { + attributes: {} + } as IRoktSelectPlacementsOptions; + + roktManager.selectPlacements(options); + expect(launcher.selectPlacements).toHaveBeenCalledWith(options); + }); + + it('should call launcher.selectPlacements with passed in attributes', () => { + const launcher: IRoktLauncher = { + selectPlacements: jest.fn() + }; + + roktManager.attachLauncher(launcher); + + const options: IRoktSelectPlacementsOptions = { + attributes: { + age: 25, + score: 100.5, + isSubscribed: true, + isActive: false, + interests: 'sports,music,books' + } + }; + + roktManager.selectPlacements(options); + expect(launcher.selectPlacements).toHaveBeenCalledWith(options); + }); + + it('should queue the selectPlacements method if no launcher is attached', () => { + const options = { + attributes: {} + } as IRoktSelectPlacementsOptions; + + roktManager.selectPlacements(options); + + expect(roktManager['launcher']).toBeNull(); + expect(roktManager['messageQueue'].length).toBe(1); + expect(roktManager['messageQueue'][0].methodName).toBe('selectPlacements'); + expect(roktManager['messageQueue'][0].payload).toBe(options); + }); + + it('should process queued selectPlacements calls once the launcher is attached', () => { + const launcher: IRoktLauncher = { + selectPlacements: jest.fn() + }; + + const options = { + attributes: {} + } as IRoktSelectPlacementsOptions; + + roktManager.selectPlacements(options); + expect(roktManager['launcher']).toBeNull(); + expect(roktManager['messageQueue'].length).toBe(1); + expect(roktManager['messageQueue'][0].methodName).toBe('selectPlacements'); + expect(roktManager['messageQueue'][0].payload).toBe(options); + + roktManager.attachLauncher(launcher); + expect(roktManager['launcher']).not.toBeNull(); + expect(roktManager['messageQueue'].length).toBe(0); + expect(launcher.selectPlacements).toHaveBeenCalledWith(options); + }); + }); +}); \ No newline at end of file diff --git a/test/src/tests-mparticle-instance-manager.ts b/test/src/tests-mparticle-instance-manager.ts index e56dd2e3c..767199596 100644 --- a/test/src/tests-mparticle-instance-manager.ts +++ b/test/src/tests-mparticle-instance-manager.ts @@ -213,6 +213,7 @@ describe('mParticle instance manager', () => { 'isInitialized', 'getEnvironment', 'upload', + 'Rokt', ]); });