Skip to content
Open
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
19 changes: 19 additions & 0 deletions src/Rokt-Kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ interface RoktLauncher {
selectPlacements(options: Record<string, unknown>): RoktSelection | Promise<RoktSelection>;
hashAttributes(attributes: Record<string, unknown>): Promise<Record<string, unknown>>;
use(extensionName: string): Promise<unknown>;
terminate(): Promise<void>;
}

interface RoktGlobal {
Expand Down Expand Up @@ -1521,6 +1522,24 @@ class RoktKit implements KitInterface {
return this.launcher!.use(extensionName);
}

/**
* Tears down the Rokt launcher and the placements it rendered.
*
* The launcher reference is deliberately left in place. The Rokt Web SDK
* memoizes a single launcher per page, so clearing it here could not buy the
* caller a fresh one — it would only flip the kit to not-ready and leave
* later calls queued forever. Leaving state untouched makes this exactly
* equivalent to the `window.Rokt.currentLauncher.terminate()` call partners
* use today, just reachable through a supported API.
*/
public terminate(): Promise<void> {
if (!this.isKitReady()) {
console.error('Rokt Kit: Not initialized');
return Promise.resolve();
}
return this.launcher!.terminate();
}

/**
* Registers a callback to be invoked once rokt-thank-you-element.js becomes available.
*/
Expand Down
133 changes: 133 additions & 0 deletions test/src/tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3249,6 +3249,139 @@ describe('Rokt Forwarder', () => {
});
});

describe('#terminate', () => {
beforeEach(() => {
(window as any).Rokt = new (MockRoktForwarder as any)();
(window as any).mParticle.Rokt = (window as any).Rokt;
(window as any).mParticle.Rokt.attachKitCalled = false;
(window as any).mParticle.Rokt.attachKit = async (kit: any) => {
(window as any).mParticle.Rokt.attachKitCalled = true;
(window as any).mParticle.Rokt.kit = kit;
Promise.resolve();
};
});

it('should call launcher.terminate when fully initialized', async () => {
let terminateCalled = false;
(window as any).mParticle.forwarder.isInitialized = true;
(window as any).mParticle.forwarder.launcher = {
terminate: function () {
terminateCalled = true;
return Promise.resolve();
},
};

await (window as any).mParticle.forwarder.terminate();

expect(terminateCalled).toBe(true);
});

it('should return the promise from launcher.terminate', async () => {
let resolved = false;
(window as any).mParticle.forwarder.isInitialized = true;
(window as any).mParticle.forwarder.launcher = {
terminate: () => new Promise<void>((resolve) => setTimeout(resolve, 0)),
};

await (window as any).mParticle.forwarder.terminate().then(() => {
resolved = true;
});

expect(resolved).toBe(true);
});

it('should resolve without calling the launcher when called before initialization', async () => {
const originalConsoleError = window.console.error;
let errorMessage = null;
window.console.error = function (message: any) {
errorMessage = message;
};

(window as any).mParticle.forwarder.isInitialized = false;
(window as any).mParticle.forwarder.launcher = null;

try {
await expect((window as any).mParticle.forwarder.terminate()).resolves.toBeUndefined();
} finally {
window.console.error = originalConsoleError;
}

expect(errorMessage).toBe('Rokt Kit: Not initialized');
});

it('should resolve without calling the launcher when initialized but the launcher is missing', async () => {
const originalConsoleError = window.console.error;
let errorMessage = null;
window.console.error = function (message: any) {
errorMessage = message;
};

(window as any).mParticle.forwarder.isInitialized = true;
(window as any).mParticle.forwarder.launcher = null;

try {
await expect((window as any).mParticle.forwarder.terminate()).resolves.toBeUndefined();
} finally {
window.console.error = originalConsoleError;
}

expect(errorMessage).toBe('Rokt Kit: Not initialized');
});

// The Rokt Web SDK memoizes one launcher per page, so terminate must not
// clear the kit's launcher references — doing so would flip the kit to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Typically createLauncher can be called again after terminate, usually for SPAs.
Can this create another launcher instance in that flow and continue? (given the launcher reference is maintained)
Create a test to capture this scenario

// not-ready with no way back.
it('should leave the launcher references intact so the kit stays ready', async () => {
const launcher = {
terminate: () => Promise.resolve(),
};
(window as any).mParticle.forwarder.isInitialized = true;
(window as any).mParticle.forwarder.launcher = launcher;
(window as any).Rokt.currentLauncher = launcher;

await (window as any).mParticle.forwarder.terminate();

expect((window as any).mParticle.forwarder.launcher).toBe(launcher);
expect((window as any).Rokt.currentLauncher).toBe(launcher);
});

it('should call launcher.terminate after init (test mode) and attach', async () => {
let terminateCalled = false;

(window as any).mParticle.Rokt.attachKitCalled = false;
(window as any).mParticle.Rokt.attachKit = async (kit: any) => {
(window as any).mParticle.Rokt.attachKitCalled = true;
(window as any).mParticle.Rokt.kit = kit;
Promise.resolve();
};

(window as any).Rokt.createLauncher = async function () {
return Promise.resolve({
terminate: function () {
terminateCalled = true;
return Promise.resolve();
},
});
};

await (window as any).mParticle.forwarder.init(
{
accountId: '123456',
},
reportService.cb,
true,
null,
{},
);

await waitForCondition(() => (window as any).mParticle.Rokt.attachKitCalled);

await (window as any).mParticle.forwarder.terminate();

expect(terminateCalled).toBe(true);
});
});

describe('#setUserAttribute', () => {
beforeEach(() => {
(window as any).mParticle.sessionManager = {
Expand Down
Loading