Skip to content

Commit

Permalink
Merge pull request #101 from IABTechLab/ans-UID2-3836-js-sdk-bootstra…
Browse files Browse the repository at this point in the history
…p-init-call

 js sdk bootstrap init call
  • Loading branch information
ashleysmithTTD authored Aug 26, 2024
2 parents da2f766 + 043e89e commit f626314
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 31 deletions.
10 changes: 4 additions & 6 deletions src/configManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ export const storeConfig = (options: SdkOptions, productDetails: ProductDetails)
}
};

export const loadConfig = (
options: SdkOptions,
productDetails: ProductDetails
): storedConfig | null => {
if (options.useCookie) {
return loadConfigFromCookie(productDetails);
export const loadConfig = (productDetails: ProductDetails): storedConfig | null => {
const configCookie = loadConfigFromCookie(productDetails);
if (configCookie) {
return configCookie;
} else {
return loadConfigFromLocalStorage(productDetails);
}
Expand Down
29 changes: 21 additions & 8 deletions src/euidSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@ import { EventType, CallbackHandler } from './callbackManager';
import { CallbackContainer, sdkAssertErrorText, SdkBase, SDKSetup } from './sdkBase';
import { ProductDetails } from './product';
import { UidSecureSignalProviderType } from './secureSignal_types';
import { loadConfig } from './configManager';

export * from './exports';

const productDetails: ProductDetails = {
name: 'EUID',
defaultBaseUrl: 'https://prod.euid.eu',
localStorageKey: 'EUID-sdk-identity',
cookieName: '__euid',
};

export class EUID extends SdkBase {
private static cookieName = '__euid';
private static cookieName = productDetails.cookieName;
// Deprecated. Integrators should never access the cookie directly!
static get COOKIE_NAME() {
console.warn(
'Detected access to EUID.COOKIE_NAME. This is deprecated and will be removed in the future. Integrators should not access the cookie directly.'
);
return EUID.cookieName;
}
private static get EuidDetails(): ProductDetails {
return {
name: 'EUID',
defaultBaseUrl: 'https://prod.euid.eu',
localStorageKey: 'EUID-sdk-identity',
cookieName: EUID.cookieName,
};
static get EuidDetails(): ProductDetails {
return productDetails;
}

static setupGoogleTag() {
Expand Down Expand Up @@ -59,6 +62,15 @@ export function assertEUID(sdk: typeof window.__euid): asserts sdk is EUID {
if (!(sdk instanceof EUID)) throw new Error(sdkAssertErrorText('EUID', 'assertEUID'));
}

function bootstrapInit() {
if (window.__euid instanceof EUID) {
const config = loadConfig(productDetails);
if (config) {
window.__euid.init(config);
}
}
}

export function __euidInternalHandleScriptLoad() {
if (window.__euid && 'init' in window.__euid) {
// This has already been run
Expand All @@ -69,6 +81,7 @@ export function __euidInternalHandleScriptLoad() {
const callbackContainer: CallbackContainer = {};
window.__euid = new EUID(callbacks, callbackContainer);
if (callbackContainer.callback) callbackContainer.callback();
bootstrapInit();
}
__euidInternalHandleScriptLoad();

Expand Down
42 changes: 42 additions & 0 deletions src/integrationTests/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1061,3 +1061,45 @@ describe('Include sdk script multiple times', () => {
expect(asyncCallback).toBeCalledWith(EventType.SdkLoaded, expect.anything());
});
});

describe('SDK bootstraps itself if init has already been completed', () => {
const makeIdentity = mocks.makeIdentityV2;
const email = 'test@test.com';
const emailHash = 'lz3+Rj7IV4X1+Vr1ujkG7tstkxwk5pgkqJ6mXbpOgTs=';

beforeEach(() => {
sdkWindow.__uid2 = new UID2();
});

test('should bootstrap therefore public functions should return the correct values without calling init again', async () => {
const identity = { ...makeIdentity(), refresh_from: Date.now() + 100 };

uid2.init({ identity });

// mimicking closing the page, but not re-calling the UID2 constructor
sdkWindow.__uid2 = undefined;

__uid2InternalHandleScriptLoad();

expect(uid2.getAdvertisingToken()).toBe(identity.advertising_token);
expect(uid2.getIdentity()).toStrictEqual(identity);
expect(async () => {
await uid2.setIdentityFromEmail(email, mocks.makeCstgOption());
}).not.toThrow();
expect(async () => {
uid2.setIdentityFromEmailHash(emailHash, mocks.makeCstgOption());
}).not.toThrow();
});

test('should not bootstrap therefore public functions error or return undefined/null', async () => {
__uid2InternalHandleScriptLoad();
expect(uid2.getAdvertisingToken()).toBe(undefined);
expect(uid2.getIdentity()).toStrictEqual(null);
expect(async () => {
await uid2.setIdentityFromEmail(email, mocks.makeCstgOption());
}).rejects.toThrow();
expect(async () => {
await uid2.setIdentityFromEmailHash(emailHash, mocks.makeCstgOption());
}).rejects.toThrow();
});
});
8 changes: 4 additions & 4 deletions src/integrationTests/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,14 +528,14 @@ describe('Store config UID2', () => {
const cookie = getConfigCookie();
expect(cookie).toBeInstanceOf(Object);
expect(cookie).toHaveProperty('cookieDomain');
const storageConfig = loadConfig(options, productDetails);
const storageConfig = loadConfig(productDetails);
expect(storageConfig).toBeNull();
});
});
describe('when useCookie is false', () => {
test('should store config in local storage', () => {
uid2.init({ callback: callback, identity: identity, ...options });
const storageConfig = loadConfig(options, productDetails);
const storageConfig = loadConfig(productDetails);
expect(storageConfig).toBeInstanceOf(Object);
expect(storageConfig).toHaveProperty('cookieDomain');
const cookie = getConfigCookie();
Expand All @@ -545,11 +545,11 @@ describe('Store config UID2', () => {
describe('when useCookie is false', () => {
test('can successfully clear the config in storage', () => {
uid2.init({ callback: callback, identity: identity, ...options });
let storageConfig = loadConfig(options, productDetails);
let storageConfig = loadConfig(productDetails);
expect(storageConfig).toBeInstanceOf(Object);
expect(storageConfig).toHaveProperty('cookieDomain');
removeConfig(previousOptions, productDetails);
storageConfig = loadConfig(options, productDetails);
storageConfig = loadConfig(productDetails);
expect(storageConfig).toBeNull();
});
});
Expand Down
3 changes: 3 additions & 0 deletions src/integrationTests/secureSignal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ describe('Secure Signal Tests', () => {
});

describe('When SDK initialized after both SDK and SS script loaded - UID2', () => {
beforeEach(() => {
window.__uid2 = new UID2();
});
test('should send identity to Google ESP', async () => {
__uid2InternalHandleScriptLoad();
__uid2SSProviderScriptLoad();
Expand Down
8 changes: 2 additions & 6 deletions src/sdkBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ export abstract class SdkBase {
this.initInternal(opts);
}

public isInitialized() {
return this._initComplete;
}

private setInitComplete(isInitComplete: boolean) {
this._initComplete = isInitComplete;
}
Expand Down Expand Up @@ -137,7 +133,7 @@ export abstract class SdkBase {
return this._tokenPromiseHandler.createMaybeDeferredPromise(token ?? null);
}

public initComplete(): boolean {
public isInitComplete(): boolean {
return this._initComplete;
}

Expand Down Expand Up @@ -195,7 +191,7 @@ export abstract class SdkBase {
throw new TypeError('Calling init() once aborted or disconnected is not allowed');
}

if (this.isInitialized()) {
if (this.isInitComplete()) {
const previousOpts = { ...this._opts };
Object.assign(this._opts, opts);

Expand Down
27 changes: 20 additions & 7 deletions src/uid2Sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { isBase64Hash } from './hashedDii';
import { hashAndEncodeIdentifier, hashIdentifier } from './encoding/hash';
import { CallbackContainer, sdkAssertErrorText, SdkBase, SDKSetup } from './sdkBase';
import { ProductDetails } from './product';
import { loadConfig } from './configManager';
import { UidSecureSignalProviderType } from './secureSignal_types';

export * from './exports';
Expand All @@ -30,8 +31,15 @@ export class UID2Helper {
}
}

const productDetails: ProductDetails = {
name: 'UID2',
defaultBaseUrl: 'https://prod.uidapi.com',
localStorageKey: 'UID2-sdk-identity',
cookieName: '__uid_2',
};

export class UID2 extends SdkBase {
private static cookieName = '__uid_2';
private static cookieName = productDetails.cookieName;
// Deprecated. Integrators should never access the cookie directly!
static get COOKIE_NAME() {
console.warn(
Expand All @@ -40,12 +48,7 @@ export class UID2 extends SdkBase {
return UID2.cookieName;
}
private static get Uid2Details(): ProductDetails {
return {
name: 'UID2',
defaultBaseUrl: 'https://prod.uidapi.com',
localStorageKey: 'UID2-sdk-identity',
cookieName: UID2.cookieName,
};
return productDetails;
}

static setupGoogleTag() {
Expand Down Expand Up @@ -109,6 +112,15 @@ export function assertUID2(sdk: typeof window.__uid2): asserts sdk is UID2 {
if (!(sdk instanceof UID2)) throw new Error(sdkAssertErrorText('UID2', 'assertUID2'));
}

function bootstrapInit() {
if (window.__uid2 instanceof UID2) {
const config = loadConfig(productDetails);
if (config) {
window.__uid2.init(config);
}
}
}

export function __uid2InternalHandleScriptLoad() {
if (window.__uid2 && 'init' in window.__uid2) {
// This has already been run
Expand All @@ -120,6 +132,7 @@ export function __uid2InternalHandleScriptLoad() {
window.__uid2 = new UID2(callbacks, callbackContainer);
window.__uid2Helper = new UID2Helper();
if (callbackContainer.callback) callbackContainer.callback();
bootstrapInit();
}
__uid2InternalHandleScriptLoad();

Expand Down

0 comments on commit f626314

Please sign in to comment.