Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/mp-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -80,6 +81,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
_IntegrationCapture: IntegrationCapture;
_NativeSdkHelpers: INativeSdkHelpers;
_Persistence: IPersistence;
_RoktManager: RoktManager;
_SessionManager: ISessionManager;
_ServerModel: IServerModel;
_Store: IStore;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/mparticle-instance-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) {
return client;
}
};

this.Rokt = self.getInstance()._RoktManager;

this.getDeviceId = function() {
return self.getInstance().getDeviceId();
};
Expand Down
85 changes: 85 additions & 0 deletions src/roktManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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 ISelectPlacementsOptions {
attributes: IRoktPartnerAttributes;
identifier?: string;
}

export interface ISelection {
placementId?: string;
status?: string;
error?: string;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see https://docs.rokt.com/developers/integration-guides/web/library/integration-launcher#select-placements which shows that yes, it does return Promise<selection> but if you click on selection in this page, it brings you here which doesn't have much
https://docs.rokt.com/developers/integration-guides/web/library/selection


export interface IRoktLauncher {
selectPlacements: (options: ISelectPlacementsOptions) => Promise<ISelection>;
}

export interface IRoktMessage {
methodName: string;
payload: any;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

group with other rokt interfaces


export default class RoktManager {
private launcher: IRoktLauncher | null = null;
private messageQueue: IRoktMessage[] = [];

constructor() {
this.launcher = null;
}

public attachLauncher(launcher: IRoktLauncher): Promise<void> {
return new Promise<void>((resolve) => {
if (!launcher) {
this.queueMessage({
methodName: 'attachLauncher',
payload: launcher,
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be removed. the expectation is there is no launcher and we are setting.

} else {
this.setLauncher(launcher);
this.processMessageQueue();
}
resolve();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for this to be a promise

});
}

public selectPlacements(options: ISelectPlacementsOptions): Promise<ISelection> {
if (!this.launcher) {
this.queueMessage({
methodName: 'selectPlacements',
payload: options,
});
return Promise.resolve({});
}

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;
}
}
2 changes: 2 additions & 0 deletions src/sdkRuntimeModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>;
Expand Down Expand Up @@ -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<ISessionManager, 'getSession'>;
Store: IStore;
Expand Down
120 changes: 120 additions & 0 deletions test/jest/roktManager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import RoktManager, { IRoktLauncher, ISelectPlacementsOptions } 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 queue the launcher method if no launcher is attached', () => {
roktManager.attachLauncher(null);
expect(roktManager['launcher']).toBeNull();
expect(roktManager['messageQueue'].length).toBe(1);
expect(roktManager['messageQueue'][0].methodName).toBe('attachLauncher');
expect(roktManager['messageQueue'][0].payload).toBe(null);
});

it('should process the message queue if a launcher is attached', () => {
const launcher = jest.fn() as unknown as IRoktLauncher;

roktManager.attachLauncher(null);
expect(roktManager['launcher']).toBeNull();
expect(roktManager['messageQueue'].length).toBe(1);
expect(roktManager['messageQueue'][0].methodName).toBe('attachLauncher');
expect(roktManager['messageQueue'][0].payload).toBe(null);

roktManager.attachLauncher(launcher);
expect(roktManager['launcher']).not.toBeNull();
expect(roktManager['messageQueue'].length).toBe(0);
});
});

describe('#selectPlacements', () => {
it('should call selectPlacements', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general for all these tests, i think it gets confusing when you are saying selectPlacements gets called. we should be more explicit that one selectPlacements gets mapped to anotheri selectPlacements call think being explicit is good here. should call launcher.selectPlacements when roktManager.selectPlacements is called

Suggested change
it('should call selectPlacements', () => {
it('should map a IRoktLauncher selectPlacement to a launcher selectPlacements call', () => {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "map an IRoktLauncher selectPlacement" is too much of an implementation detail for this specific tests, but I agree that it should more explicit to referenc the launcher. I'll revise.

const launcher = {
selectPlacements: jest.fn()
} as unknown as IRoktLauncher;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a reason to make it unknown

Suggested change
const launcher = {
selectPlacements: jest.fn()
} as unknown as IRoktLauncher;
const launcher: IRoktLauncher = {
selectPlacements: jest.fn()
}


roktManager.attachLauncher(launcher);
const options = {
attributes: {}
} as ISelectPlacementsOptions;

roktManager.selectPlacements(options);
expect(launcher.selectPlacements).toHaveBeenCalledWith(options);
});

it('should call selectPlacements with any passed in attributes', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it('should call selectPlacements with any passed in attributes', () => {
it('should call IRoktLauncher selectPlacements with any passed in attributes to roktManager.selectPlacements', () => {

const launcher = {
selectPlacements: jest.fn()
} as unknown as IRoktLauncher;

roktManager.attachLauncher(launcher);

const options: ISelectPlacementsOptions = {
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 ISelectPlacementsOptions;

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 = {
selectPlacements: jest.fn()
} as unknown as IRoktLauncher;

const options = {
attributes: {}
} as ISelectPlacementsOptions;

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);
});
});
});
1 change: 1 addition & 0 deletions test/src/tests-mparticle-instance-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ describe('mParticle instance manager', () => {
'isInitialized',
'getEnvironment',
'upload',
'Rokt',
]);
});

Expand Down
Loading