diff --git a/src/Uid2Options.ts b/src/Uid2Options.ts index 4a36455..9b8cb48 100644 --- a/src/Uid2Options.ts +++ b/src/Uid2Options.ts @@ -10,6 +10,7 @@ export type Uid2Options = BaseUid2Options & type BaseUid2Options = { refreshRetryPeriod?: number; identity?: Uid2Identity; + useCookie?: boolean; }; export function isUID2OptionsOrThrow( diff --git a/src/integrationTests/async.spec.ts b/src/integrationTests/async.spec.ts index a2bd786..810af33 100644 --- a/src/integrationTests/async.spec.ts +++ b/src/integrationTests/async.spec.ts @@ -23,201 +23,233 @@ beforeEach(() => { xhrMock = new mocks.XhrMock(sdkWindow); _cryptoMock = new mocks.CryptoMock(sdkWindow); mocks.setCookieMock(sdkWindow.document); + removeUid2Cookie(); + removeUid2LocalStorage(); }); afterEach(() => { mocks.resetFakeTime(); }); +const removeUid2Cookie = mocks.removeUid2Cookie; +const removeUid2LocalStorage = mocks.removeUid2LocalStorage; + const makeIdentity = mocks.makeIdentityV2; -describe("when getAdvertisingTokenAsync is called before init", () => { - describe("when initialising with a valid identity", () => { - const identity = makeIdentity(); - test("it should resolve promise after invoking the callback", () => { - const p = uid2.getAdvertisingTokenAsync().then((token: any) => { - expect(callback).toHaveBeenCalled(); - return token; - }); - uid2.init({ callback: callback, identity: identity }); - jest.runAllTimers(); - return expect(p).resolves.toBe(identity.advertising_token); +let useCookie: boolean | undefined = undefined; + +const testCookieAndLocalStorage = (test: () => void, only = false) => { + const describeFn = only ? describe.only : describe; + describeFn('Using default: ', () => { + beforeEach(() => { + useCookie = undefined; }); + test(); }); - - describe("when initialising with an invalid identity", () => { - test("it should reject promise after invoking the callback", () => { - const p = uid2.getAdvertisingTokenAsync().catch((e: any) => { - expect(callback).toHaveBeenCalled(); - throw e; - }); - uid2.init({ callback: callback }); - return expect(p).rejects.toBeInstanceOf(Error); + describeFn('Using cookies ', () => { + beforeEach(() => { + useCookie = true; }); + test(); }); - - describe("when initalising with a non-expired identity which requires a refresh", () => { - test("it should resolve updated advertising", () => { - const originalIdentity = makeIdentity({ - refresh_from: Date.now() - 100000, - }); - const updatedIdentity = makeIdentity({ - advertising_token: "updated_advertising_token", - }); - const p = uid2.getAdvertisingTokenAsync(); - uid2.init({ identity: originalIdentity }); - xhrMock.responseText = btoa( - JSON.stringify({ status: "success", body: updatedIdentity }) - ); - xhrMock.onreadystatechange(new Event("")); - return expect(p).resolves.toBe(updatedIdentity.advertising_token); + describeFn('Using local storage ', () => { + beforeEach(() => { + useCookie = false; }); + test(); }); +}; + +testCookieAndLocalStorage(() => { + describe("when getAdvertisingTokenAsync is called before init", () => { + describe("when initialising with a valid identity", () => { + const identity = makeIdentity(); + test("it should resolve promise after invoking the callback", () => { + const p = uid2.getAdvertisingTokenAsync().then((token: any) => { + expect(callback).toHaveBeenCalled(); + return token; + }); + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + jest.runAllTimers(); + return expect(p).resolves.toBe(identity.advertising_token); + }); + }); - describe("when auto refresh fails, but identity still valid", () => { - test("it should resolve original advertising token", () => { - const originalIdentity = makeIdentity({ - refresh_from: Date.now() - 100000, + describe("when initialising with an invalid identity", () => { + test("it should reject promise after invoking the callback", () => { + const p = uid2.getAdvertisingTokenAsync().catch((e: any) => { + expect(callback).toHaveBeenCalled(); + throw e; + }); + uid2.init({ callback: callback, useCookie: useCookie }); + return expect(p).rejects.toBeInstanceOf(Error); }); - const p = uid2.getAdvertisingTokenAsync().then((token: any) => { - expect(callback).toHaveBeenCalled(); - return token; + }); + + describe("when initialising with a non-expired identity which requires a refresh", () => { + test("it should resolve updated advertising", () => { + const originalIdentity = makeIdentity({ + refresh_from: Date.now() - 100000, + }); + const updatedIdentity = makeIdentity({ + advertising_token: "updated_advertising_token", + }); + const p = uid2.getAdvertisingTokenAsync(); + uid2.init({ identity: originalIdentity, useCookie: useCookie }); + xhrMock.responseText = btoa( + JSON.stringify({ status: "success", body: updatedIdentity }) + ); + xhrMock.onreadystatechange(new Event("")); + return expect(p).resolves.toBe(updatedIdentity.advertising_token); }); - uid2.init({ callback: callback, identity: originalIdentity }); - xhrMock.responseText = JSON.stringify({ status: "error" }); - xhrMock.onreadystatechange(new Event("")); - return expect(p).resolves.toBe(originalIdentity.advertising_token); }); - }); - describe("when auto refresh fails, but identity already expired", () => { - test("it should reject promise after invoking the callback", () => { - const originalIdentity = makeIdentity({ - refresh_from: Date.now() - 100000, - identity_expires: Date.now() - 1, + describe("when auto refresh fails, but identity still valid", () => { + test("it should resolve original advertising token", () => { + const originalIdentity = makeIdentity({ + refresh_from: Date.now() - 100000, + }); + const p = uid2.getAdvertisingTokenAsync().then((token: any) => { + expect(callback).toHaveBeenCalled(); + return token; + }); + uid2.init({ callback: callback, identity: originalIdentity, useCookie: useCookie }); + xhrMock.responseText = JSON.stringify({ status: "error" }); + xhrMock.onreadystatechange(new Event("")); + return expect(p).resolves.toBe(originalIdentity.advertising_token); }); - const p = uid2.getAdvertisingTokenAsync().catch((e: any) => { - expect(callback).toHaveBeenCalled(); - throw e; + }); + + describe("when auto refresh fails, but identity already expired", () => { + test("it should reject promise after invoking the callback", () => { + const originalIdentity = makeIdentity({ + refresh_from: Date.now() - 100000, + identity_expires: Date.now() - 1, + }); + const p = uid2.getAdvertisingTokenAsync().catch((e: any) => { + expect(callback).toHaveBeenCalled(); + throw e; + }); + uid2.init({ callback: callback, identity: originalIdentity, useCookie: useCookie }); + xhrMock.responseText = JSON.stringify({ status: "error" }); + xhrMock.onreadystatechange(new Event("")); + return expect(p).rejects.toBeInstanceOf(Error); }); - uid2.init({ callback: callback, identity: originalIdentity }); - xhrMock.responseText = JSON.stringify({ status: "error" }); - xhrMock.onreadystatechange(new Event("")); - return expect(p).rejects.toBeInstanceOf(Error); }); - }); - describe("when giving multiple promises", () => { - const identity = makeIdentity(); - test("it should resolve all promises", () => { - const p1 = uid2.getAdvertisingTokenAsync(); - const p2 = uid2.getAdvertisingTokenAsync(); - const p3 = uid2.getAdvertisingTokenAsync(); - uid2.init({ identity: identity }); - return expect(Promise.all([p1, p2, p3])).resolves.toStrictEqual( - Array(3).fill(identity.advertising_token) - ); + describe("when giving multiple promises", () => { + const identity = makeIdentity(); + test("it should resolve all promises", () => { + const p1 = uid2.getAdvertisingTokenAsync(); + const p2 = uid2.getAdvertisingTokenAsync(); + const p3 = uid2.getAdvertisingTokenAsync(); + uid2.init({ identity: identity, useCookie: useCookie }); + return expect(Promise.all([p1, p2, p3])).resolves.toStrictEqual( + Array(3).fill(identity.advertising_token) + ); + }); }); }); -}); -describe("when getAdvertisingTokenAsync is called after init completed", () => { - describe("when initialised with a valid identity", () => { - const identity = makeIdentity(); - test("it should resolve promise", () => { - uid2.init({ identity: identity }); - return expect(uid2.getAdvertisingTokenAsync()).resolves.toBe( - identity.advertising_token - ); + describe("when getAdvertisingTokenAsync is called after init completed", () => { + describe("when initialised with a valid identity", () => { + const identity = makeIdentity(); + test("it should resolve promise", () => { + uid2.init({ identity: identity, useCookie: useCookie }); + return expect(uid2.getAdvertisingTokenAsync()).resolves.toBe( + identity.advertising_token + ); + }); }); - }); - describe("when initialisation failed", () => { - test("it should reject promise", () => { - uid2.init({}); - return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( - Error - ); + describe("when initialisation failed", () => { + test("it should reject promise", () => { + uid2.init({}); + return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( + Error + ); + }); }); - }); - describe("when identity is temporarily not available", () => { - test("it should reject promise", () => { - const originalIdentity = makeIdentity({ - refresh_from: Date.now() - 100000, - identity_expires: Date.now() - 1, + describe("when identity is temporarily not available", () => { + test("it should reject promise", () => { + const originalIdentity = makeIdentity({ + refresh_from: Date.now() - 100000, + identity_expires: Date.now() - 1, + }); + uid2.init({ identity: originalIdentity, useCookie: useCookie }); + xhrMock.responseText = JSON.stringify({ status: "error" }); + xhrMock.onreadystatechange(new Event("")); + return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( + Error + ); }); - uid2.init({ identity: originalIdentity }); - xhrMock.responseText = JSON.stringify({ status: "error" }); - xhrMock.onreadystatechange(new Event("")); - return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( - Error - ); }); - }); - describe("when disconnect() has been called", () => { - test("it should reject promise", () => { - uid2.init({ identity: makeIdentity() }); - uid2.disconnect(); - return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( - Error - ); + describe("when disconnect() has been called", () => { + test("it should reject promise", () => { + uid2.init({ identity: makeIdentity(), useCookie: useCookie }); + uid2.disconnect(); + return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( + Error + ); + }); }); }); -}); -describe("when getAdvertisingTokenAsync is called before refresh on init completes", () => { - const originalIdentity = makeIdentity({ - refresh_from: Date.now() - 100000, - }); - beforeEach(() => { - uid2.init({ identity: originalIdentity }); - }); + describe("when getAdvertisingTokenAsync is called before refresh on init completes", () => { + const originalIdentity = makeIdentity({ + refresh_from: Date.now() - 100000, + }); + beforeEach(() => { + uid2.init({ identity: originalIdentity, useCookie: useCookie }); + }); - describe("when promise obtained after disconnect", () => { - test("it should reject promise", () => { - uid2.disconnect(); - return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( - Error - ); + describe("when promise obtained after disconnect", () => { + test("it should reject promise", () => { + uid2.disconnect(); + return expect(uid2.getAdvertisingTokenAsync()).rejects.toBeInstanceOf( + Error + ); + }); }); }); -}); -describe("when window.__uid2.init is called on SdkLoaded from a callback", () => { - const identity = makeIdentity(); - // Reset window UID2 instance - const callback = jest.fn((eventType: EventType) => { - if (eventType === UID2.EventType.SdkLoaded) { - console.log("Trying"); - try { - (sdkWindow.__uid2 as UID2).init({ identity }); - } catch (ex) { - console.log(ex); - throw ex; + describe("when window.__uid2.init is called on SdkLoaded from a callback", () => { + const identity = makeIdentity(); + // Reset window UID2 instance + const callback = jest.fn((eventType: EventType) => { + if (eventType === UID2.EventType.SdkLoaded) { + console.log("Trying"); + try { + (sdkWindow.__uid2 as UID2).init({ identity, useCookie: useCookie }); + } catch (ex) { + console.log(ex); + throw ex; + } + console.log("Succeeded"); } - console.log("Succeeded"); - } - }); - test("the SDK should be initialized correctly", () => { - sdkWindow.__uid2 = { callbacks: [] }; - sdkWindow.__uid2.callbacks!.push(callback); - expect(callback).toHaveBeenCalledTimes(0); - __uid2InternalHandleScriptLoad(); - jest.runOnlyPendingTimers(); - if (!(sdkWindow.__uid2 instanceof UID2)) - throw Error( - "UID2 should be ready to use by the time SdkLoaded is triggered." + }); + test("the SDK should be initialized correctly", () => { + sdkWindow.__uid2 = { callbacks: [] }; + sdkWindow.__uid2.callbacks!.push(callback); + expect(callback).toHaveBeenCalledTimes(0); + __uid2InternalHandleScriptLoad(); + jest.runOnlyPendingTimers(); + if (!(sdkWindow.__uid2 instanceof UID2)) + throw Error( + "UID2 should be ready to use by the time SdkLoaded is triggered." + ); + expect(callback).toHaveBeenNthCalledWith( + 1, + UID2.EventType.SdkLoaded, + expect.anything() ); - expect(callback).toHaveBeenNthCalledWith( - 1, - UID2.EventType.SdkLoaded, - expect.anything() - ); - console.log(identity.advertising_token); - expect(sdkWindow.__uid2.getAdvertisingToken()).toBe( - identity.advertising_token - ); + console.log(sdkWindow.__uid2.getAdvertisingToken()); + console.log(identity.advertising_token); + expect(sdkWindow.__uid2.getAdvertisingToken()).toBe( + identity.advertising_token + ); + }); }); }); diff --git a/src/integrationTests/autoRefresh.test.ts b/src/integrationTests/autoRefresh.test.ts index bd6734f..4dcb6d6 100644 --- a/src/integrationTests/autoRefresh.test.ts +++ b/src/integrationTests/autoRefresh.test.ts @@ -24,538 +24,568 @@ beforeEach(() => { xhrMock = new mocks.XhrMock(sdkWindow); _cryptoMock = new mocks.CryptoMock(sdkWindow); mocks.setCookieMock(sdkWindow.document); + removeUid2Cookie(); + removeUid2LocalStorage(); }); afterEach(() => { mocks.resetFakeTime(); }); -const getUid2Cookie = mocks.getUid2Cookie; +const getUid2 = mocks.getUid2; const makeIdentity = mocks.makeIdentityV2; +const removeUid2Cookie = mocks.removeUid2Cookie; +const removeUid2LocalStorage = mocks.removeUid2LocalStorage; -describe("when auto refreshing a non-expired identity which does not require a refresh", () => { - const originalIdentity = makeIdentity({ - advertising_token: "original_advertising_token", - }); - beforeEach(() => { - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - jest.clearAllMocks(); - jest.runOnlyPendingTimers(); - uid2.init({ callback: callback, identity: originalIdentity }); - }); - - test("should invoke the callback", () => { - expect(sdkWindow.crypto).toBeDefined(); - expect(callback).toHaveBeenCalledTimes(1); - }); - test("should not initiate token refresh", () => { - expect(xhrMock.send).not.toHaveBeenCalled(); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toBeCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState(); - }); +let useCookie: boolean | undefined = undefined; - test("getAdvertisingTokenAsync should return current adverstising token", async () => { - expect(await getAdvertisingTokenPromise).toEqual( - originalIdentity.advertising_token - ); - }); -}); - -describe("when auto refreshing a non-expired identity which requires a refresh", () => { - const refreshFrom = Date.now() + 100; - const originalIdentity = makeIdentity({ - advertising_token: "original_advertising_token", - refresh_from: refreshFrom, - }); - const updatedIdentity = makeIdentity({ - advertising_token: "updated_advertising_token", - }); - - beforeEach(() => { - uid2.init({ callback: callback, identity: originalIdentity }); - jest.clearAllMocks(); - jest.setSystemTime(refreshFrom); - jest.runOnlyPendingTimers(); - }); - - test("should not invoke the callback", () => { - expect(callback).not.toHaveBeenCalled(); - }); - test("should initiate token refresh", () => { - expect(xhrMock.send).toHaveBeenCalledTimes(1); +const testCookieAndLocalStorage = (test: () => void, only = false) => { + const describeFn = only ? describe.only : describe; + describeFn('Using default: ', () => { + beforeEach(() => { + useCookie = undefined; + }); + test(); }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + describeFn('Using cookies ', () => { + beforeEach(() => { + useCookie = true; + }); + test(); }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState(); + describeFn('Using local storage ', () => { + beforeEach(() => { + useCookie = false; + }); + test(); }); +}; - describe("when token refresh succeeds", () => { +testCookieAndLocalStorage(() => { + describe("when auto refreshing a non-expired identity which does not require a refresh", () => { + const originalIdentity = makeIdentity({ + advertising_token: "original_advertising_token", + }); beforeEach(() => { getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - xhrMock.responseText = btoa( - JSON.stringify({ status: "success", body: updatedIdentity }) - ); - xhrMock.onreadystatechange(new Event("")); + jest.clearAllMocks(); + jest.runOnlyPendingTimers(); + uid2.init({ callback: callback, identity: originalIdentity, useCookie: useCookie }); }); test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: updatedIdentity.advertising_token, - advertising_token: updatedIdentity.advertising_token, - status: UID2.IdentityStatus.REFRESHED, - }) - ); + expect(sdkWindow.crypto).toBeDefined(); + expect(callback).toHaveBeenCalledTimes(1); }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - updatedIdentity.advertising_token - ); + test("should not initiate token refresh", () => { + expect(xhrMock.send).not.toHaveBeenCalled(); }); test("should set refresh timer", () => { expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toBeCalled(); }); test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - updatedIdentity.advertising_token - ); + (expect(uid2) as any).toBeInAvailableState(); }); - test("getAdvertisingTokenAsync should return new advertising token", async () => { + test("getAdvertisingTokenAsync should return current advertising token", async () => { expect(await getAdvertisingTokenPromise).toEqual( - updatedIdentity.advertising_token + originalIdentity.advertising_token ); }); }); - describe("when token refresh returns optout", () => { - let expection: any; - beforeEach(async () => { - try { - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); - xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } - }); - test("getAdvertisingTokenPromise should reject", () => { - expect(expection).toEqual(new Error("UID2 SDK aborted.")); - }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.OPTOUT, - }) - ); - }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + describe("when auto refreshing a non-expired identity which requires a refresh", () => { + const refreshFrom = Date.now() + 100; + const originalIdentity = makeIdentity({ + advertising_token: "original_advertising_token", + refresh_from: refreshFrom, }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + const updatedIdentity = makeIdentity({ + advertising_token: "updated_advertising_token", }); - }); - describe("when token refresh returns refresh token expired", () => { - let expection: any; - beforeEach(async () => { - try { - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - xhrMock.responseText = btoa( - JSON.stringify({ status: "expired_token" }) - ); - xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } - }); - test("getAdvertisingTokenPromise should reject", () => { - expect(expection).toEqual(new Error("UID2 SDK aborted.")); + beforeEach(() => { + uid2.init({ callback: callback, identity: originalIdentity, useCookie: useCookie }); + jest.clearAllMocks(); + jest.setSystemTime(refreshFrom); + jest.runOnlyPendingTimers(); }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); + + test("should not invoke the callback", () => { + expect(callback).not.toHaveBeenCalled(); }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); + test("should initiate token refresh", () => { + expect(xhrMock.send).toHaveBeenCalledTimes(1); }); test("should not set refresh timer", () => { expect(setTimeout).not.toHaveBeenCalled(); expect(clearTimeout).not.toHaveBeenCalled(); }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState(); }); - }); - describe("when token refresh returns an error status", () => { - let expection: any; - beforeEach(async () => { - try { + describe("when token refresh succeeds", () => { + beforeEach(() => { getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - xhrMock.responseText = JSON.stringify({ - status: "error", - body: updatedIdentity, - }); + xhrMock.responseText = btoa( + JSON.stringify({ status: "success", body: updatedIdentity }) + ); xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } - }); - test("getAdvertisingTokenPromise should return current advertising token", async () => { - expect(await getAdvertisingTokenPromise).toEqual( - originalIdentity.advertising_token - ); - }); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: updatedIdentity.advertising_token, + advertising_token: updatedIdentity.advertising_token, + status: UID2.IdentityStatus.REFRESHED, + }) + ); + }); + test("should store value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + updatedIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + updatedIdentity.advertising_token + ); + }); - test("should not update cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - originalIdentity.advertising_token - ); - }); - }); + test("getAdvertisingTokenAsync should return new advertising token", async () => { + expect(await getAdvertisingTokenPromise).toEqual( + updatedIdentity.advertising_token + ); + }); + }); + + describe("when token refresh returns optout", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + test("getAdvertisingTokenPromise should reject", () => { + expect(exception).toEqual(new Error("UID2 SDK aborted.")); + }); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.OPTOUT, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); + }); + + describe("when token refresh returns refresh token expired", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + xhrMock.responseText = btoa( + JSON.stringify({ status: "expired_token" }) + ); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + test("getAdvertisingTokenPromise should reject", () => { + expect(exception).toEqual(new Error("UID2 SDK aborted.")); + }); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); + }); + + describe("when token refresh returns an error status", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + xhrMock.responseText = JSON.stringify({ + status: "error", + body: updatedIdentity, + }); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + test("getAdvertisingTokenPromise should return current advertising token", async () => { + expect(await getAdvertisingTokenPromise).toEqual( + originalIdentity.advertising_token + ); + }); - describe("when token refresh fails and current identity expires", () => { - let expection: any; - beforeEach(async () => { - try { + test("should not update value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + originalIdentity.advertising_token + ); + }); + }); + + describe("when token refresh fails and current identity expires", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); + xhrMock.responseText = JSON.stringify({ status: "error" }); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + + test("getAdvertisingTokenPromise should reject", () => { + expect(exception).toEqual(new Error("UID2 SDK aborted.")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); + }); + + describe("when a new token is set using setIdentity", () => { + const manualSetIdentity = makeIdentity({ + advertising_token: "manual_set_advertising_token", + }); + beforeEach(() => { + uid2.setIdentity(manualSetIdentity); getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); - xhrMock.responseText = JSON.stringify({ status: "error" }); - xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } - }); - - test("getAdvertisingTokenPromise should reject", () => { - expect(expection).toEqual(new Error("UID2 SDK aborted.")); - }); + }); + + test("should abort the refreshing request", () => { + expect(xhrMock.abort).toHaveBeenCalledTimes(1); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: manualSetIdentity.advertising_token, + advertising_token: manualSetIdentity.advertising_token, + status: UID2.IdentityStatus.REFRESHED, + }) + ); + }); + test("should store value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + manualSetIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + manualSetIdentity.advertising_token + ); + }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); - }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("getAdvertisingTokenAsync should return manual set token", async () => { + expect(await getAdvertisingTokenPromise).toEqual( + manualSetIdentity.advertising_token + ); + }); }); }); - describe("when a new token is set using setIdentity", () => { - const manualSetIdentity = makeIdentity({ - advertising_token: "manual_set_advertising_token", + describe("when auto refreshing an expired identity", () => { + const refreshFrom = Date.now() + 100; + const originalIdentity = makeIdentity({ + advertising_token: "original_advertising_token", + identity_expires: refreshFrom, + refresh_from: refreshFrom, }); - beforeEach(() => { - uid2.setIdentity(manualSetIdentity); - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + const updatedIdentity = makeIdentity({ + advertising_token: "updated_advertising_token", }); - test("should abort the refreshing request", () => { - expect(xhrMock.abort).toHaveBeenCalledTimes(1); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: manualSetIdentity.advertising_token, - advertising_token: manualSetIdentity.advertising_token, - status: UID2.IdentityStatus.REFRESHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - manualSetIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - manualSetIdentity.advertising_token - ); - }); - - test("getAdvertisingTokenAsync should return manual set token", async () => { - expect(await getAdvertisingTokenPromise).toEqual( - manualSetIdentity.advertising_token - ); - }); - }); -}); - -describe("when auto refreshing an expired identity", () => { - const refreshFrom = Date.now() + 100; - const originalIdentity = makeIdentity({ - advertising_token: "original_advertising_token", - identity_expires: refreshFrom, - refresh_from: refreshFrom, - }); - const updatedIdentity = makeIdentity({ - advertising_token: "updated_advertising_token", - }); - - beforeEach(() => { - uid2.init({ callback: callback, identity: originalIdentity }); - jest.clearAllMocks(); - jest.setSystemTime(refreshFrom); - jest.runOnlyPendingTimers(); - }); - - test("should not invoke the callback", () => { - expect(callback).not.toHaveBeenCalled(); - }); - test("should initiate token refresh", () => { - expect(xhrMock.send).toHaveBeenCalledTimes(1); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInTemporarilyUnavailableState(); - }); - - describe("when token refresh succeeds", () => { beforeEach(() => { - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - xhrMock.responseText = btoa( - JSON.stringify({ status: "success", body: updatedIdentity }) - ); - xhrMock.onreadystatechange(new Event("")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: updatedIdentity.advertising_token, - advertising_token: updatedIdentity.advertising_token, - status: UID2.IdentityStatus.REFRESHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - updatedIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - updatedIdentity.advertising_token - ); - }); - test("getAdvertisingTokenPromise should return new advertising token", async () => { - expect(await getAdvertisingTokenPromise).toEqual( - updatedIdentity.advertising_token - ); + uid2.init({ callback: callback, identity: originalIdentity, useCookie: useCookie }); + jest.clearAllMocks(); + jest.setSystemTime(refreshFrom); + jest.runOnlyPendingTimers(); }); - }); - describe("when token refresh returns optout", () => { - let expection: any; - beforeEach(async () => { - try { - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); - xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } + test("should not invoke the callback", () => { + expect(callback).not.toHaveBeenCalled(); }); - test("getAdvertisingTokenPromise should reject", () => { - expect(expection).toEqual(new Error("UID2 SDK aborted.")); - }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.OPTOUT, - }) - ); - }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); + test("should initiate token refresh", () => { + expect(xhrMock.send).toHaveBeenCalledTimes(1); }); test("should not set refresh timer", () => { expect(setTimeout).not.toHaveBeenCalled(); expect(clearTimeout).not.toHaveBeenCalled(); }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should be in available state", () => { + (expect(uid2) as any).toBeInTemporarilyUnavailableState(); }); - }); - describe("when token refresh returns refresh token expired", () => { - let expection: any; - beforeEach(async () => { - try { + describe("when token refresh succeeds", () => { + beforeEach(() => { getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); xhrMock.responseText = btoa( - JSON.stringify({ status: "expired_token" }) + JSON.stringify({ status: "success", body: updatedIdentity }) ); xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } - }); - test("getAdvertisingTokenPromise should reject", () => { - expect(expection).toEqual(new Error("UID2 SDK aborted.")); - }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); - }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); - }); - }); - - describe("when token refresh returns an error status", () => { - let expection: any; - beforeEach(async () => { - try { - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - xhrMock.responseText = JSON.stringify({ - status: "error", - body: updatedIdentity, - }); - xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } - }); - test("getAdvertisingTokenPromise should reject", () => { - expect(expection).toEqual(new Error("No identity available.")); - }); - test("should not update cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in temporarily unavailable state", () => { - (expect(uid2) as any).toBeInTemporarilyUnavailableState( - originalIdentity.advertising_token - ); - }); - }); - - describe("when token refresh fails and current identity expires", () => { - let expection: any; - beforeEach(async () => { - try { - getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); - jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); - xhrMock.responseText = JSON.stringify({ status: "error" }); - xhrMock.onreadystatechange(new Event("")); - await getAdvertisingTokenPromise; - } catch (err) { - expection = err; - } - }); - - test("getAdvertisingTokenPromise should reject", () => { - expect(expection).toEqual(new Error("UID2 SDK aborted.")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); - }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: updatedIdentity.advertising_token, + advertising_token: updatedIdentity.advertising_token, + status: UID2.IdentityStatus.REFRESHED, + }) + ); + }); + test("should store value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + updatedIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + updatedIdentity.advertising_token + ); + }); + test("getAdvertisingTokenPromise should return new advertising token", async () => { + expect(await getAdvertisingTokenPromise).toEqual( + updatedIdentity.advertising_token + ); + }); + }); + + describe("when token refresh returns optout", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + test("getAdvertisingTokenPromise should reject", () => { + expect(exception).toEqual(new Error("UID2 SDK aborted.")); + }); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.OPTOUT, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); + }); + + describe("when token refresh returns refresh token expired", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + xhrMock.responseText = btoa( + JSON.stringify({ status: "expired_token" }) + ); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + test("getAdvertisingTokenPromise should reject", () => { + expect(exception).toEqual(new Error("UID2 SDK aborted.")); + }); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); + }); + + describe("when token refresh returns an error status", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + xhrMock.responseText = JSON.stringify({ + status: "error", + body: updatedIdentity, + }); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + test("getAdvertisingTokenPromise should reject", () => { + expect(exception).toEqual(new Error("No identity available.")); + }); + test("should not update value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in temporarily unavailable state", () => { + (expect(uid2) as any).toBeInTemporarilyUnavailableState( + originalIdentity.advertising_token + ); + }); + }); + + describe("when token refresh fails and current identity expires", () => { + let exception: any; + beforeEach(async () => { + try { + getAdvertisingTokenPromise = uid2.getAdvertisingTokenAsync(); + jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); + xhrMock.responseText = JSON.stringify({ status: "error" }); + xhrMock.onreadystatechange(new Event("")); + await getAdvertisingTokenPromise; + } catch (err) { + exception = err; + } + }); + + test("getAdvertisingTokenPromise should reject", () => { + expect(exception).toEqual(new Error("UID2 SDK aborted.")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); }); }); diff --git a/src/integrationTests/basic.test.ts b/src/integrationTests/basic.test.ts index 6e69fe2..2341bf5 100644 --- a/src/integrationTests/basic.test.ts +++ b/src/integrationTests/basic.test.ts @@ -21,933 +21,974 @@ beforeEach(() => { uid2 = new UID2(); xhrMock = new mocks.XhrMock(sdkWindow); mocks.setCookieMock(sdkWindow.document); + removeUid2Cookie(); + removeUid2LocalStorage(); }); afterEach(() => { mocks.resetFakeTime(); }); -const setUid2Cookie = mocks.setUid2Cookie; -const getUid2Cookie = mocks.getUid2Cookie; +const getUid2 = mocks.getUid2; +const setUid2 = mocks.setUid2; +const removeUid2Cookie = mocks.removeUid2Cookie; +const removeUid2LocalStorage = mocks.removeUid2LocalStorage; const makeIdentityV1 = mocks.makeIdentityV1; const makeIdentityV2 = mocks.makeIdentityV2; -describe("When google tag setup is called", () => { - test("should not fail when there is no googletag", () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - sdkWindow.googletag = null; - expect(() => UID2.setupGoogleTag()).not.toThrow(TypeError); - }); - test("should not fail when there is no googletag secureSignalProviders and no uid2SecureSignalProvider", () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - sdkWindow.googletag = { secureSignalProviders: null }; - expect(() => UID2.setupGoogleTag()).not.toThrow(TypeError); - }); +let useCookie: boolean | undefined = undefined; - test("should not fail when there is no uid2SecureSignalProvider", () => { - const mockPush = jest.fn(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - sdkWindow.googletag = { secureSignalProviders: { push: mockPush } }; - expect(() => UID2.setupGoogleTag()).not.toThrow(TypeError); - expect(mockPush.mock.calls.length).toBe(0); +const testCookieAndLocalStorage = (test: () => void, only = false) => { + const describeFn = only ? describe.only : describe; + describeFn('Using default: ', () => { + beforeEach(() => { + useCookie = undefined; + }); + test(); }); - - test("should push if googletag has secureSignalProviders", () => { - const mockRegister = jest.fn(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - sdkWindow.__uid2SecureSignalProvider = { - registerSecureSignalProvider: mockRegister, - }; - UID2.setupGoogleTag(); - expect(mockRegister.mock.calls.length).toBe(1); + describeFn('Using cookies ', () => { + beforeEach(() => { + useCookie = true; + }); + test(); }); -}); - -describe("initial state before init() is called", () => { - test("should be in initialising state", () => { - (expect(uid2) as any).toBeInInitialisingState(); + describeFn('Using local storage ', () => { + beforeEach(() => { + useCookie = false; + }); + test(); }); +}; - test("getAdvertisingToken should return undefined", () => { - expect(uid2.getAdvertisingToken()).toBeUndefined(); - }); -}); - -describe("when initialising with invalid options", () => { - test("should fail on no opts", () => { - expect(() => (uid2 as any).init()).toThrow(TypeError); - }); - test("should fail on opts not being an object", () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(() => uid2.init(12345)).toThrow(TypeError); - }); - test("should fail on opts being null", () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(() => uid2.init(null)).toThrow(TypeError); - }); - test("should work on no callback provided", () => { - expect(() => uid2.init({})).not.toThrow(TypeError); - }); - test("should fail on callback not being a function", () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(() => uid2.init({ callback: 12345 })).toThrow(TypeError); - }); - test("should fail on refreshRetryPeriod not being a number", () => { - expect(() => +testCookieAndLocalStorage(() => { + describe("When google tag setup is called", () => { + test("should not fail when there is no googletag", () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - uid2.init({ callback: () => {}, refreshRetryPeriod: "abc" }) - ).toThrow(TypeError); - }); - test("should fail on refreshRetryPeriod being less than 1 second", () => { - expect(() => - uid2.init({ callback: () => {}, refreshRetryPeriod: 1 }) - ).toThrow(RangeError); - }); -}); - -test("init() should fail if called multiple times", () => { - uid2.init({ callback: () => {} }); - expect(() => uid2.init({ callback: () => {} })).toThrow(); -}); - -describe("when initialised without identity", () => { - describe("when uid2 cookie is not available", () => { - beforeEach(() => { - uid2.init({ callback: callback }); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.NO_IDENTITY, - }) - ); - }); - test("should not set cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + sdkWindow.googletag = null; + expect(() => UID2.setupGoogleTag()).not.toThrow(TypeError); }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should not fail when there is no googletag secureSignalProviders and no uid2SecureSignalProvider", () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + sdkWindow.googletag = { secureSignalProviders: null }; + expect(() => UID2.setupGoogleTag()).not.toThrow(TypeError); }); - }); - describe("when uid2 cookie with invalid JSON is available", () => { - beforeEach(() => { - setUid2Cookie({}); - uid2.init({ callback: callback }); + test("should not fail when there is no uid2SecureSignalProvider", () => { + const mockPush = jest.fn(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + sdkWindow.googletag = { secureSignalProviders: { push: mockPush } }; + expect(() => UID2.setupGoogleTag()).not.toThrow(TypeError); + expect(mockPush.mock.calls.length).toBe(0); }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.NO_IDENTITY, - }) - ); - }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should push if googletag has secureSignalProviders", () => { + const mockRegister = jest.fn(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + sdkWindow.__uid2SecureSignalProvider = { + registerSecureSignalProvider: mockRegister, + }; + UID2.setupGoogleTag(); + expect(mockRegister.mock.calls.length).toBe(1); }); }); - describe("when uid2 cookie with up-to-date identity is available v2", () => { - const identity = makeIdentityV2(); - - beforeEach(() => { - setUid2Cookie(identity); - uid2.init({ callback: callback }); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: identity.advertising_token, - advertising_token: identity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - identity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + describe("initial state before init() is called", () => { + test("should be in initialising state", () => { + (expect(uid2) as any).toBeInInitialisingState(); }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState(identity.advertising_token); + + test("getAdvertisingToken should return undefined", () => { + expect(uid2.getAdvertisingToken()).toBeUndefined(); }); }); - describe("when uid2 cookie with expired refresh is available", () => { - const identity = makeIdentityV2({ - refresh_expires: Date.now() - 100000, + describe("when initialising with invalid options", () => { + test("should fail on no opts", () => { + expect(() => (uid2 as any).init()).toThrow(TypeError); }); - - beforeEach(() => { - setUid2Cookie(identity); - uid2.init({ callback: callback }); + test("should fail on opts not being an object", () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => uid2.init(12345)).toThrow(TypeError); }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); + test("should fail on opts being null", () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => uid2.init(null)).toThrow(TypeError); }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); + test("should work on no callback provided", () => { + expect(() => uid2.init({})).not.toThrow(TypeError); }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + test("should fail on callback not being a function", () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => uid2.init({ callback: 12345 })).toThrow(TypeError); }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should fail on refreshRetryPeriod not being a number", () => { + expect(() => + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + uid2.init({ callback: () => { }, refreshRetryPeriod: "abc" }) + ).toThrow(TypeError); + }); + test("should fail on refreshRetryPeriod being less than 1 second", () => { + expect(() => + uid2.init({ callback: () => { }, refreshRetryPeriod: 1 }) + ).toThrow(RangeError); }); }); - describe("when uid2 cookie with valid but refreshable identity is available", () => { - const identity = makeIdentityV2({ - refresh_from: Date.now() - 100000, - }); + test("init() should fail if called multiple times", () => { + uid2.init({ callback: () => { } }); + expect(() => uid2.init({ callback: () => { } })).toThrow(); + }); - beforeEach(() => { - setUid2Cookie(identity); - uid2.init({ callback: callback }); - }); + describe("when initialised without identity", () => { + describe("when uid2 value is not available", () => { + beforeEach(() => { + uid2.init({ callback: callback, useCookie: useCookie }); + }); - test("should initiate token refresh", () => { - expect(xhrMock.send).toHaveBeenCalledTimes(1); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState(); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.NO_IDENTITY, + }) + ); + }); + test("should not set value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - }); - describe("when uid2 v2 cookie with expired but refreshable identity is available", () => { - const identity = makeIdentityV2({ - identity_expires: Date.now() - 100000, - refresh_from: Date.now() - 100000, - }); + describe("when uid2 value with invalid JSON is available", () => { + beforeEach(() => { + setUid2({}, useCookie); + uid2.init({ callback: callback, useCookie: useCookie }); + }); - beforeEach(() => { - setUid2Cookie(identity); - uid2.init({ callback: callback }); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.NO_IDENTITY, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - test("should initiate token refresh", () => { - const cryptoMock = new mocks.CryptoMock(sdkWindow); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - const url = "https://prod.uidapi.com/v2/token/refresh"; - expect(xhrMock.open).toHaveBeenLastCalledWith("POST", url, true); - expect(xhrMock.send).toHaveBeenLastCalledWith(identity.refresh_token); - xhrMock.onreadystatechange(); - expect(cryptoMock.subtle.importKey).toHaveBeenCalled(); - }); + describe("when uid2 value with up-to-date identity is available v2", () => { + const identity = makeIdentityV2(); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in initialising state", () => { - (expect(uid2) as any).toBeInTemporarilyUnavailableState(); - }); - }); - describe("when uid2 v1 cookie with expired but refreshable identity is available", () => { - const identity = makeIdentityV1({ - identity_expires: Date.now() - 100000, - refresh_from: Date.now() - 100000, - }); + beforeEach(() => { + setUid2(identity, useCookie); + uid2.init({ callback: callback, useCookie: useCookie }); + }); - beforeEach(() => { - setUid2Cookie(identity); - uid2.init({ callback: callback }); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: identity.advertising_token, + advertising_token: identity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe(identity.advertising_token); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState(identity.advertising_token); + }); }); - test("should initiate token refresh", () => { - const cryptoMock = new mocks.CryptoMock(sdkWindow); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - const url = "https://prod.uidapi.com/v2/token/refresh"; - expect(xhrMock.open).toHaveBeenLastCalledWith("POST", url, true); - expect(xhrMock.send).toHaveBeenLastCalledWith(identity.refresh_token); - xhrMock.onreadystatechange(); - expect(cryptoMock.subtle.importKey).toHaveBeenCalledTimes(0); - }); - }); -}); + describe("when uid2 cookie with expired refresh is available", () => { + const identity = makeIdentityV2({ + refresh_expires: Date.now() - 100000, + }); -describe("when initialised with specific identity", () => { - describe("when invalid identity is supplied", () => { - beforeEach(() => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - uid2.init({ callback: callback, identity: {} }); - }); + beforeEach(() => { + setUid2(identity, useCookie); + uid2.init({ callback: callback, useCookie: useCookie }); + }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.INVALID, - }) - ); - }); - test("should clear cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - }); - describe("when valid v2 identity is supplied", () => { - const identity = makeIdentityV2(); + describe("when uid2 cookie with valid but refreshable identity is available", () => { + const identity = makeIdentityV2({ + refresh_from: Date.now() - 100000, + }); - beforeEach(() => { - uid2.init({ callback: callback, identity: identity }); - }); + beforeEach(() => { + setUid2(identity, useCookie); + uid2.init({ callback: callback, useCookie: useCookie }); + }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: identity.advertising_token, - advertising_token: identity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - identity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState(identity.advertising_token); + test("should initiate token refresh", () => { + expect(xhrMock.send).toHaveBeenCalledTimes(1); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState(); + }); }); - }); - describe("when valid identity is supplied and cookie is available", () => { - const initIdentity = makeIdentityV2({ - advertising_token: "init_advertising_token", - }); - const cookieIdentity = makeIdentityV2({ - advertising_token: "cookie_advertising_token", - }); + describe("when uid2 v2 cookie with expired but refreshable identity is available", () => { + const identity = makeIdentityV2({ + identity_expires: Date.now() - 100000, + refresh_from: Date.now() - 100000, + }); + let cryptoMock: any; - beforeEach(() => { - setUid2Cookie(cookieIdentity); - uid2.init({ callback: callback, identity: initIdentity }); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: initIdentity.advertising_token, - advertising_token: initIdentity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - initIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - initIdentity.advertising_token - ); - }); - }); -}); -describe("when still valid identity is refreshed on init", () => { - const originalIdentity = makeIdentityV2({ - advertising_token: "original_advertising_token", - refresh_from: Date.now() - 100000, - }); - const updatedIdentity = makeIdentityV2({ - advertising_token: "updated_advertising_token", - }); + beforeEach(() => { + xhrMock.open.mockClear(); + xhrMock.send.mockClear(); + cryptoMock = new mocks.CryptoMock(sdkWindow); + setUid2(identity, useCookie); + uid2.init({ callback: callback, useCookie: useCookie }); + }); - beforeEach(() => { - uid2.init({ callback: callback, identity: originalIdentity }); - }); + afterEach(() => { + xhrMock.open.mockClear(); + xhrMock.send.mockClear(); + cryptoMock.subtle.importKey.mockClear(); + }); - describe("when token refresh succeeds", () => { - beforeEach(() => { - xhrMock.responseText = btoa( - JSON.stringify({ status: "success", body: updatedIdentity }) - ); - xhrMock.onreadystatechange(new Event("")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenLastCalledWith( - expect.objectContaining({ - advertisingToken: updatedIdentity.advertising_token, - advertising_token: updatedIdentity.advertising_token, - status: UID2.IdentityStatus.REFRESHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - updatedIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - updatedIdentity.advertising_token - ); - }); - }); + test("should initiate token refresh", () => { + expect(xhrMock.send).toHaveBeenCalledTimes(1); + const url = "https://prod.uidapi.com/v2/token/refresh"; + expect(xhrMock.open).toHaveBeenLastCalledWith("POST", url, true); + expect(xhrMock.send).toHaveBeenLastCalledWith(identity.refresh_token); + xhrMock.onreadystatechange(); + expect(cryptoMock.subtle.importKey).toHaveBeenCalled(); + }); - describe("when token refresh returns invalid response", () => { - beforeEach(() => { - xhrMock.responseText = "abc"; - xhrMock.onreadystatechange(new Event("")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: originalIdentity.advertising_token, - advertising_token: originalIdentity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - originalIdentity.advertising_token - ); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in initialising state", () => { + (expect(uid2) as any).toBeInTemporarilyUnavailableState(); + }); }); - }); + describe("when uid2 v1 cookie with expired but refreshable identity is available", () => { + const identity = makeIdentityV1({ + identity_expires: Date.now() - 100000, + refresh_from: Date.now() - 100000, + }); - describe("when token refresh returns optout", () => { - beforeEach(() => { - xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); - xhrMock.onreadystatechange(new Event("")); - }); + beforeEach(() => { + setUid2(identity, useCookie); + uid2.init({ callback: callback, useCookie: useCookie }); + }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenLastCalledWith( - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.OPTOUT, - }) - ); - }); - test("should not set cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should initiate token refresh", () => { + const cryptoMock = new mocks.CryptoMock(sdkWindow); + expect(xhrMock.send).toHaveBeenCalledTimes(1); + const url = "https://prod.uidapi.com/v2/token/refresh"; + expect(xhrMock.open).toHaveBeenLastCalledWith("POST", url, true); + expect(xhrMock.send).toHaveBeenLastCalledWith(identity.refresh_token); + xhrMock.onreadystatechange(); + expect(cryptoMock.subtle.importKey).toHaveBeenCalledTimes(0); + }); }); }); - describe("when token refresh returns expired token", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ status: "expired_token" }); - xhrMock.status = 400; - xhrMock.onreadystatechange(new Event("")); - }); + describe("when initialised with specific identity", () => { + describe("when invalid identity is supplied", () => { + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + uid2.init({ callback: callback, identity: {}, useCookie: useCookie }); + }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenLastCalledWith( - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); - }); - test("should not set cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.INVALID, + }) + ); + }); + test("should clear value", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - }); - describe("when token refresh returns an error status", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ - status: "error", - body: updatedIdentity, + describe("when valid v2 identity is supplied", () => { + const identity = makeIdentityV2(); + + beforeEach(() => { + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); }); - xhrMock.onreadystatechange(new Event("")); - }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: originalIdentity.advertising_token, - advertising_token: originalIdentity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: identity.advertising_token, + advertising_token: identity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + identity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState(identity.advertising_token); + }); }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - originalIdentity.advertising_token - ); + + describe("when valid identity is supplied and existing value is available", () => { + const initIdentity = makeIdentityV2({ + advertising_token: "init_advertising_token", + }); + const existingIdentity = makeIdentityV2({ + advertising_token: "existing_advertising_token", + }); + + beforeEach(() => { + setUid2(existingIdentity, useCookie); + uid2.init({ callback: callback, identity: initIdentity, useCookie: useCookie }); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: initIdentity.advertising_token, + advertising_token: initIdentity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + initIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + initIdentity.advertising_token + ); + }); }); }); - describe("when token refresh returns no body", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ status: "success" }); - xhrMock.onreadystatechange(new Event("")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: originalIdentity.advertising_token, - advertising_token: originalIdentity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + describe("when still valid identity is refreshed on init", () => { + const originalIdentity = makeIdentityV2({ + advertising_token: "original_advertising_token", + refresh_from: Date.now() - 100000, }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - originalIdentity.advertising_token - ); + const updatedIdentity = makeIdentityV2({ + advertising_token: "updated_advertising_token", }); - }); - describe("when token refresh returns incorrect body type", () => { beforeEach(() => { - xhrMock.responseText = JSON.stringify({ status: "success", body: 5 }); - xhrMock.onreadystatechange(new Event("")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: originalIdentity.advertising_token, - advertising_token: originalIdentity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + uid2.init({ callback: callback, identity: originalIdentity, useCookie: useCookie }); }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - originalIdentity.advertising_token - ); + + describe("when token refresh succeeds", () => { + beforeEach(() => { + xhrMock.responseText = btoa( + JSON.stringify({ status: "success", body: updatedIdentity }) + ); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenLastCalledWith( + expect.objectContaining({ + advertisingToken: updatedIdentity.advertising_token, + advertising_token: updatedIdentity.advertising_token, + status: UID2.IdentityStatus.REFRESHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + updatedIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + updatedIdentity.advertising_token + ); + }); }); - }); - describe("when token refresh returns invalid body", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ status: "success", body: {} }); - xhrMock.onreadystatechange(new Event("")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: originalIdentity.advertising_token, - advertising_token: originalIdentity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + describe("when token refresh returns invalid response", () => { + beforeEach(() => { + xhrMock.responseText = "abc"; + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: originalIdentity.advertising_token, + advertising_token: originalIdentity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + originalIdentity.advertising_token + ); + }); }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - originalIdentity.advertising_token - ); + + describe("when token refresh returns optout", () => { + beforeEach(() => { + xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenLastCalledWith( + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.OPTOUT, + }) + ); + }); + test("should not set cookie", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - }); - describe("when token refresh fails and current identity expires", () => { - beforeEach(() => { - jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); - xhrMock.responseText = JSON.stringify({ status: "error" }); - xhrMock.onreadystatechange(new Event("")); + describe("when token refresh returns expired token", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ status: "expired_token" }); + xhrMock.status = 400; + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenLastCalledWith( + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should not set cookie", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenLastCalledWith( - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); + describe("when token refresh returns an error status", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ + status: "error", + body: updatedIdentity, + }); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: originalIdentity.advertising_token, + advertising_token: originalIdentity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + originalIdentity.advertising_token + ); + }); }); - test("should not set cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); + + describe("when token refresh returns no body", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ status: "success" }); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: originalIdentity.advertising_token, + advertising_token: originalIdentity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + originalIdentity.advertising_token + ); + }); }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + + describe("when token refresh returns incorrect body type", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ status: "success", body: 5 }); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: originalIdentity.advertising_token, + advertising_token: originalIdentity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + originalIdentity.advertising_token + ); + }); }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + + describe("when token refresh returns invalid body", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ status: "success", body: {} }); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: originalIdentity.advertising_token, + advertising_token: originalIdentity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + originalIdentity.advertising_token + ); + }); }); - }); -}); -describe("when expired identity is refreshed on init", () => { - const originalIdentity = makeIdentityV2({ - advertising_token: "original_advertising_token", - refresh_from: Date.now() - 100000, - identity_expires: Date.now() - 1, - }); - const updatedIdentity = makeIdentityV2({ - advertising_token: "updated_advertising_token", - }); + describe("when token refresh fails and current identity expires", () => { + beforeEach(() => { + jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); + xhrMock.responseText = JSON.stringify({ status: "error" }); + xhrMock.onreadystatechange(new Event("")); + }); - beforeEach(() => { - uid2.init({ callback: callback, identity: originalIdentity }); + test("should invoke the callback", () => { + expect(callback).toHaveBeenLastCalledWith( + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should not set cookie", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); + }); }); - describe("when token refresh succeeds", () => { - beforeEach(() => { - xhrMock.responseText = btoa( - JSON.stringify({ status: "success", body: updatedIdentity }) - ); - xhrMock.onreadystatechange(new Event("")); - }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - advertisingToken: updatedIdentity.advertising_token, - advertising_token: updatedIdentity.advertising_token, - status: UID2.IdentityStatus.REFRESHED, - }) - ); - }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - updatedIdentity.advertising_token - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + describe("when expired identity is refreshed on init", () => { + const originalIdentity = makeIdentityV2({ + advertising_token: "original_advertising_token", + refresh_from: Date.now() - 100000, + identity_expires: Date.now() - 1, }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - updatedIdentity.advertising_token - ); + const updatedIdentity = makeIdentityV2({ + advertising_token: "updated_advertising_token", }); - }); - describe("when token refresh returns optout", () => { beforeEach(() => { - xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); - xhrMock.onreadystatechange(new Event("")); + uid2.init({ callback: callback, identity: originalIdentity, useCookie: useCookie }); }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenLastCalledWith( - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.OPTOUT, - }) - ); - }); - test("should not set cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); - }); - }); + describe("when token refresh succeeds", () => { + beforeEach(() => { + xhrMock.responseText = btoa( + JSON.stringify({ status: "success", body: updatedIdentity }) + ); + xhrMock.onreadystatechange(new Event("")); + }); - describe("when token refresh returns expired token", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ status: "expired_token" }); - xhrMock.status = 400; - xhrMock.onreadystatechange(new Event("")); + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + advertisingToken: updatedIdentity.advertising_token, + advertising_token: updatedIdentity.advertising_token, + status: UID2.IdentityStatus.REFRESHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + updatedIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + updatedIdentity.advertising_token + ); + }); }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); - }); - test("should not set cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + describe("when token refresh returns optout", () => { + beforeEach(() => { + xhrMock.responseText = btoa(JSON.stringify({ status: "optout" })); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenLastCalledWith( + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.OPTOUT, + }) + ); + }); + test("should not set cookie", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - test("should be in unavailable state", () => { - (expect(uid2) as any).toBeInUnavailableState(); + + describe("when token refresh returns expired token", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ status: "expired_token" }); + xhrMock.status = 400; + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should not set cookie", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - }); - describe("when token refresh returns an error status", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ - status: "error", - body: updatedIdentity, + describe("when token refresh returns an error status", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ + status: "error", + body: updatedIdentity, + }); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.EXPIRED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe( + originalIdentity.advertising_token + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in temporarily unavailable state", () => { + (expect(uid2) as any).toBeInTemporarilyUnavailableState(); }); - xhrMock.onreadystatechange(new Event("")); }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.EXPIRED, - }) - ); + describe("when token refresh fails and current identity expires", () => { + beforeEach(() => { + jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); + xhrMock.responseText = JSON.stringify({ status: "error" }); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + advertisingToken: undefined, + advertising_token: undefined, + status: UID2.IdentityStatus.REFRESH_EXPIRED, + }) + ); + }); + test("should not set cookie", () => { + expect(getUid2(useCookie)).toBeNull(); + }); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in unavailable state", () => { + (expect(uid2) as any).toBeInUnavailableState(); + }); }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - originalIdentity.advertising_token - ); + }); + + describe("abort()", () => { + test("should not clear cookie", () => { + const identity = makeIdentityV2(); + setUid2(identity, useCookie); + uid2.abort(); + expect(getUid2(useCookie).advertising_token).toBe(identity.advertising_token); }); - test("should set refresh timer", () => { + test("should abort refresh timer", () => { + uid2.init({ callback: callback, identity: makeIdentityV2(), useCookie: useCookie }); expect(setTimeout).toHaveBeenCalledTimes(1); expect(clearTimeout).not.toHaveBeenCalled(); + uid2.abort(); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).toHaveBeenCalledTimes(1); + }); + test("should not abort refresh timer if not timer is set", () => { + uid2.init({ + callback: callback, + identity: makeIdentityV2({ refresh_from: Date.now() - 100000 }), + useCookie: useCookie + }); + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + uid2.abort(); + expect(setTimeout).not.toHaveBeenCalled(); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should abort refresh token request", () => { + uid2.init({ + callback: callback, + identity: makeIdentityV2({ refresh_from: Date.now() - 100000 }), + useCookie: useCookie + }); + expect(xhrMock.send).toHaveBeenCalledTimes(1); + expect(xhrMock.abort).not.toHaveBeenCalled(); + uid2.abort(); + expect(xhrMock.send).toHaveBeenCalledTimes(1); + expect(xhrMock.abort).toHaveBeenCalledTimes(1); }); - test("should be in temporarily unavailable state", () => { - (expect(uid2) as any).toBeInTemporarilyUnavailableState(); + test("should prevent subsequent calls to init()", () => { + uid2.abort(); + expect(() => uid2.init({ callback: () => { } })).toThrow(); }); }); - describe("when token refresh fails and current identity expires", () => { - beforeEach(() => { - jest.setSystemTime(originalIdentity.refresh_expires * 1000 + 1); - xhrMock.responseText = JSON.stringify({ status: "error" }); - xhrMock.onreadystatechange(new Event("")); + describe("disconnect()", () => { + test("should clear cookie", () => { + setUid2(makeIdentityV2(), useCookie); + uid2.disconnect(); + expect(getUid2(useCookie)).toBeNull(); }); - - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - advertisingToken: undefined, - advertising_token: undefined, - status: UID2.IdentityStatus.REFRESH_EXPIRED, - }) - ); + test("should abort refresh timer", () => { + uid2.init({ callback: callback, identity: makeIdentityV2(), useCookie: useCookie }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + uid2.disconnect(); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).toHaveBeenCalledTimes(1); }); - test("should not set cookie", () => { - expect(getUid2Cookie()).toBeUndefined(); + test("should abort refresh token request", () => { + uid2.init({ + callback: callback, + identity: makeIdentityV2({ refresh_from: Date.now() - 100000 }), + useCookie: useCookie + }); + expect(xhrMock.send).toHaveBeenCalledTimes(1); + expect(xhrMock.abort).not.toHaveBeenCalled(); + uid2.disconnect(); + expect(xhrMock.send).toHaveBeenCalledTimes(1); + expect(xhrMock.abort).toHaveBeenCalledTimes(1); }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + test("should prevent subsequent calls to init()", () => { + uid2.disconnect(); + expect(() => uid2.init({ callback: () => { } })).toThrow(); }); - test("should be in unavailable state", () => { + test("should switch to unavailable state", () => { + uid2.init({ callback: callback, identity: makeIdentityV2(), useCookie: useCookie }); + uid2.disconnect(); (expect(uid2) as any).toBeInUnavailableState(); }); }); }); - -describe("abort()", () => { - test("should not clear cookie", () => { - const identity = makeIdentityV2(); - setUid2Cookie(identity); - uid2.abort(); - expect(getUid2Cookie().advertising_token).toBe(identity.advertising_token); - }); - test("should abort refresh timer", () => { - uid2.init({ callback: callback, identity: makeIdentityV2() }); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - uid2.abort(); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).toHaveBeenCalledTimes(1); - }); - test("should not abort refresh timer if not timer is set", () => { - uid2.init({ - callback: callback, - identity: makeIdentityV2({ refresh_from: Date.now() - 100000 }), - }); - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - uid2.abort(); - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - }); - test("should abort refresh token request", () => { - uid2.init({ - callback: callback, - identity: makeIdentityV2({ refresh_from: Date.now() - 100000 }), - }); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - expect(xhrMock.abort).not.toHaveBeenCalled(); - uid2.abort(); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - expect(xhrMock.abort).toHaveBeenCalledTimes(1); - }); - test("should prevent subsequent calls to init()", () => { - uid2.abort(); - expect(() => uid2.init({ callback: () => {} })).toThrow(); - }); -}); - -describe("disconnect()", () => { - test("should clear cookie", () => { - setUid2Cookie(makeIdentityV2()); - uid2.disconnect(); - expect(getUid2Cookie()).toBeUndefined(); - }); - test("should abort refresh timer", () => { - uid2.init({ callback: callback, identity: makeIdentityV2() }); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); - uid2.disconnect(); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).toHaveBeenCalledTimes(1); - }); - test("should abort refresh token request", () => { - uid2.init({ - callback: callback, - identity: makeIdentityV2({ refresh_from: Date.now() - 100000 }), - }); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - expect(xhrMock.abort).not.toHaveBeenCalled(); - uid2.disconnect(); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - expect(xhrMock.abort).toHaveBeenCalledTimes(1); - }); - test("should prevent subsequent calls to init()", () => { - uid2.disconnect(); - expect(() => uid2.init({ callback: () => {} })).toThrow(); - }); - test("should switch to unavailable state", () => { - uid2.init({ callback: callback, identity: makeIdentityV2() }); - uid2.disconnect(); - (expect(uid2) as any).toBeInUnavailableState(); - }); -}); diff --git a/src/integrationTests/callbacks.test.ts b/src/integrationTests/callbacks.test.ts index b642684..7f4b3e4 100644 --- a/src/integrationTests/callbacks.test.ts +++ b/src/integrationTests/callbacks.test.ts @@ -47,100 +47,126 @@ afterEach(() => { 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 init is called", () => { - test("it should be called at the end of init", () => { - uid2.callbacks.push(asyncCallback); - const calls = asyncCallback.mock.calls.length; - uid2.init({ callback: callback, identity: identity }); - expect(asyncCallback.mock.calls.length).toBe(calls + 1); +let useCookie: boolean | undefined = undefined; + +const testCookieAndLocalStorage = (test: () => void, only = false) => { + const describeFn = only ? describe.only : describe; + describeFn('Using default: ', () => { + beforeEach(() => { + useCookie = undefined; }); - test("it should be called with a 'successful init' message", () => { - uid2.callbacks.push(asyncCallback); - uid2.init({ callback: callback, identity: identity }); - expect(asyncCallback.mock.calls.slice(-1)[0][0]).toBe( - UID2.EventType.InitCompleted - ); + test(); + }); + describeFn('Using cookies ', () => { + beforeEach(() => { + useCookie = true; }); - test("it should be provided with the loaded identity", () => { - uid2.callbacks.push(asyncCallback); - uid2.init({ callback: callback, identity: identity }); - expect(asyncCallback.mock.calls.slice(-1)[0][1]).toMatchObject({ - identity, - }); + test(); + }); + describeFn('Using local storage ', () => { + beforeEach(() => { + useCookie = false; }); + test(); }); - - describe("after init is called", () => { - test("it should be called with SdkLoaded and InitComplete immediately", () => { - uid2.init({ callback: callback, identity: identity }); - expect(asyncCallback.mock.calls.length).toBe(0); - uid2.callbacks.push(asyncCallback); - expect(asyncCallback.mock.calls.length).toBe(2); - expect(asyncCallback.mock.calls[0][0]).toBe(UID2.EventType.SdkLoaded); - expect(asyncCallback.mock.calls[1][0]).toBe(UID2.EventType.InitCompleted); +}; + +testCookieAndLocalStorage(() => { + 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 init is called", () => { + test("it should be called at the end of init", () => { + uid2.callbacks.push(asyncCallback); + const calls = asyncCallback.mock.calls.length; + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + expect(asyncCallback.mock.calls.length).toBe(calls + 1); + }); + test("it should be called with a 'successful init' message", () => { + uid2.callbacks.push(asyncCallback); + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + expect(asyncCallback.mock.calls.slice(-1)[0][0]).toBe( + UID2.EventType.InitCompleted + ); + }); + test("it should be provided with the loaded identity", () => { + uid2.callbacks.push(asyncCallback); + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + expect(asyncCallback.mock.calls.slice(-1)[0][1]).toMatchObject({ + identity, + }); + }); }); - test("it should be provided with the loaded identity", () => { - uid2.init({ callback: callback, identity: identity }); - uid2.callbacks.push(asyncCallback); - - expect(asyncCallback.mock.calls.slice(-1)[0][1]).toMatchObject({ - identity, + describe("after init is called", () => { + test("it should be called with SdkLoaded and InitComplete immediately", () => { + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + expect(asyncCallback.mock.calls.length).toBe(0); + uid2.callbacks.push(asyncCallback); + expect(asyncCallback.mock.calls.length).toBe(2); + expect(asyncCallback.mock.calls[0][0]).toBe(UID2.EventType.SdkLoaded); + expect(asyncCallback.mock.calls[1][0]).toBe(UID2.EventType.InitCompleted); }); - }); - test("it should receive subsequent identity updates", async () => { - uid2.init({ callback: callback, identity: identity }); - uid2.callbacks.push(asyncCallback); - - const callsBeforeRefresh = asyncCallback.mock.calls.length; - jest.setSystemTime(refreshFrom); - jest.runOnlyPendingTimers(); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - xhrMock.sendRefreshApiResponse(refreshedIdentity); - await mocks.flushPromises(); - - expect(asyncCallback.mock.calls.length).toBe(callsBeforeRefresh + 1); - expect(asyncCallback.mock.calls[callsBeforeRefresh][0]).toBe( - UID2.EventType.IdentityUpdated - ); - expect(asyncCallback.mock.calls[callsBeforeRefresh][1]).toMatchObject({ - identity: refreshedIdentity, + test("it should be provided with the loaded identity", () => { + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + uid2.callbacks.push(asyncCallback); + + expect(asyncCallback.mock.calls.slice(-1)[0][1]).toMatchObject({ + identity, + }); + }); + // this test is only passing for cookies + test("it should receive subsequent identity updates", async () => { + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + uid2.callbacks.push(asyncCallback); + + const callsBeforeRefresh = asyncCallback.mock.calls.length; + jest.setSystemTime(refreshFrom); + jest.runOnlyPendingTimers(); + expect(xhrMock.send).toHaveBeenCalledTimes(1); + xhrMock.sendRefreshApiResponse(refreshedIdentity); + await mocks.flushPromises(); + + expect(asyncCallback.mock.calls.length).toBe(callsBeforeRefresh + 1); + expect(asyncCallback.mock.calls[callsBeforeRefresh][0]).toBe( + UID2.EventType.IdentityUpdated + ); + expect(asyncCallback.mock.calls[callsBeforeRefresh][1]).toMatchObject({ + identity: refreshedIdentity, + }); }); - }); - test("it should receive a null identity update if opt-out is called", () => { - uid2.init({ callback: callback, identity: identity }); - uid2.callbacks.push(asyncCallback); - const callsBeforeRefresh = asyncCallback.mock.calls.length; + test("it should receive a null identity update if opt-out is called", () => { + uid2.init({ callback: callback, identity: identity, useCookie: useCookie }); + uid2.callbacks.push(asyncCallback); + const callsBeforeRefresh = asyncCallback.mock.calls.length; - uid2.disconnect(); + uid2.disconnect(); - expect(asyncCallback.mock.calls.length).toBe(callsBeforeRefresh + 1); - expect(asyncCallback.mock.calls[callsBeforeRefresh][0]).toBe( - UID2.EventType.IdentityUpdated - ); - expect(asyncCallback.mock.calls[callsBeforeRefresh][1]).toMatchObject({ - identity: null, + expect(asyncCallback.mock.calls.length).toBe(callsBeforeRefresh + 1); + expect(asyncCallback.mock.calls[callsBeforeRefresh][0]).toBe( + UID2.EventType.IdentityUpdated + ); + expect(asyncCallback.mock.calls[callsBeforeRefresh][1]).toMatchObject({ + identity: null, + }); }); - }); - test("it should receive identity updates when set identity is called", () => { - uid2.init({ callback: callback }); - uid2.callbacks.push(asyncCallback); - const callsBeforeSetIdentity= asyncCallback.mock.calls.length; - uid2.setIdentity(identity) + test("it should receive identity updates when set identity is called", () => { + uid2.init({ callback: callback, useCookie: useCookie }); + uid2.callbacks.push(asyncCallback); + const callsBeforeSetIdentity = asyncCallback.mock.calls.length; + uid2.setIdentity(identity); - expect(asyncCallback.mock.calls.length).toBe(callsBeforeSetIdentity+1); - expect(asyncCallback.mock.calls[callsBeforeSetIdentity][0]).toBe(UID2.EventType.IdentityUpdated); - expect(asyncCallback.mock.calls[callsBeforeSetIdentity][1]).toMatchObject({ identity: identity }); + expect(asyncCallback.mock.calls.length).toBe(callsBeforeSetIdentity + 1); + expect(asyncCallback.mock.calls[callsBeforeSetIdentity][0]).toBe(UID2.EventType.IdentityUpdated); + expect(asyncCallback.mock.calls[callsBeforeSetIdentity][1]).toMatchObject({ identity: identity }); + }); }); }); }); diff --git a/src/integrationTests/compatibility.test.ts b/src/integrationTests/compatibility.test.ts index e279c64..c9d76d2 100644 --- a/src/integrationTests/compatibility.test.ts +++ b/src/integrationTests/compatibility.test.ts @@ -28,105 +28,130 @@ afterEach(() => { }); const setUid2Cookie = mocks.setUid2Cookie; -const getUid2Cookie = mocks.getUid2Cookie; +const getUid2 = mocks.getUid2; const makeIdentity = mocks.makeIdentityV2; -describe("when a v0 cookie is available", () => { - const originalIdentity = { - advertising_token: "original_advertising_token", - refresh_token: "original_refresh_token", - }; - const updatedIdentity = makeIdentity({ - advertising_token: "updated_advertising_token", - }); - - beforeEach(() => { - setUid2Cookie(originalIdentity); - uid2.init({ callback: callback }); - }); +let useCookie: boolean | undefined = undefined; - test("should initiate token refresh", () => { - expect(xhrMock.send).toHaveBeenCalledTimes(1); +const testCookieAndLocalStorage = (test: () => void, only = false) => { + const describeFn = only ? describe.only : describe; + describeFn('Using default: ', () => { + beforeEach(() => { + useCookie = undefined; + }); + test(); }); - test("should not set refresh timer", () => { - expect(setTimeout).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); + describeFn('Using cookies ', () => { + beforeEach(() => { + useCookie = true; + }); + test(); }); - test("should be in initialising state", () => { - (expect(uid2) as any).toBeInAvailableState(); + describeFn('Using local storage ', () => { + beforeEach(() => { + useCookie = false; + }); + test(); }); +}; - describe("when token refresh succeeds", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ - status: "success", - body: updatedIdentity, - }); - xhrMock.onreadystatechange(new Event("")); +testCookieAndLocalStorage(() => { + describe("when a v0 cookie is available", () => { + const originalIdentity = { + advertising_token: "original_advertising_token", + refresh_token: "original_refresh_token", + }; + const updatedIdentity = makeIdentity({ + advertising_token: "updated_advertising_token", }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenLastCalledWith( - expect.objectContaining({ - advertising_token: updatedIdentity.advertising_token, - status: UID2.IdentityStatus.REFRESHED, - }) - ); + beforeEach(() => { + setUid2Cookie(originalIdentity); + uid2.init({ callback: callback, useCookie: useCookie }); }); - test("should set cookie", () => { - expect(getUid2Cookie().advertising_token).toBe( - updatedIdentity.advertising_token - ); + + test("should initiate token refresh", () => { + expect(xhrMock.send).toHaveBeenCalledTimes(1); }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); + test("should not set refresh timer", () => { + expect(setTimeout).not.toHaveBeenCalled(); expect(clearTimeout).not.toHaveBeenCalled(); }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - updatedIdentity.advertising_token - ); + test("should be in initialising state", () => { + (expect(uid2) as any).toBeInAvailableState(); }); - }); - describe("when token refresh returns an error status", () => { - beforeEach(() => { - xhrMock.responseText = JSON.stringify({ - status: "error", - body: updatedIdentity, + describe("when token refresh succeeds", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ + status: "success", + body: updatedIdentity, + }); + xhrMock.onreadystatechange(new Event("")); }); - xhrMock.onreadystatechange(new Event("")); - }); - test("should invoke the callback", () => { - expect(callback).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - advertisingToken: originalIdentity.advertising_token, - advertising_token: originalIdentity.advertising_token, - status: UID2.IdentityStatus.ESTABLISHED, - }) - ); - }); - test("should set enriched cookie", () => { - expect(getUid2Cookie().refresh_token).toBe( - originalIdentity.refresh_token - ); - expect(getUid2Cookie().refresh_from).toBe(Date.now()); - expect(getUid2Cookie().identity_expires).toBeGreaterThan(Date.now()); - expect(getUid2Cookie().refresh_expires).toBeGreaterThan(Date.now()); - expect(getUid2Cookie().identity_expires).toBeLessThan( - getUid2Cookie().refresh_expires - ); - }); - test("should set refresh timer", () => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(clearTimeout).not.toHaveBeenCalled(); + test("should invoke the callback", () => { + expect(callback).toHaveBeenLastCalledWith( + expect.objectContaining({ + advertising_token: updatedIdentity.advertising_token, + status: UID2.IdentityStatus.REFRESHED, + }) + ); + }); + test("should set value", () => { + expect(getUid2(useCookie).advertising_token).toBe(updatedIdentity.advertising_token); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + updatedIdentity.advertising_token + ); + }); }); - test("should be in available state", () => { - (expect(uid2) as any).toBeInAvailableState( - originalIdentity.advertising_token - ); + + describe("when token refresh returns an error status", () => { + beforeEach(() => { + xhrMock.responseText = JSON.stringify({ + status: "error", + body: updatedIdentity, + }); + xhrMock.onreadystatechange(new Event("")); + }); + + test("should invoke the callback", () => { + expect(callback).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + advertisingToken: originalIdentity.advertising_token, + advertising_token: originalIdentity.advertising_token, + status: UID2.IdentityStatus.ESTABLISHED, + }) + ); + }); + test("should set enriched value", () => { + const value = getUid2(useCookie); + expect(value.refresh_token).toBe( + originalIdentity.refresh_token + ); + expect(value.refresh_from).toBe(Date.now()); + expect(value.identity_expires).toBeGreaterThan(Date.now()); + expect(value.refresh_expires).toBeGreaterThan(Date.now()); + expect(value.identity_expires).toBeLessThan( + value.refresh_expires + ); + }); + test("should set refresh timer", () => { + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(clearTimeout).not.toHaveBeenCalled(); + }); + test("should be in available state", () => { + (expect(uid2) as any).toBeInAvailableState( + originalIdentity.advertising_token + ); + }); }); }); }); diff --git a/src/integrationTests/options.test.ts b/src/integrationTests/options.test.ts index 40e1041..ffc196c 100644 --- a/src/integrationTests/options.test.ts +++ b/src/integrationTests/options.test.ts @@ -26,6 +26,8 @@ beforeEach(() => { xhrMock = new mocks.XhrMock(sdkWindow); jest.spyOn(document, "URL", "get").mockImplementation(() => mockUrl); cookieMock = new mocks.CookieMock(sdkWindow.document); + removeUid2Cookie(); + removeUid2LocalStorage(); }); afterEach(() => { @@ -33,11 +35,15 @@ afterEach(() => { }); const makeIdentity = mocks.makeIdentityV2; +const getUid2Cookie = mocks.getUid2Cookie; +const getUid2LocalStorage = mocks.getUid2LocalStorage; +const removeUid2Cookie = mocks.removeUid2Cookie; +const removeUid2LocalStorage = mocks.removeUid2LocalStorage; describe("cookieDomain option", () => { describe("when using default value", () => { beforeEach(() => { - uid2.init({ callback: callback, identity: makeIdentity() }); + uid2.init({ callback: callback, identity: makeIdentity(), useCookie: true }); }); test("should not mention domain in the cookie string", () => { @@ -55,6 +61,7 @@ describe("cookieDomain option", () => { callback: callback, identity: makeIdentity(), cookieDomain: domain, + useCookie: true }); }); @@ -68,7 +75,7 @@ describe("cookieDomain option", () => { describe("cookiePath option", () => { describe("when using default value", () => { beforeEach(() => { - uid2.init({ callback: callback, identity: makeIdentity() }); + uid2.init({ callback: callback, identity: makeIdentity(), useCookie: true }); }); test("should use the default path in the cookie string", () => { @@ -85,6 +92,7 @@ describe("cookiePath option", () => { callback: callback, identity: makeIdentity(), cookiePath: path, + useCookie: true }); }); @@ -156,3 +164,34 @@ describe("refreshRetryPeriod option", () => { }); }); }); + +describe("useCookie option", () => { + const identity = makeIdentity(); + + describe("when using default value", () => { + beforeEach(() => { + uid2.init({ callback: callback, identity: identity }); + }); + test("should set identity in local storage", () => { + expect(getUid2LocalStorage().advertising_token).toBe(identity.advertising_token); + }); + }); + describe("when useCookie is false", () => { + beforeEach(() => { + uid2.init({ callback: callback, identity: identity, useCookie: false }); + }); + test("should set identity in local storage only", () => { + expect(getUid2LocalStorage().advertising_token).toBe(identity.advertising_token); + expect(getUid2Cookie()).toBeNull(); + }); + }); + describe("when useCookie is true", () => { + beforeEach(() => { + uid2.init({ callback: callback, identity: identity, useCookie: true }); + }); + test("should set identity in cookie only", () => { + expect(getUid2Cookie().advertising_token).toBe(identity.advertising_token); + expect(getUid2LocalStorage()).toBeNull(); + }); + }); +}); diff --git a/src/integrationTests/secureSignal.test.ts b/src/integrationTests/secureSignal.test.ts index f169a81..34a22ea 100644 --- a/src/integrationTests/secureSignal.test.ts +++ b/src/integrationTests/secureSignal.test.ts @@ -65,7 +65,7 @@ describe("Secure Signal Tests", () => { }); }); - describe("when getUid2AdvertisingToken is not definied", () => { + describe("when getUid2AdvertisingToken is not defined", () => { test("should not send signal to ESP", () => { uid2ESP = new Uid2SecureSignalProvider(); expect(secureSignalProvidersPushMock).not.toBeCalled(); diff --git a/src/mocks.ts b/src/mocks.ts index 546961c..46b788a 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -2,6 +2,7 @@ import * as jsdom from "jsdom"; import { Cookie } from "tough-cookie"; import { UID2 } from "./uid2Sdk"; import { Uid2Identity } from "./Uid2Identity"; +import { localStorageKeyName } from "./uid2LocalStorageManager"; export class CookieMock { jar: jsdom.CookieJar; @@ -114,7 +115,7 @@ export class CryptoMock { ); this.applyTo = (window) => { - Object.defineProperty(window, "crypto", { value: this }); + Object.defineProperty(window, "crypto", { value: this, writable: true }); }; this.applyTo(window); @@ -146,11 +147,24 @@ export function setUid2Cookie(value: any) { UID2.COOKIE_NAME + "=" + encodeURIComponent(JSON.stringify(value)); } +export function removeUid2Cookie() { + document.cookie = + document.cookie + "=;expires=Tue, 1 Jan 1980 23:59:59 GMT"; +} + export async function flushPromises() { await Promise.resolve(); await Promise.resolve(); } +export function getUid2(useCookie?: boolean) { + return useCookie ? getUid2Cookie() : getUid2LocalStorage(); +} + +export function setUid2(value: any, useCookie?: boolean) { + return useCookie ? setUid2Cookie(value) : setUid2LocalStorage(value); +} + export function getUid2Cookie() { const docCookie = document.cookie; if (docCookie) { @@ -161,6 +175,21 @@ export function getUid2Cookie() { return JSON.parse(decodeURIComponent(payload.split("=")[1])); } } + return null; +} + +export function removeUid2LocalStorage() { + localStorage.removeItem(localStorageKeyName); +} + +export function setUid2LocalStorage(identity: any) { + const value = JSON.stringify(identity); + localStorage.setItem(localStorageKeyName, value); +} + +export function getUid2LocalStorage() { + const value = localStorage.getItem(localStorageKeyName); + return value !== null ? JSON.parse(value) : null; } export function setEuidCookie(value: any) { diff --git a/src/uid2CookieManager.ts b/src/uid2CookieManager.ts index 3c8d23b..31a2416 100644 --- a/src/uid2CookieManager.ts +++ b/src/uid2CookieManager.ts @@ -71,12 +71,20 @@ export class UID2CookieManager { } } + private migrateLegacyCookie(identity: LegacyUid2SDKCookie, now: number): Uid2Identity { + const newCookie = enrichIdentity(identity, now); + this.setCookie(newCookie); + return newCookie; + } + public loadIdentityFromCookie(): Uid2Identity | null { const payload = this.getCookie(); if (payload) { const result = JSON.parse(payload) as unknown; if (isValidIdentity(result)) return result; - if (isLegacyCookie(result)) return enrichIdentity(result, Date.now()); + if (isLegacyCookie(result)) { + return this.migrateLegacyCookie(result, Date.now()); + } } return null; } diff --git a/src/uid2LocalStorageManager.ts b/src/uid2LocalStorageManager.ts new file mode 100644 index 0000000..2cd059e --- /dev/null +++ b/src/uid2LocalStorageManager.ts @@ -0,0 +1,25 @@ +import { isValidIdentity, Uid2Identity } from "./Uid2Identity"; + +export const localStorageKeyName = 'UID2-sdk-identity' + +export class UID2LocalStorageManager { + public setValue(identity: Uid2Identity) { + const value = JSON.stringify(identity); + localStorage.setItem(localStorageKeyName, value); + } + public removeValue() { + localStorage.removeItem(localStorageKeyName); + } + private getValue() { + return localStorage.getItem(localStorageKeyName); + } + + public loadIdentityFromLocalStorage(): Uid2Identity | null { + const payload = this.getValue(); + if (payload) { + const result = JSON.parse(payload) as unknown; + if (isValidIdentity(result)) return result; + } + return null; + } +} diff --git a/src/uid2Sdk.ts b/src/uid2Sdk.ts index cdaba0f..b3f503c 100644 --- a/src/uid2Sdk.ts +++ b/src/uid2Sdk.ts @@ -1,22 +1,22 @@ +import { version } from "../package.json"; +import { Uid2Identity } from "./Uid2Identity"; +import { IdentityStatus, notifyInitCallback } from "./Uid2InitCallbacks"; +import { Uid2Options, isUID2OptionsOrThrow } from "./Uid2Options"; import { Uid2ApiClient } from "./uid2ApiClient"; +import { bytesToBase64 } from "./uid2Base64"; import { EventType, Uid2CallbackHandler, Uid2CallbackManager, } from "./uid2CallbackManager"; -import { UID2CookieManager } from "./uid2CookieManager"; -import { Uid2Identity } from "./Uid2Identity"; -import { IdentityStatus, notifyInitCallback } from "./Uid2InitCallbacks"; -import { isUID2OptionsOrThrow, Uid2Options } from "./Uid2Options"; -import { UID2PromiseHandler } from "./uid2PromiseHandler"; -import { version } from "../package.json"; -import { isBase64Hash } from "./uid2HashedDii"; -import { isNormalizedPhone, normalizeEmail } from "./uid2DiiNormalization"; import { ClientSideIdentityOptions, isClientSideIdentityOptionsOrThrow, } from "./uid2ClientSideIdentityOptions"; -import { bytesToBase64 } from "./uid2Base64"; +import { isNormalizedPhone, normalizeEmail } from "./uid2DiiNormalization"; +import { isBase64Hash } from "./uid2HashedDii"; +import { UID2PromiseHandler } from "./uid2PromiseHandler"; +import { UID2StorageManager } from "./uid2StorageManager"; function hasExpired(expiry: number, now = Date.now()) { return expiry <= now; @@ -54,7 +54,7 @@ export class UID2 { private _callbackManager: Uid2CallbackManager; // Dependencies initialised on call to init due to requirement for options - private _cookieManager: UID2CookieManager | undefined; + private _storageManager: UID2StorageManager | undefined; private _apiClient: Uid2ApiClient | undefined; // State @@ -179,8 +179,8 @@ export class UID2 { public disconnect() { this.abort(`UID2 SDK disconnected.`); // Note: This silently fails to clear the cookie if init hasn't been called and a cookieDomain is used! - if (this._cookieManager) this._cookieManager.removeCookie(); - else new UID2CookieManager({}).removeCookie(); + if (this._storageManager) this._storageManager.removeValues(); + else new UID2StorageManager({}).removeValues(); this._identity = undefined; this._callbackManager.runCallbacks(UID2.EventType.IdentityUpdated, { identity: null, @@ -218,12 +218,16 @@ export class UID2 { ); this._opts = opts; - this._cookieManager = new UID2CookieManager({ ...opts }); + this._storageManager = new UID2StorageManager({ ...opts }); this._apiClient = new Uid2ApiClient(opts); this._tokenPromiseHandler.registerApiClient(this._apiClient); - const identity = this._opts.identity - ? this._opts.identity - : this._cookieManager.loadIdentityFromCookie(); + + let identity; + if (this._opts.identity) { + identity = this._opts.identity; + } else { + identity = this._storageManager.loadIdentityWithFallback(); + } const validatedIdentity = this.validateAndSetIdentity(identity); if (validatedIdentity) this.triggerRefreshOrSetTimer(validatedIdentity); this._initComplete = true; @@ -247,17 +251,17 @@ export class UID2 { private getIdentityStatus(identity: Uid2Identity | null): | { - valid: true; - identity: Uid2Identity; - errorMessage: string; - status: IdentityStatus; - } + valid: true; + identity: Uid2Identity; + errorMessage: string; + status: IdentityStatus; + } | { - valid: false; - errorMessage: string; - status: IdentityStatus; - identity: null; - } { + valid: false; + errorMessage: string; + status: IdentityStatus; + identity: null; + } { if (!identity) { return { valid: false, @@ -318,7 +322,7 @@ export class UID2 { status?: IdentityStatus, statusText?: string ): Uid2Identity | null { - if (!this._cookieManager) + if (!this._storageManager) throw new Error("Cannot set identity before calling init."); const validity = this.getIdentityStatus(identity); if ( @@ -329,10 +333,10 @@ export class UID2 { this._identity = validity.identity; if (validity.identity) { - this._cookieManager.setCookie(validity.identity); + this._storageManager.setValue(validity.identity); } else { this.abort(); - this._cookieManager.removeCookie(); + this._storageManager.removeValues(); } notifyInitCallback( this._opts, @@ -362,7 +366,7 @@ export class UID2 { this._refreshTimerId = setTimeout(() => { if (this.isLoginRequired()) return; const validatedIdentity = this.validateAndSetIdentity( - this._cookieManager?.loadIdentityFromCookie() ?? null + this._storageManager?.loadIdentity() ?? null ); if (validatedIdentity) this.triggerRefreshOrSetTimer(validatedIdentity); this._refreshTimerId = null; @@ -423,7 +427,7 @@ export class UID2 { } private async callCstgAndSetIdentity( - request: { emailHash: string } | { phoneHash: string }, + request: { emailHash: string; } | { phoneHash: string; }, opts: ClientSideIdentityOptions ) { const cstgResult = await this._apiClient!.callCstgApi(request, opts); diff --git a/src/uid2StorageManager.ts b/src/uid2StorageManager.ts new file mode 100644 index 0000000..18850a1 --- /dev/null +++ b/src/uid2StorageManager.ts @@ -0,0 +1,48 @@ +import { UID2CookieManager } from "./uid2CookieManager"; +import { Uid2Identity } from "./Uid2Identity"; +import { UID2LocalStorageManager } from "./uid2LocalStorageManager"; +import { Uid2Options } from "./Uid2Options"; + +export class UID2StorageManager { + private _cookieManager: UID2CookieManager; + private _localStorageManager: UID2LocalStorageManager; + + private _opts: Uid2Options; + constructor(opts: Uid2Options) { + this._opts = opts; + this._cookieManager = new UID2CookieManager({ ...opts }); + this._localStorageManager = new UID2LocalStorageManager(); + } + + public loadIdentityWithFallback(): Uid2Identity | null { + const localStorageIdentity = this._localStorageManager.loadIdentityFromLocalStorage(); + const cookieIdentity = this._cookieManager.loadIdentityFromCookie(); + const shouldUseCookie = cookieIdentity && (!localStorageIdentity || cookieIdentity.identity_expires > localStorageIdentity.identity_expires); + return shouldUseCookie ? cookieIdentity : localStorageIdentity; + }; + + public loadIdentity(): Uid2Identity | null { + return this._opts.useCookie + ? this._cookieManager.loadIdentityFromCookie() + : this._localStorageManager.loadIdentityFromLocalStorage(); + } + + public setValue(identity: Uid2Identity) { + if (this._opts.useCookie) { + this._cookieManager.setCookie(identity); + return; + } + + this._localStorageManager.setValue(identity); + if (this._opts.useCookie === false && + this._localStorageManager.loadIdentityFromLocalStorage() + ) { + this._cookieManager.removeCookie(); + } + } + + public removeValues() { + this._cookieManager.removeCookie(); + this._localStorageManager.removeValue(); + } +}