Skip to content

Commit a006d7f

Browse files
Add EUIDSdk class and initialization code.
Add relevant EUID tests (mostly we'll rely on the UID2 tests). Make the callback manager handle SDKLoaded state separately for each SDK.
1 parent 79c5f8b commit a006d7f

File tree

6 files changed

+146
-9
lines changed

6 files changed

+146
-9
lines changed

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"cSpell.words": [
33
"cstg",
4+
"EUID",
45
"googletag",
56
"initialised",
67
"initialising",

src/euidSdk.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { EventType, Uid2CallbackHandler } from './uid2CallbackManager';
2+
import { CallbackContainer, ProductDetails, UID2SdkBase, UID2Setup } from './uid2Sdk';
3+
4+
export class EUID extends UID2SdkBase {
5+
// Deprecated. Integrators should never access the cookie directly!
6+
static get COOKIE_NAME() {
7+
return '__euid';
8+
}
9+
private static get Uid2Details(): ProductDetails {
10+
return {
11+
name: 'EUID',
12+
defaultBaseUrl: 'https://prod.euid.eu',
13+
localStorageKey: 'EUID-sdk-identity',
14+
cookieName: '__euid',
15+
};
16+
}
17+
18+
constructor(
19+
existingCallbacks: Uid2CallbackHandler[] | undefined = undefined,
20+
callbackContainer: CallbackContainer = {}
21+
) {
22+
super(existingCallbacks, EUID.Uid2Details);
23+
const runCallbacks = () => {
24+
this._callbackManager.runCallbacks(EventType.SdkLoaded, {});
25+
};
26+
if (window.__euid instanceof EUID) {
27+
runCallbacks();
28+
} else {
29+
// Need to defer running callbacks until this is assigned to the window global
30+
callbackContainer.callback = runCallbacks;
31+
}
32+
}
33+
}
34+
35+
declare global {
36+
interface Window {
37+
__euid: EUID | UID2Setup | undefined;
38+
}
39+
}
40+
41+
export function __euidInternalHandleScriptLoad() {
42+
const callbacks = window?.__euid?.callbacks || [];
43+
const callbackContainer: CallbackContainer = {};
44+
window.__euid = new EUID(callbacks, callbackContainer);
45+
if (callbackContainer.callback) callbackContainer.callback();
46+
}
47+
__euidInternalHandleScriptLoad();
48+
49+
export const sdkWindow = globalThis.window;

src/integrationTests/euidSdk.test.ts

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { afterEach, beforeEach, describe, expect, jest, test } from '@jest/globals';
2+
3+
import * as mocks from '../mocks';
4+
import { sdkWindow, EUID, __euidInternalHandleScriptLoad } from '../euidSdk';
5+
import { EventType, Uid2CallbackHandler } from '../uid2CallbackManager';
6+
7+
let callback: any;
8+
let asyncCallback: jest.Mock<Uid2CallbackHandler>;
9+
let euid: EUID;
10+
let xhrMock: any;
11+
12+
const debugOutput = false;
13+
14+
mocks.setupFakeTime();
15+
16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
mocks.resetFakeTime();
19+
jest.runOnlyPendingTimers();
20+
21+
callback = jest.fn();
22+
xhrMock = new mocks.XhrMock(sdkWindow);
23+
mocks.setCookieMock(sdkWindow.document);
24+
asyncCallback = jest.fn((event, payload) => {
25+
if (debugOutput) {
26+
console.log('Async Callback Event:', event);
27+
console.log('Payload:', payload);
28+
}
29+
});
30+
});
31+
32+
afterEach(() => {
33+
mocks.resetFakeTime();
34+
});
35+
36+
const makeIdentity = mocks.makeIdentityV2;
37+
38+
describe('when a callback is provided', () => {
39+
const refreshFrom = Date.now() + 100;
40+
const identity = { ...makeIdentity(), refresh_from: refreshFrom };
41+
const refreshedIdentity = {
42+
...makeIdentity(),
43+
advertising_token: 'refreshed_token',
44+
};
45+
describe('before constructor is called', () => {
46+
test('it should be called during the construction process', () => {
47+
sdkWindow.__euid = { callbacks: [asyncCallback] };
48+
const calls = asyncCallback.mock.calls.length;
49+
__euidInternalHandleScriptLoad();
50+
expect(asyncCallback).toBeCalledTimes(calls + 1);
51+
expect(asyncCallback).toBeCalledWith(EventType.SdkLoaded, expect.anything());
52+
});
53+
test('it should not be called by the constructor itself', () => {
54+
sdkWindow.__euid = { callbacks: [asyncCallback] };
55+
const calls = asyncCallback.mock.calls.length;
56+
new EUID([asyncCallback]);
57+
expect(asyncCallback).toBeCalledTimes(calls);
58+
});
59+
});
60+
describe('before construction but the window global has already been assigned', () => {
61+
// N.B. this is an artificial situation to check an edge case.
62+
test('it should be called during construction', () => {
63+
sdkWindow.__euid = new EUID();
64+
const calls = asyncCallback.mock.calls.length;
65+
new EUID([asyncCallback]);
66+
expect(asyncCallback).toBeCalledTimes(calls + 1);
67+
});
68+
});
69+
describe('after construction', () => {
70+
test('the SDKLoaded event is sent immediately', () => {
71+
sdkWindow.__euid = new EUID();
72+
const calls = asyncCallback.mock.calls.length;
73+
sdkWindow.__euid.callbacks!.push(asyncCallback);
74+
expect(asyncCallback).toBeCalledTimes(calls + 1);
75+
expect(asyncCallback).toBeCalledWith(EventType.SdkLoaded, expect.anything());
76+
});
77+
});
78+
});

