Skip to content

Commit 89ee1b3

Browse files
Merge branch 'development' into feat/SQDSDKS-6035-kit-interfaces
2 parents aa37ca0 + 04d1efc commit 89ee1b3

10 files changed

+587
-773
lines changed

src/mp-instance.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { IECommerce } from './ecommerce.interfaces';
4949
import { INativeSdkHelpers } from './nativeSdkHelpers.interfaces';
5050
import { IPersistence } from './persistence.interfaces';
5151
import ForegroundTimer from './foregroundTimeTracker';
52+
import RoktManager from './roktManager';
5253

5354
export interface IErrorLogMessage {
5455
message?: string;
@@ -80,6 +81,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
8081
_IntegrationCapture: IntegrationCapture;
8182
_NativeSdkHelpers: INativeSdkHelpers;
8283
_Persistence: IPersistence;
84+
_RoktManager: RoktManager;
8385
_SessionManager: ISessionManager;
8486
_ServerModel: IServerModel;
8587
_Store: IStore;
@@ -126,13 +128,14 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
126128
forwarderConstructors: [],
127129
};
128130
this._IntegrationCapture = new IntegrationCapture();
131+
this._RoktManager = new RoktManager();
129132

130133
// required for forwarders once they reference the mparticle instance
131134
this.IdentityType = IdentityType;
132-
this.EventType = EventType as unknown as valueof<typeof EventType>;
133-
this.CommerceEventType = CommerceEventType as unknown as valueof<typeof CommerceEventType>;
134-
this.PromotionType = PromotionActionType as unknown as valueof<typeof PromotionActionType>;
135-
this.ProductActionType = ProductActionType as unknown as valueof<typeof ProductActionType>;
135+
this.EventType = EventType;
136+
this.CommerceEventType = CommerceEventType;
137+
this.PromotionType = PromotionActionType;
138+
this.ProductActionType = ProductActionType;
136139

137140

138141
this._Identity = new Identity(this);

src/mparticle-instance-manager.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import Polyfill from './polyfill';
2-
import Types, { CommerceEventType, EventType, ProductActionType, PromotionActionType } from './types';
2+
import { CommerceEventType, EventType, IdentityType, ProductActionType, PromotionActionType } from './types';
33
import Constants from './constants';
44
import mParticleInstance, { IMParticleWebSDKInstance } from './mp-instance.js';
55
import _BatchValidator from './mockBatchCreator';
66
import MPSideloadedKit from './sideloadedKit';
77
import { IMParticleInstanceManager } from './sdkRuntimeModels';
88
import { IStore } from './store';
99
import { Dictionary } from '@mparticle/web-sdk';
10-
import { valueof } from './utils';
1110

1211
if (!Array.prototype.forEach) {
1312
Array.prototype.forEach = Polyfill.forEach;
@@ -32,11 +31,11 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) {
3231
// Only leaving this here in case any clients are trying to access mParticle.Store, to prevent from throwing
3332
this.Store = {} as IStore;
3433
this._instances = {} as Dictionary<IMParticleWebSDKInstance>;
35-
this.IdentityType = Types.IdentityType;
36-
this.EventType = EventType as unknown as valueof<typeof EventType>;
37-
this.CommerceEventType = CommerceEventType as unknown as valueof<typeof CommerceEventType>;
38-
this.PromotionType = PromotionActionType as unknown as valueof<typeof PromotionActionType>;
39-
this.ProductActionType = ProductActionType as unknown as valueof<typeof ProductActionType>;
34+
this.IdentityType = IdentityType;
35+
this.EventType = EventType;
36+
this.CommerceEventType = CommerceEventType;
37+
this.PromotionType = PromotionActionType;
38+
this.ProductActionType = ProductActionType;
4039

4140
this.MPSideloadedKit = MPSideloadedKit;
4241

@@ -104,6 +103,9 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) {
104103
return client;
105104
}
106105
};
106+
107+
this.Rokt = self.getInstance()._RoktManager;
108+
107109
this.getDeviceId = function() {
108110
return self.getInstance().getDeviceId();
109111
};

