Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable EUID in the SDK #56

Merged
merged 7 commits into from
Feb 15, 2024
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"cSpell.words": [
"cstg",
"EUID",
"googletag",
"initialised",
"initialising",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@uid2/uid2-sdk",
"version": "4.0.0",
"version": "3.3.0",
"description": "UID2 Client SDK",
"main": "lib/uid2Sdk.js",
"types": "lib/uid2Sdk.d.ts",
Expand Down
49 changes: 49 additions & 0 deletions src/euidSdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { EventType, Uid2CallbackHandler } from './uid2CallbackManager';
import { CallbackContainer, ProductDetails, UID2SdkBase, UID2Setup } from './uid2Sdk';

export class EUID extends UID2SdkBase {
// Deprecated. Integrators should never access the cookie directly!
static get COOKIE_NAME() {
return '__euid';
}
private static get Uid2Details(): ProductDetails {
return {
name: 'EUID',
defaultBaseUrl: 'https://prod.euid.eu',
localStorageKey: 'EUID-sdk-identity',
cookieName: '__euid',
};
}

constructor(
existingCallbacks: Uid2CallbackHandler[] | undefined = undefined,
callbackContainer: CallbackContainer = {}
) {
super(existingCallbacks, EUID.Uid2Details);
const runCallbacks = () => {
this._callbackManager.runCallbacks(EventType.SdkLoaded, {});
};
if (window.__euid instanceof EUID) {
runCallbacks();
} else {
// Need to defer running callbacks until this is assigned to the window global
callbackContainer.callback = runCallbacks;
}
}
}

declare global {
interface Window {
__euid: EUID | UID2Setup | undefined;
}
}

export function __euidInternalHandleScriptLoad() {
const callbacks = window?.__euid?.callbacks || [];
const callbackContainer: CallbackContainer = {};
window.__euid = new EUID(callbacks, callbackContainer);
if (callbackContainer.callback) callbackContainer.callback();
}
__euidInternalHandleScriptLoad();

export const sdkWindow = globalThis.window;
78 changes: 78 additions & 0 deletions src/integrationTests/euidSdk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { afterEach, beforeEach, describe, expect, jest, test } from '@jest/globals';

import * as mocks from '../mocks';
import { sdkWindow, EUID, __euidInternalHandleScriptLoad } from '../euidSdk';
import { EventType, Uid2CallbackHandler } from '../uid2CallbackManager';

let callback: any;
let asyncCallback: jest.Mock<Uid2CallbackHandler>;
let euid: EUID;
let xhrMock: any;

const debugOutput = false;

mocks.setupFakeTime();

beforeEach(() => {
jest.clearAllMocks();
mocks.resetFakeTime();
jest.runOnlyPendingTimers();

callback = jest.fn();
xhrMock = new mocks.XhrMock(sdkWindow);
mocks.setCookieMock(sdkWindow.document);
asyncCallback = jest.fn((event, payload) => {
if (debugOutput) {
console.log('Async Callback Event:', event);
console.log('Payload:', payload);
}
});
});

afterEach(() => {
mocks.resetFakeTime();
});

const makeIdentity = mocks.makeIdentityV2;

describe('when a callback is provided', () => {
const refreshFrom = Date.now() + 100;
const identity = { ...makeIdentity(), refresh_from: refreshFrom };
const refreshedIdentity = {
...makeIdentity(),
advertising_token: 'refreshed_token',
};
describe('before constructor is called', () => {
test('it should be called during the construction process', () => {
sdkWindow.__euid = { callbacks: [asyncCallback] };
const calls = asyncCallback.mock.calls.length;
__euidInternalHandleScriptLoad();
expect(asyncCallback).toBeCalledTimes(calls + 1);
expect(asyncCallback).toBeCalledWith(EventType.SdkLoaded, expect.anything());
});
test('it should not be called by the constructor itself', () => {
sdkWindow.__euid = { callbacks: [asyncCallback] };
const calls = asyncCallback.mock.calls.length;
new EUID([asyncCallback]);
expect(asyncCallback).toBeCalledTimes(calls);
});
});
describe('before construction but the window global has already been assigned', () => {
// N.B. this is an artificial situation to check an edge case.
test('it should be called during construction', () => {
sdkWindow.__euid = new EUID();
const calls = asyncCallback.mock.calls.length;
new EUID([asyncCallback]);
expect(asyncCallback).toBeCalledTimes(calls + 1);
});
});
describe('after construction', () => {
test('the SDKLoaded event is sent immediately', () => {
sdkWindow.__euid = new EUID();
const calls = asyncCallback.mock.calls.length;
sdkWindow.__euid.callbacks!.push(asyncCallback);
expect(asyncCallback).toBeCalledTimes(calls + 1);
expect(asyncCallback).toBeCalledWith(EventType.SdkLoaded, expect.anything());
});
});
});
2 changes: 1 addition & 1 deletion src/uid2ApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export class Uid2ApiClient {
this._requestsInFlight.push(req);
req.overrideMimeType('text/plain');
req.open('POST', url, true);
req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); // TODO: EUID
req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); // N.B. EUID and UID2 currently both use the same header
let resolvePromise: (result: RefreshResult) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let rejectPromise: (reason?: any) => void;
Expand Down
10 changes: 7 additions & 3 deletions src/uid2CallbackManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,26 @@ export class Uid2CallbackManager {
private _getIdentity: () => Uid2Identity | null | undefined;
private _logger: Logger;
private _sdk: UID2SdkBase;
private _productName: string;
constructor(
sdk: UID2SdkBase,
productName: string,
getIdentity: () => Uid2Identity | null | undefined,
logger: Logger
) {
this._productName = productName;
this._logger = logger;
this._getIdentity = getIdentity;
this._sdk = sdk;
this._sdk.callbacks.push = this.callbackPushInterceptor.bind(this);
}

private static _sentSdkLoaded = false; //TODO: This needs to be fixed for EUID!
private static _sentSdkLoaded: Record<string, boolean> = {}; //TODO: This needs to be fixed for EUID!
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this comment still apply?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch - no, this fixes it for EUID :) I'll update it.