src/uid2ApiClient.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export class Uid2ApiClient {
135135
this._requestsInFlight.push(req);
136136
req.overrideMimeType('text/plain');
137137
req.open('POST', url, true);
138-
req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); // TODO: EUID
138+
req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); // N.B. EUID and UID2 currently both use the same header
139139
let resolvePromise: (result: RefreshResult) => void;
140140
// eslint-disable-next-line @typescript-eslint/no-explicit-any
141141
let rejectPromise: (reason?: any) => void;

src/uid2CallbackManager.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,26 @@ export class Uid2CallbackManager {
2020
private _getIdentity: () => Uid2Identity | null | undefined;
2121
private _logger: Logger;
2222
private _sdk: UID2SdkBase;
23+
private _productName: string;
2324
constructor(
2425
sdk: UID2SdkBase,
26+
productName: string,
2527
getIdentity: () => Uid2Identity | null | undefined,
2628
logger: Logger
2729
) {
30+
this._productName = productName;
2831
this._logger = logger;
2932
this._getIdentity = getIdentity;
3033
this._sdk = sdk;
3134
this._sdk.callbacks.push = this.callbackPushInterceptor.bind(this);
3235
}
3336

34-
private static _sentSdkLoaded = false; //TODO: This needs to be fixed for EUID!
37+
private static _sentSdkLoaded: Record<string, boolean> = {}; //TODO: This needs to be fixed for EUID!
3538
private _sentInit = false;
3639
private callbackPushInterceptor(...args: Uid2CallbackHandler[]) {
3740
for (const c of args) {
38-
if (Uid2CallbackManager._sentSdkLoaded) this.safeRunCallback(c, EventType.SdkLoaded, {});
41+
if (Uid2CallbackManager._sentSdkLoaded[this._productName])
42+
this.safeRunCallback(c, EventType.SdkLoaded, {});
3943
if (this._sentInit)
4044
this.safeRunCallback(c, EventType.InitCompleted, {
4145
identity: this._getIdentity() ?? null,
@@ -46,7 +50,7 @@ export class Uid2CallbackManager {
4650

4751
public runCallbacks(event: EventType, payload: Uid2CallbackPayload) {
4852
if (event === EventType.InitCompleted) this._sentInit = true;
49-
if (event === EventType.SdkLoaded) Uid2CallbackManager._sentSdkLoaded = true;
53+
if (event === EventType.SdkLoaded) Uid2CallbackManager._sentSdkLoaded[this._productName] = true;
5054
if (!this._sentInit && event !== EventType.SdkLoaded) return;
5155

5256
const enrichedPayload = {

src/uid2Sdk.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ function hasExpired(expiry: number, now = Date.now()) {
2020
return expiry <= now;
2121
}
2222

23-
type CallbackContainer = { callback?: () => void };
23+
export type CallbackContainer = { callback?: () => void };
2424

25-
type ProductName = 'UID2' | 'EUID';
26-
type ProductDetails = {
25+
export type ProductName = 'UID2' | 'EUID';
26+
export type ProductDetails = {
2727
name: ProductName;
2828
cookieName: string;
2929
localStorageKey: string;
@@ -68,7 +68,12 @@ export abstract class UID2SdkBase {
6868
if (existingCallbacks) this.callbacks = existingCallbacks;
6969

7070
this._tokenPromiseHandler = new UID2PromiseHandler(this);
71-
this._callbackManager = new Uid2CallbackManager(this, () => this.getIdentity(), this._logger);
71+
this._callbackManager = new Uid2CallbackManager(
72+
this,
73+
this._product.name,
74+
() => this.getIdentity(),
75+
this._logger
76+
);
7277
}
7378

7479
public init(opts: Uid2Options) {
@@ -450,7 +455,7 @@ export class UID2 extends UID2SdkBase {
450455
}
451456
}
452457

453-
type UID2Setup = {
458+
export type UID2Setup = {
454459
callbacks: Uid2CallbackHandler[] | undefined;
455460
};
456461
declare global {

0 commit comments

Comments
 (0)