src/roktManager.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// https://docs.rokt.com/developers/integration-guides/web/library/attributes
2+
export interface IRoktPartnerAttributes {
3+
[key: string]: string | number | boolean | undefined | null;
4+
}
5+
6+
// https://docs.rokt.com/developers/integration-guides/web/library/select-placements-options
7+
export interface IRoktSelectPlacementsOptions {
8+
attributes: IRoktPartnerAttributes;
9+
identifier?: string;
10+
}
11+
12+
interface IRoktPlacement {}
13+
14+
export interface IRoktSelection {
15+
close: () => void;
16+
getPlacements: () => Promise<IRoktPlacement[]>;
17+
}
18+
19+
export interface IRoktLauncher {
20+
selectPlacements: (options: IRoktSelectPlacementsOptions) => Promise<IRoktSelection>;
21+
}
22+
23+
export interface IRoktMessage {
24+
methodName: string;
25+
payload: any;
26+
}
27+
28+
export default class RoktManager {
29+
public launcher: IRoktLauncher | null = null;
30+
private messageQueue: IRoktMessage[] = [];
31+
32+
constructor() {
33+
this.launcher = null;
34+
}
35+
36+
public attachLauncher(launcher: IRoktLauncher): void {
37+
this.setLauncher(launcher);
38+
this.processMessageQueue();
39+
}
40+
41+
public selectPlacements(options: IRoktSelectPlacementsOptions): Promise<IRoktSelection> {
42+
if (!this.launcher) {
43+
this.queueMessage({
44+
methodName: 'selectPlacements',
45+
payload: options,
46+
});
47+
return Promise.resolve({} as IRoktSelection);
48+
}
49+
50+
try {
51+
return this.launcher.selectPlacements(options);
52+
} catch (error) {
53+
return Promise.reject(error instanceof Error ? error : new Error('Unknown error occurred'));
54+
}
55+
}
56+
57+
private processMessageQueue(): void {
58+
if (this.messageQueue.length > 0) {
59+
this.messageQueue.forEach(async (message) => {
60+
if (this.launcher && message.methodName in this.launcher) {
61+
await (this.launcher[message.methodName] as Function)(message.payload);
62+
}
63+
});
64+
this.messageQueue = [];
65+
}
66+
}
67+
68+
69+
private queueMessage(message: IRoktMessage): void {
70+
this.messageQueue.push(message);
71+
}
72+
73+
private setLauncher(launcher: IRoktLauncher): void {
74+
this.launcher = launcher;
75+
}
76+
}

src/sdkRuntimeModels.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import _BatchValidator from './mockBatchCreator';
3939
import { SDKECommerceAPI } from './ecommerce.interfaces';
4040
import { IErrorLogMessage, IMParticleWebSDKInstance, IntegrationDelays } from './mp-instance';
4141
import Constants from './constants';
42+
import RoktManager from './roktManager';
4243