private _sentInit = false;
private callbackPushInterceptor(...args: Uid2CallbackHandler[]) {
for (const c of args) {
if (Uid2CallbackManager._sentSdkLoaded) this.safeRunCallback(c, EventType.SdkLoaded, {});
if (Uid2CallbackManager._sentSdkLoaded[this._productName])
this.safeRunCallback(c, EventType.SdkLoaded, {});
if (this._sentInit)
this.safeRunCallback(c, EventType.InitCompleted, {
identity: this._getIdentity() ?? null,
Expand All @@ -46,7 +50,7 @@ export class Uid2CallbackManager {

public runCallbacks(event: EventType, payload: Uid2CallbackPayload) {
if (event === EventType.InitCompleted) this._sentInit = true;
if (event === EventType.SdkLoaded) Uid2CallbackManager._sentSdkLoaded = true;
if (event === EventType.SdkLoaded) Uid2CallbackManager._sentSdkLoaded[this._productName] = true;
if (!this._sentInit && event !== EventType.SdkLoaded) return;

const enrichedPayload = {
Expand Down
15 changes: 10 additions & 5 deletions src/uid2Sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ function hasExpired(expiry: number, now = Date.now()) {
return expiry <= now;
}

type CallbackContainer = { callback?: () => void };
export type CallbackContainer = { callback?: () => void };

type ProductName = 'UID2' | 'EUID';
type ProductDetails = {
export type ProductName = 'UID2' | 'EUID';
export type ProductDetails = {
name: ProductName;
cookieName: string;
localStorageKey: string;
Expand Down Expand Up @@ -67,7 +67,12 @@ export abstract class UID2SdkBase {
if (existingCallbacks) this.callbacks = existingCallbacks;

this._tokenPromiseHandler = new UID2PromiseHandler(this);
this._callbackManager = new Uid2CallbackManager(this, () => this.getIdentity(), this._logger);
this._callbackManager = new Uid2CallbackManager(
this,
this._product.name,
() => this.getIdentity(),
this._logger
);
}

public init(opts: Uid2Options) {
Expand Down Expand Up @@ -451,7 +456,7 @@ export class UID2 extends UID2SdkBase {
}
}

type UID2Setup = {
export type UID2Setup = {
callbacks: Uid2CallbackHandler[] | undefined;
};
declare global {
Expand Down
Loading