Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions src/identityApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ export default function IdentityAPIClient(
}

mpInstance._Store.identityCallInFlight = false;
mpInstance._Store.identityCallFailed = false;

verbose(message);
parseIdentityResponse(
Expand All @@ -301,9 +302,14 @@ export default function IdentityAPIClient(
);
} catch (err) {
mpInstance._Store.identityCallInFlight = false;

// Marking identity call failed - ready() will fire callbacks if
// RoktManager is ready, allowing Rokt methods to execute even if identity fails
mpInstance._Store.identityCallFailed = true;

const errorMessage = (err as Error).message || err.toString();

debugger;
error('Error sending identity request to servers' + ' - ' + errorMessage);
invokeCallback(
callback,
Expand Down
41 changes: 36 additions & 5 deletions src/mp-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
integrationDelays: {},
forwarderConstructors: [],
};
this._RoktManager = new RoktManager();

// TODO: Refactor into a preinit class or ready queue manager
this._RoktManager = new RoktManager(this._preInit);

// required for forwarders once they reference the mparticle instance
this.IdentityType = IdentityType;
Expand All @@ -150,12 +152,17 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan

if (typeof window !== 'undefined') {
if (window.mParticle && window.mParticle.config) {
// TODO: Set up test cases for this as well
if (window.mParticle.config.hasOwnProperty('rq')) {
console.warn('ready queue from config', window.mParticle.config.rq);
console.warn('typeof ready queue from config', typeof window.mParticle.config.rq);
// debugger;
this._preInit.readyQueue = window.mParticle.config.rq;
}
}
}
this.init = function(apiKey, config) {
console.warn('local dev');
if (!config) {
console.warn(
'You did not pass a config object to init(). mParticle will not initialize properly'
Expand Down Expand Up @@ -251,7 +258,18 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
* @param {Function} function A function to be called after mParticle is initialized
*/
this.ready = function(f) {
if (self.isInitialized() && typeof f === 'function') {
// debugger;
console.warn('ready', f);
console.warn('typeof f', typeof f);
console.warn('isInitialized', self.isInitialized());
console.warn('identityCallFailed', self._Store?.identityCallFailed);
const shouldExecute = typeof f === 'function' && (
self._Store?.isInitialized ||
(self._Store?.identityCallFailed && self._RoktManager.isReady())
);

console.warn('shouldExecute', shouldExecute);
if (shouldExecute) {
f();
} else {
self._preInit.readyQueue.push(f);
Expand Down Expand Up @@ -1460,10 +1478,15 @@ function completeSDKInitialization(apiKey, config, mpInstance) {

// We will continue to clear out the ready queue as part of the initial init flow
// if an identify request is unnecessary, such as if there is an existing session
console.warn('Checking if store can be initialized', mpInstance._Store.mpid, mpInstance._Store.identifyCalled);

console.warn('ready queue on init complete', mpInstance._preInit.readyQueue);

if (
(mpInstance._Store.mpid && !mpInstance._Store.identifyCalled) ||
mpInstance._Store.webviewBridgeEnabled
) {
// TODO: We may need to extract this out of the identified check above
mpInstance._Store.isInitialized = true;

mpInstance._preInit.readyQueue = processReadyQueue(
Expand Down Expand Up @@ -1612,9 +1635,17 @@ function processIdentityCallback(
}

function queueIfNotInitialized(func, self) {
if (!self.isInitialized()) {
self.ready(function() {
func();
// Core SDK methods must wait for Store initialization
// We queue directly to readyQueue (not via ready()) because:
// - ready() fires when either Store OR RoktManager is ready
// - Core SDK methods specifically need Store to be initialized
if (!self._Store?.isInitialized) {
self._preInit.readyQueue.push(function() {
// Double-check Store before executing - prevents execution
// when only RoktManager is ready
if (self._Store?.isInitialized) {
func();
}
});
return true;
}
Expand Down
1 change: 1 addition & 0 deletions src/pre-init-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const processReadyQueue = (readyQueue): Function[] => {
if (isFunction(readyQueueItem)) {
readyQueueItem();
} else if (Array.isArray(readyQueueItem)) {
// TODO:L Can we add test coverage for this?
processPreloadedItem(readyQueueItem);
}
});
Expand Down
18 changes: 17 additions & 1 deletion src/roktManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SDKLoggerApi } from "./sdkRuntimeModels";
import { IStore, LocalSessionAttributes } from "./store";
import { UserIdentities } from "@mparticle/web-sdk";
import { IdentityType } from "./types";
import { IPreInit, processReadyQueue } from "./pre-init-utils";

// https://docs.rokt.com/developers/integration-guides/web/library/attributes
export interface IRoktPartnerAttributes {
Expand Down Expand Up @@ -100,6 +101,12 @@ export default class RoktManager {
private logger: SDKLoggerApi;
private domain?: string;
private mappedEmailShaIdentityType?: string | null;

// TODO: Refactor into a preinit class or ready queue manager
constructor(private _preInit: IPreInit) {
this._preInit = _preInit;
}

/**
* Initializes the RoktManager with configuration settings and user data.
*
Expand Down Expand Up @@ -155,8 +162,15 @@ export default class RoktManager {
}

public attachKit(kit: IRoktKit): void {
console.warn('RoktManager: attaching kit');
this.kit = kit;
this.processMessageQueue();

// Process ready queue only if identity call failed
// This ensures queued SDK methods execute even when identity initialization fails
if (this.store.identityCallFailed) {
this._preInit.readyQueue = processReadyQueue(this._preInit.readyQueue);
}
}

/**
Expand All @@ -180,6 +194,7 @@ export default class RoktManager {
}

try {
console.warn('RoktManager: selectPlacements');
const { attributes } = options;
const sandboxValue = attributes?.sandbox || null;
const mappedAttributes = this.mapPlacementAttributes(attributes, this.placementAttributesMapping);
Expand Down Expand Up @@ -357,7 +372,7 @@ export default class RoktManager {
this.store.setLocalSessionAttribute(key, value);
}

private isReady(): boolean {
public isReady(): boolean {
// The Rokt Manager is ready when a kit is attached and has a launcher
return Boolean(this.kit && this.kit.launcher);
}
Expand Down Expand Up @@ -430,6 +445,7 @@ export default class RoktManager {
}

private deferredCall<T>(methodName: string, payload: any): Promise<T> {
console.warn('deferredCall', methodName, payload);
return new Promise<T>((resolve, reject) => {
const messageId = `${methodName}_${generateUniqueId()}`;

Expand Down
2 changes: 2 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface IStore {
context: Context | null;
configurationLoaded: boolean;
identityCallInFlight: boolean;
identityCallFailed: boolean;
SDKConfig: Partial<SDKConfig>;
nonCurrentUserMPIDs: Record<MPID, Dictionary>;
identifyCalled: boolean;
Expand Down Expand Up @@ -266,6 +267,7 @@ export default function Store(
context: null,
configurationLoaded: false,
identityCallInFlight: false,
identityCallFailed: false,
SDKConfig: {},
nonCurrentUserMPIDs: {},
identifyCalled: false,
Expand Down
82 changes: 79 additions & 3 deletions test/jest/roktManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IKitConfigs } from "../../src/configAPIClient";
import { IMParticleUser } from "../../src/identity-user-interfaces";
import { SDKIdentityApi } from "../../src/identity.interfaces";
import { IMParticleWebSDKInstance } from "../../src/mp-instance";
import { IPreInit } from "../../src/pre-init-utils";
import RoktManager, { IRoktKit, IRoktSelectPlacementsOptions } from "../../src/roktManager";
import { testMPID } from '../src/config/constants';

Expand All @@ -10,6 +11,7 @@ const resolvePromise = () => new Promise(resolve => setTimeout(resolve, 0));
describe('RoktManager', () => {
let roktManager: RoktManager;
let currentUser: IMParticleUser;
let preInit: IPreInit;

const mockMPInstance = ({
Identity: {
Expand Down Expand Up @@ -42,7 +44,12 @@ describe('RoktManager', () => {
} as unknown) as IMParticleWebSDKInstance;

beforeEach(() => {
roktManager = new RoktManager();
preInit = {
readyQueue: [],
integrationDelays: {},
forwarderConstructors: [],
};
roktManager = new RoktManager(preInit);
currentUser = {
setUserAttributes: jest.fn(),
getUserIdentities: jest.fn().mockReturnValue({
Expand Down Expand Up @@ -440,8 +447,10 @@ describe('RoktManager', () => {
});

describe('#attachKit', () => {
it('should attach a kit', () => {
const kit: IRoktKit = {
let kit: IRoktKit;

beforeEach(() => {
kit = {
launcher: {
selectPlacements: jest.fn(),
hashAttributes: jest.fn(),
Expand All @@ -455,10 +464,77 @@ describe('RoktManager', () => {
setExtensionData: jest.fn(),
use: jest.fn(),
};
});

it('should attach a kit', () => {
roktManager.attachKit(kit);
expect(roktManager['kit']).not.toBeNull();
});

it('should call processMessageQueue when kit is attached', () => {
const processMessageQueueSpy = jest.spyOn(roktManager as any, 'processMessageQueue');

roktManager.attachKit(kit);

expect(processMessageQueueSpy).toHaveBeenCalledTimes(1);
processMessageQueueSpy.mockRestore();
});

it('should process ready queue when kit is attached and identity call failed', () => {
const readyQueueItem1 = jest.fn();
const readyQueueItem2 = jest.fn();
preInit.readyQueue = [readyQueueItem1, readyQueueItem2];

// Set identityCallFailed to true
roktManager['store'].identityCallFailed = true;

roktManager.attachKit(kit);

expect(readyQueueItem1).toHaveBeenCalledTimes(1);
expect(readyQueueItem2).toHaveBeenCalledTimes(1);
expect(preInit.readyQueue).toEqual([]);
});

it('should NOT process ready queue when identity call did not fail', () => {
const readyQueueItem1 = jest.fn();
const readyQueueItem2 = jest.fn();
preInit.readyQueue = [readyQueueItem1, readyQueueItem2];

// Set identityCallFailed to false
roktManager['store'].identityCallFailed = false;

roktManager.attachKit(kit);

// Ready queue items should NOT be called
expect(readyQueueItem1).not.toHaveBeenCalled();
expect(readyQueueItem2).not.toHaveBeenCalled();
// Ready queue should remain unchanged
expect(preInit.readyQueue).toEqual([readyQueueItem1, readyQueueItem2]);
});

it('should process message queue before processing ready queue when identity call failed', () => {
const processMessageQueueSpy = jest.spyOn(roktManager as any, 'processMessageQueue');
const processReadyQueueSpy = jest.spyOn(require('../../src/pre-init-utils'), 'processReadyQueue');

// Set identityCallFailed to true so ready queue is processed
roktManager['store'].identityCallFailed = true;

roktManager.attachKit(kit);

// Verify both were called
expect(processMessageQueueSpy).toHaveBeenCalled();
expect(processReadyQueueSpy).toHaveBeenCalled();

// Verify processMessageQueue was called before processReadyQueue
// by checking the order of calls
const messageQueueCallIndex = processMessageQueueSpy.mock.invocationCallOrder[0];
const readyQueueCallIndex = processReadyQueueSpy.mock.invocationCallOrder[0];

expect(messageQueueCallIndex).toBeLessThan(readyQueueCallIndex);

processMessageQueueSpy.mockRestore();
processReadyQueueSpy.mockRestore();
});
});

describe('#processMessageQueue', () => {
Expand Down
Loading
Loading