4344
// TODO: Resolve this with version in @mparticle/web-sdk
4445
export type SDKEventCustomFlags = Dictionary<any>;
@@ -205,13 +206,15 @@ export interface MParticleWebSDK {
205206
logError(error: IErrorLogMessage, attrs?: SDKEventAttrs): void;
206207
logLink(selector: string, eventName: string, eventType: valueof<typeof EventType>, eventInfo: SDKEventAttrs): void;
207208
logForm(selector: string, eventName: string, eventType: valueof<typeof EventType>, eventInfo: SDKEventAttrs): void;
208-
logPageView(eventName: string, attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions): void;
209+
logPageView(eventName?: string, attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions): void;
209210
setOptOut(isOptingOut: boolean): void;
210211
eCommerce: SDKECommerceAPI;
211212
isInitialized(): boolean;
212-
ProductActionType: valueof<typeof ProductActionType>;
213213
ready(f: Function): void;
214-
reset(instance: IMParticleWebSDKInstance): void;
214+
215+
// https://go.mparticle.com/work/SQDSDKS-7072
216+
reset(instance?: IMParticleWebSDKInstance): void;
217+
215218
setAppName(name: string): void;
216219
setAppVersion(version: string): void;
217220
setOptOut(isOptingOut: boolean): void;
@@ -239,6 +242,7 @@ export interface IMParticleInstanceManager extends MParticleWebSDK {
239242
config: SDKInitConfig;
240243
isIOS?: boolean;
241244
MPSideloadedKit: typeof MPSideloadedKit;
245+
Rokt: RoktManager;
242246
// https://go.mparticle.com/work/SQDSDKS-7060
243247
sessionManager: Pick<ISessionManager, 'getSession'>;
244248
Store: IStore;

test/jest/roktManager.spec.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import RoktManager, { IRoktLauncher, IRoktSelectPlacementsOptions } from "../../src/roktManager";
2+
3+
describe('RoktManager', () => {
4+
let roktManager: RoktManager;
5+
6+
beforeEach(() => {
7+
roktManager = new RoktManager();
8+
});
9+
10+
describe('constructor', () => {
11+
it('should be initialized', () => {
12+
expect(roktManager).toBeDefined();
13+
});
14+
15+
it('should have a null launcher', () => {
16+
expect(roktManager['launcher']).toBeNull();
17+
});
18+
});
19+
20+
describe('#attachLauncher', () => {
21+
it('should attach a launcher', () => {
22+
const launcher = {} as IRoktLauncher;
23+
roktManager.attachLauncher(launcher);
24+
expect(roktManager['launcher']).not.toBeNull();
25+
});
26+
27+
it('should process the message queue if a launcher is attached', () => {
28+
const launcher: IRoktLauncher = {
29+
selectPlacements: jest.fn()
30+
};
31+
32+
roktManager.selectPlacements({} as IRoktSelectPlacementsOptions);
33+
roktManager.selectPlacements({} as IRoktSelectPlacementsOptions);
34+
roktManager.selectPlacements({} as IRoktSelectPlacementsOptions);
35+
36+
expect(roktManager['messageQueue'].length).toBe(3);
37+
38+
roktManager.attachLauncher(launcher);
39+
expect(roktManager['launcher']).not.toBeNull();
40+
expect(roktManager['messageQueue'].length).toBe(0);
41+
expect(launcher.selectPlacements).toHaveBeenCalledTimes(3);
42+
});
43+
});
44+
45+
describe('#selectPlacements', () => {
46+
it('should call launcher.selectPlacements with empty attributes', () => {
47+
const launcher: IRoktLauncher = {
48+
selectPlacements: jest.fn()
49+
};
50+
51+
roktManager.attachLauncher(launcher);
52+
const options = {
53+
attributes: {}
54+
} as IRoktSelectPlacementsOptions;
55+
56+
roktManager.selectPlacements(options);
57+
expect(launcher.selectPlacements).toHaveBeenCalledWith(options);
58+
});
59+
60+
it('should call launcher.selectPlacements with passed in attributes', () => {
61+
const launcher: IRoktLauncher = {
62+
selectPlacements: jest.fn()
63+
};
64+
65+
roktManager.attachLauncher(launcher);
66+
67+
const options: IRoktSelectPlacementsOptions = {
68+
attributes: {
69+
age: 25,
70+
score: 100.5,
71+
isSubscribed: true,
72+
isActive: false,
73+
interests: 'sports,music,books'
74+
}
75+
};
76+
77+
roktManager.selectPlacements(options);
78+
expect(launcher.selectPlacements).toHaveBeenCalledWith(options);
79+
});
80+
81+
it('should queue the selectPlacements method if no launcher is attached', () => {
82+
const options = {
83+
attributes: {}
84+
} as IRoktSelectPlacementsOptions;
85+
86+
roktManager.selectPlacements(options);
87+
88+
expect(roktManager['launcher']).toBeNull();
89+
expect(roktManager['messageQueue'].length).toBe(1);
90+
expect(roktManager['messageQueue'][0].methodName).toBe('selectPlacements');
91+
expect(roktManager['messageQueue'][0].payload).toBe(options);
92+
});
93+
94+
it('should process queued selectPlacements calls once the launcher is attached', () => {
95+
const launcher: IRoktLauncher = {
96+
selectPlacements: jest.fn()
97+
};
98+
99+
const options = {
100+
attributes: {}
101+
} as IRoktSelectPlacementsOptions;
102+
103+
roktManager.selectPlacements(options);
104+
expect(roktManager['launcher']).toBeNull();
105+
expect(roktManager['messageQueue'].length).toBe(1);
106+
expect(roktManager['messageQueue'][0].methodName).toBe('selectPlacements');
107+
expect(roktManager['messageQueue'][0].payload).toBe(options);
108+
109+
roktManager.attachLauncher(launcher);
110+
expect(roktManager['launcher']).not.toBeNull();
111+
expect(roktManager['messageQueue'].length).toBe(0);
112+
expect(launcher.selectPlacements).toHaveBeenCalledWith(options);
113+
});
114+
});
115+
});

test/src/tests-batchUploader_3.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,27 +147,27 @@ describe('batch uploader', () => {
147147
});
148148
});
149149

150-
it('should return pending uploads if a 500 is returned', function(done) {
150+
it('should return pending uploads if a 500 is returned', async function() {
151151
window.mParticle._resetForTests(MPConfig);
152152

153153
fetchMock.post(urls.events, 500);
154154

155155
window.mParticle.init(apiKey, window.mParticle.config);
156-
waitForCondition(hasIdentifyReturned)
157-
.then(() => {
156+
await waitForCondition(hasIdentifyReturned);
157+
158158
window.mParticle.logEvent('Test Event');
159159

160160
let pendingEvents = window.mParticle.getInstance()._APIClient.uploader.eventsQueuedForProcessing;
161161

162-
pendingEvents.length.should.equal(3)
162+
pendingEvents.length.should.equal(3);
163163
pendingEvents[0].EventName.should.equal(1);
164164
pendingEvents[1].EventName.should.equal(10);
165165
pendingEvents[2].EventName.should.equal('Test Event');
166166

167167
fetchMock.post(urls.events, 200);
168168

169169
// First fetch call is an identify call
170-
(fetchMock.lastCall()[0].endsWith('identify')).should.equal(true)
170+
(fetchMock.lastCall()[0].endsWith('identify')).should.equal(true);
171171
window.mParticle.upload();
172172

173173
let nowPendingEvents = window.mParticle.getInstance()._APIClient.uploader.eventsQueuedForProcessing;
@@ -178,9 +178,6 @@ describe('batch uploader', () => {
178178
batch.events[1].event_type.should.equal('application_state_transition');
179179
batch.events[2].event_type.should.equal('custom_event');
180180
batch.events[2].data.event_name.should.equal('Test Event');
181-
182-
done();
183-
})
184181
});
185182

186183
it('should send source_message_id with events to v3 endpoint', function(done) {

0 commit comments

Comments
 (0)