From 4a0205da8a4c8e9e360d3c95fd44a7c811c4ac16 Mon Sep 17 00:00:00 2001 From: Lionell Pack Date: Fri, 9 Feb 2024 16:50:00 +1100 Subject: [PATCH 1/3] Move hashing and base64 encoding out to a separate module. Pass in default base URL, cookie name, and local storage key name to relevant classes and make internal values harder to access externally. Remove unused (and out of date) Prebid.js module file. Mark isLoginRequired as deprecated and provide a new alternative (less misleading) name. Move setIdentityFromPhone (and hash version) to the UID2 class, as it's only available on UID2. --- .vscode/settings.json | 3 +- package.json | 7 +- src/encoding/hash.ts | 6 + src/{ => encoding}/uid2Base64.ts | 0 .../clientSideTokenGeneration.test.ts | 2 +- src/mocks.ts | 11 +- src/prebidModule.ts | 136 ----------------- src/uid2ApiClient.ts | 18 ++- src/uid2CookieManager.ts | 5 +- src/uid2CstgCrypto.ts | 2 +- src/uid2LocalStorageManager.ts | 12 +- src/uid2Sdk.ts | 138 ++++++++++-------- src/uid2StorageManager.ts | 6 +- src/unitTests/uid2ApiClient.test.ts | 4 +- src/unitTests/uid2Base64.test.ts | 28 ++-- src/unitTests/uid2CstgBox.test.ts | 2 +- src/unitTests/uid2CstgCrypto.test.ts | 2 +- src/unitTests/uid2HashedDii.test.ts | 2 +- 18 files changed, 144 insertions(+), 240 deletions(-) create mode 100644 src/encoding/hash.ts rename src/{ => encoding}/uid2Base64.ts (100%) delete mode 100644 src/prebidModule.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index abd61730..f74b7dee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "googletag", "initialised", "initialising", + "IUID", "optout", "pbjs", "refreshable" @@ -11,7 +12,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "editor.tabSize": 2 } diff --git a/package.json b/package.json index 39e58c0a..88b14bd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uid2/uid2-sdk", - "version": "3.2.0", + "version": "4.0.0", "description": "UID2 Client SDK", "main": "lib/uid2Sdk.js", "types": "lib/uid2Sdk.d.ts", @@ -9,6 +9,11 @@ ], "author": "The Trade Desk", "license": "Apache 2.0", + "wallaby": { + "delays": { + "run": 1000 + } + }, "scripts": { "lint": "eslint -c .eslintrc.js . ../static/js/uid2-sdk-2.0.0.js ../static/js/uid2-sdk-1.0.0.js", "test": "jest", diff --git a/src/encoding/hash.ts b/src/encoding/hash.ts new file mode 100644 index 00000000..a2e7e32d --- /dev/null +++ b/src/encoding/hash.ts @@ -0,0 +1,6 @@ +import { bytesToBase64 } from './uid2Base64'; + +export async function hashAndEncodeIdentifier(value: string) { + const hash = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value)); + return bytesToBase64(new Uint8Array(hash)); +} diff --git a/src/uid2Base64.ts b/src/encoding/uid2Base64.ts similarity index 100% rename from src/uid2Base64.ts rename to src/encoding/uid2Base64.ts diff --git a/src/integrationTests/clientSideTokenGeneration.test.ts b/src/integrationTests/clientSideTokenGeneration.test.ts index 70730852..dd29298f 100644 --- a/src/integrationTests/clientSideTokenGeneration.test.ts +++ b/src/integrationTests/clientSideTokenGeneration.test.ts @@ -1,6 +1,6 @@ import * as mocks from '../mocks'; import { NAME_CURVE } from '../mocks'; -import { base64ToBytes, bytesToBase64 } from '../uid2Base64'; +import { base64ToBytes, bytesToBase64 } from '../encoding/uid2Base64'; import { EventType } from '../uid2CallbackManager'; import { sdkWindow, UID2 } from '../uid2Sdk'; diff --git a/src/mocks.ts b/src/mocks.ts index 65ba79f3..4deaf332 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -2,10 +2,11 @@ import * as jsdom from 'jsdom'; import { Cookie } from 'tough-cookie'; import { UID2 } from './uid2Sdk'; import { Uid2Identity } from './Uid2Identity'; -import { localStorageKeyName } from './uid2LocalStorageManager'; -import { base64ToBytes, bytesToBase64 } from './uid2Base64'; +import { base64ToBytes, bytesToBase64 } from './encoding/uid2Base64'; import * as crypto from 'crypto'; +const uid2LocalStorageKeyName = 'UID2-sdk-identity'; + export class CookieMock { jar: jsdom.CookieJar; url: string; @@ -311,16 +312,16 @@ export function getUid2Cookie() { } export function removeUid2LocalStorage() { - localStorage.removeItem(localStorageKeyName); + localStorage.removeItem(uid2LocalStorageKeyName); } export function setUid2LocalStorage(identity: any) { const value = JSON.stringify(identity); - localStorage.setItem(localStorageKeyName, value); + localStorage.setItem(uid2LocalStorageKeyName, value); } export function getUid2LocalStorage() { - const value = localStorage.getItem(localStorageKeyName); + const value = localStorage.getItem(uid2LocalStorageKeyName); return value !== null ? JSON.parse(value) : null; } diff --git a/src/prebidModule.ts b/src/prebidModule.ts deleted file mode 100644 index a25265d1..00000000 --- a/src/prebidModule.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Uid2ApiClient } from './uid2ApiClient'; -import { Uid2Identity } from './Uid2Identity'; - -function getStorageManager(config: any): Storage { - return {} as Storage; -} - -type Storage = { - cookiesAreEnabled: () => boolean; - localStorageIsEnabled: () => boolean; - setCookie: (name: string, value: string) => void; - getCookie: (name: string) => string; - setLocalStorage: (name: string, value: any) => void; - getDataFromLocalStorage: (name: string) => any; -}; - -const MODULE_NAME = 'uid2'; -const GVLID = 887; -const LOG_PRE_FIX = 'UID2: '; -const ADVERTISING_COOKIE = '__uid2_advertising_token'; - -function readCookie(): StoredValue | null { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - return storage.cookiesAreEnabled() - ? (JSON.parse(storage.getCookie(ADVERTISING_COOKIE)) as StoredValue) - : null; -} - -function readServerProvidedCookie(cookieName: string) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - return JSON.parse(storage.getCookie(cookieName)) as Uid2Identity; -} - -function readFromLocalStorage(): StoredValue | null { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - return storage.localStorageIsEnabled() - ? (storage.getDataFromLocalStorage(ADVERTISING_COOKIE) as StoredValue) - : null; -} - -function storeValue(value: any) { - if (storage.cookiesAreEnabled()) storage.setCookie(ADVERTISING_COOKIE, JSON.stringify(value)); - else if (storage.localStorageIsEnabled()) storage.setLocalStorage(ADVERTISING_COOKIE, value); -} - -function getStorage() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return getStorageManager({ gvlid: GVLID, moduleName: MODULE_NAME }); -} - -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const storage = getStorage(); - -type DecodedUid2 = { - uid2: { - id: string; - }; -}; - -type StoredValue = StoredUid2 | LegacyStoredValue; -type LegacyStoredValue = DecodedUid2; -type StoredUid2 = { - originalToken: Uid2Identity; - latestToken: Uid2Identity; -}; - -type PrebidUid2Config = { - name: 'uid2'; - uid2Token?: Uid2Identity; - uid2ServerCookie?: string; - uid2ApiBase?: string; -}; - -export const uid2IdSubmodule = { - name: MODULE_NAME, - gvlid: GVLID, - decode(value: StoredValue) { - if ('uid2' in value) return value; - if (Date.now() < value.latestToken.identity_expires) - return { uid2: { id: value.latestToken.advertising_token } }; - return null; - }, - - getId(config: PrebidUid2Config) { - let suppliedToken: Uid2Identity | null = null; - if ('uid2Token' in config && config.uid2Token) { - suppliedToken = config.uid2Token; - } else if ('uid2ServerCookie' in config && config.uid2ServerCookie) { - suppliedToken = readServerProvidedCookie(config.uid2ServerCookie); - } - - let storedTokens = readCookie() || readFromLocalStorage(); - if (storedTokens && 'uid2' in storedTokens) { - // Legacy value stored, this must be an old integration. If no token supplied, just use the legacy value. - if (!suppliedToken) return { id: storedTokens }; - // Otherwise, ignore the legacy value. - storedTokens = null; - } - - if (suppliedToken && storedTokens) { - if (storedTokens.originalToken.advertising_token !== suppliedToken.advertising_token) { - // Stored token wasn't originally sourced from the provided token - ignore the stored value. - storedTokens = null; - } - } - - // At this point, any legacy values or superseded stored tokens have been nulled out. - const newestAvailableToken = storedTokens?.latestToken ?? suppliedToken; - if (!newestAvailableToken || Date.now() > newestAvailableToken.refresh_expires) { - // Newest available token is expired and not refreshable. - return { id: null }; - } - - if (Date.now() > newestAvailableToken?.refresh_from) { - // Not expired but should try to refresh. - const apiClient = new Uid2ApiClient({ baseUrl: config.uid2ApiBase }); - const promise = apiClient.callRefreshApi(newestAvailableToken); - return { - callback: (cb: any) => { - // TODO: Store the new thing - // eslint-disable-next-line @typescript-eslint/no-floating-promises, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call - promise.then((result) => cb(result)); - }, - }; - } - - const newId = { - id: { - originalToken: suppliedToken, - latestToken: suppliedToken, - }, - }; - storeValue(newId); - return newId; - }, -}; diff --git a/src/uid2ApiClient.ts b/src/uid2ApiClient.ts index 5a1a5359..c2b70ea5 100644 --- a/src/uid2ApiClient.ts +++ b/src/uid2ApiClient.ts @@ -1,9 +1,9 @@ -import { UID2 } from './uid2Sdk'; +import { UID2SdkBase } from './uid2Sdk'; import { isValidIdentity, Uid2Identity } from './Uid2Identity'; import { UID2CstgBox } from './uid2CstgBox'; import { exportPublicKey } from './uid2CstgCrypto'; import { ClientSideIdentityOptions, stripPublicKeyPrefix } from './uid2ClientSideIdentityOptions'; -import { base64ToBytes, bytesToBase64 } from './uid2Base64'; +import { base64ToBytes, bytesToBase64 } from './encoding/uid2Base64'; export type RefreshResultWithoutIdentity = { status: ResponseStatusWithoutBody; @@ -100,10 +100,12 @@ export type Uid2ApiClientOptions = { export class Uid2ApiClient { private _baseUrl: string; private _clientVersion: string; + private _productName: string; private _requestsInFlight: XMLHttpRequest[] = []; - constructor(opts: Uid2ApiClientOptions) { - this._baseUrl = opts.baseUrl ?? 'https://prod.uidapi.com'; - this._clientVersion = 'uid2-sdk-' + UID2.VERSION; + constructor(opts: Uid2ApiClientOptions, defaultBaseUrl: string, productName: string) { + this._baseUrl = opts.baseUrl ?? defaultBaseUrl; + this._productName = productName; + this._clientVersion = productName.toLowerCase() + '-sdk-' + UID2SdkBase.VERSION; } public hasActiveRequests() { @@ -133,7 +135,7 @@ export class Uid2ApiClient { this._requestsInFlight.push(req); req.overrideMimeType('text/plain'); req.open('POST', url, true); - req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); + req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); // TODO: EUID let resolvePromise: (result: RefreshResult) => void; // eslint-disable-next-line @typescript-eslint/no-explicit-any let rejectPromise: (reason?: any) => void; @@ -181,10 +183,10 @@ export class Uid2ApiClient { if (typeof result === 'string') rejectPromise(result); else resolvePromise(result); }, - (reason) => rejectPromise(`Call to UID2 API failed: ` + reason) + (reason) => rejectPromise(`Call to ${this._productName} API failed: ` + reason) ); }, - (reason) => rejectPromise(`Call to UID2 API failed: ` + reason) + (reason) => rejectPromise(`Call to ${this._productName} API failed: ` + reason) ); } } catch (err) { diff --git a/src/uid2CookieManager.ts b/src/uid2CookieManager.ts index dbceac02..41cac511 100644 --- a/src/uid2CookieManager.ts +++ b/src/uid2CookieManager.ts @@ -34,8 +34,9 @@ function enrichIdentity(identity: LegacyUid2SDKCookie, now: number) { export class UID2CookieManager { private _opts: UID2CookieOptions; - private _cookieName: string = UID2.COOKIE_NAME; - constructor(opts: UID2CookieOptions) { + private _cookieName: string; + constructor(opts: UID2CookieOptions, cookieName: string) { + this._cookieName = cookieName; this._opts = opts; } public setCookie(identity: Uid2Identity) { diff --git a/src/uid2CstgCrypto.ts b/src/uid2CstgCrypto.ts index b69c4a9a..77220ca0 100644 --- a/src/uid2CstgCrypto.ts +++ b/src/uid2CstgCrypto.ts @@ -1,4 +1,4 @@ -import { base64ToBytes } from './uid2Base64'; +import { base64ToBytes } from './encoding/uid2Base64'; export function generateKeyPair(namedCurve: NamedCurve): Promise { const params: EcKeyGenParams = { diff --git a/src/uid2LocalStorageManager.ts b/src/uid2LocalStorageManager.ts index ef8cb4cd..508e23e9 100644 --- a/src/uid2LocalStorageManager.ts +++ b/src/uid2LocalStorageManager.ts @@ -1,17 +1,19 @@ import { isValidIdentity, Uid2Identity } from './Uid2Identity'; -export const localStorageKeyName = 'UID2-sdk-identity'; - export class UID2LocalStorageManager { + private _storageKey: string; + constructor(storageKey: string) { + this._storageKey = storageKey; + } public setValue(identity: Uid2Identity) { const value = JSON.stringify(identity); - localStorage.setItem(localStorageKeyName, value); + localStorage.setItem(this._storageKey, value); } public removeValue() { - localStorage.removeItem(localStorageKeyName); + localStorage.removeItem(this._storageKey); } private getValue() { - return localStorage.getItem(localStorageKeyName); + return localStorage.getItem(this._storageKey); } public loadIdentityFromLocalStorage(): Uid2Identity | null { diff --git a/src/uid2Sdk.ts b/src/uid2Sdk.ts index 654fb0af..a61eab53 100644 --- a/src/uid2Sdk.ts +++ b/src/uid2Sdk.ts @@ -4,7 +4,7 @@ import { IdentityStatus, notifyInitCallback } from './Uid2InitCallbacks'; import { Uid2Options, isUID2OptionsOrThrow } from './Uid2Options'; import { Logger, MakeLogger } from './sdk/logger'; import { Uid2ApiClient } from './uid2ApiClient'; -import { bytesToBase64 } from './uid2Base64'; +import { bytesToBase64 } from './encoding/uid2Base64'; import { EventType, Uid2CallbackHandler, Uid2CallbackManager } from './uid2CallbackManager'; import { ClientSideIdentityOptions, @@ -14,6 +14,7 @@ import { isNormalizedPhone, normalizeEmail } from './uid2DiiNormalization'; import { isBase64Hash } from './uid2HashedDii'; import { UID2PromiseHandler } from './uid2PromiseHandler'; import { UID2StorageManager } from './uid2StorageManager'; +import { hashAndEncodeIdentifier } from './encoding/hash'; function hasExpired(expiry: number, now = Date.now()) { return expiry <= now; @@ -21,7 +22,13 @@ function hasExpired(expiry: number, now = Date.now()) { type CallbackContainer = { callback?: () => void }; -type Product = 'UID2' | 'EUID'; +type ProductName = 'UID2' | 'EUID'; +type ProductDetails = { + name: ProductName; + cookieName: string; + localStorageKey: string; + defaultBaseUrl: string; +}; export abstract class UID2SdkBase { static get VERSION() { @@ -39,38 +46,29 @@ export abstract class UID2SdkBase { // Dependencies initialised on construction private _logger: Logger; private _tokenPromiseHandler: UID2PromiseHandler; - private _callbackManager: Uid2CallbackManager; + protected _callbackManager: Uid2CallbackManager; // Dependencies initialised on call to init due to requirement for options private _storageManager: UID2StorageManager | undefined; private _apiClient: Uid2ApiClient | undefined; // State - private _product: Product; + private _product: ProductDetails; private _opts: Uid2Options = {}; private _identity: Uid2Identity | null | undefined; private _initComplete = false; - constructor( + // Sets up nearly everything, but does not run SdkLoaded callbacks - derived classes must run them. + protected constructor( existingCallbacks: Uid2CallbackHandler[] | undefined = undefined, - callbackContainer: CallbackContainer, - product: Product + product: ProductDetails ) { this._product = product; - this._logger = MakeLogger(console, product); + this._logger = MakeLogger(console, product.name); if (existingCallbacks) this.callbacks = existingCallbacks; this._tokenPromiseHandler = new UID2PromiseHandler(this); this._callbackManager = new Uid2CallbackManager(this, () => this.getIdentity(), this._logger); - const runCallbacks = () => { - this._callbackManager.runCallbacks(EventType.SdkLoaded, {}); - }; - if (window.__uid2 instanceof UID2) { - runCallbacks(); - } else { - // Need to defer running callbacks until this is assigned to the window global - callbackContainer.callback = runCallbacks; - } } public init(opts: Uid2Options) { @@ -90,7 +88,7 @@ export abstract class UID2SdkBase { throw new Error('Invalid email address'); } - const emailHash = await UID2.hash(email); + const emailHash = await hashAndEncodeIdentifier(email); await this.callCstgAndSetIdentity({ emailHash: emailHash }, opts); } @@ -105,29 +103,6 @@ export abstract class UID2SdkBase { await this.callCstgAndSetIdentity({ emailHash: emailHash }, opts); } - public async setIdentityFromPhone(phone: string, opts: ClientSideIdentityOptions) { - this.throwIfInitNotComplete('Cannot set identity before calling init.'); - isClientSideIdentityOptionsOrThrow(opts); - - if (!isNormalizedPhone(phone)) { - throw new Error('Invalid phone number'); - } - - const phoneHash = await UID2.hash(phone); - await this.callCstgAndSetIdentity({ phoneHash: phoneHash }, opts); - } - - public async setIdentityFromPhoneHash(phoneHash: string, opts: ClientSideIdentityOptions) { - this.throwIfInitNotComplete('Cannot set identity before calling init.'); - isClientSideIdentityOptionsOrThrow(opts); - - if (!isBase64Hash(phoneHash)) { - throw new Error('Invalid hash'); - } - - await this.callCstgAndSetIdentity({ phoneHash: phoneHash }, opts); - } - public setIdentity(identity: Uid2Identity) { if (this._apiClient) this._apiClient.abortActiveRequests(); const validatedIdentity = this.validateAndSetIdentity(identity); @@ -148,16 +123,26 @@ export abstract class UID2SdkBase { return this._tokenPromiseHandler.createMaybeDeferredPromise(token ?? null); } + // Deprecated public isLoginRequired() { + return this.isSetIdentityRequired(); + } + + public isSetIdentityRequired() { if (!this._initComplete) return undefined; return !(this.isLoggedIn() || this._apiClient?.hasActiveRequests()); } public disconnect() { - this.abort(`${this._product} SDK disconnected.`); + this.abort(`${this._product.name} SDK disconnected.`); // Note: This silently fails to clear the cookie if init hasn't been called and a cookieDomain is used! if (this._storageManager) this._storageManager.removeValues(); - else new UID2StorageManager({}).removeValues(); + else + new UID2StorageManager( + {}, + this._product.cookieName, + this._product.localStorageKey + ).removeValues(); this._identity = undefined; this._callbackManager.runCallbacks(EventType.IdentityUpdated, { identity: null, @@ -168,7 +153,7 @@ export abstract class UID2SdkBase { public abort(reason?: string) { this._initComplete = true; this._tokenPromiseHandler.rejectAllPromises( - reason ?? new Error(`${this._product} SDK aborted.`) + reason ?? new Error(`${this._product.name} SDK aborted.`) ); if (this._refreshTimerId) { clearTimeout(this._refreshTimerId); @@ -177,21 +162,20 @@ export abstract class UID2SdkBase { if (this._apiClient) this._apiClient.abortActiveRequests(); } - private static async hash(value: string) { - const hash = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value)); - return bytesToBase64(new Uint8Array(hash)); - } - private initInternal(opts: Uid2Options | unknown) { if (this._initComplete) { throw new TypeError('Calling init() more than once is not allowed'); } if (!isUID2OptionsOrThrow(opts)) - throw new TypeError(`Options provided to ${this._product} init couldn't be validated.`); + throw new TypeError(`Options provided to ${this._product.name} init couldn't be validated.`); this._opts = opts; - this._storageManager = new UID2StorageManager({ ...opts }); - this._apiClient = new Uid2ApiClient(opts); + this._storageManager = new UID2StorageManager( + { ...opts }, + this._product.cookieName, + this._product.localStorageKey + ); + this._apiClient = new Uid2ApiClient(opts, this._product.defaultBaseUrl, this._product.name); this._tokenPromiseHandler.registerApiClient(this._apiClient); let identity; @@ -387,7 +371,7 @@ export abstract class UID2SdkBase { ); } - private async callCstgAndSetIdentity( + protected async callCstgAndSetIdentity( request: { emailHash: string } | { phoneHash: string }, opts: ClientSideIdentityOptions ) { @@ -396,7 +380,7 @@ export abstract class UID2SdkBase { this.setIdentity(cstgResult.identity); } - private throwIfInitNotComplete(message: string) { + protected throwIfInitNotComplete(message: string) { if (!this._initComplete) { throw new Error(message); } @@ -404,12 +388,18 @@ export abstract class UID2SdkBase { } export class UID2 extends UID2SdkBase { - get product() { - return 'UID2'; - } + // Deprecated. Integrators should never access the cookie directly! static get COOKIE_NAME() { return '__uid_2'; } + private static get Uid2Details(): ProductDetails { + return { + name: 'UID2', + defaultBaseUrl: 'https://prod.uidapi.com', + localStorageKey: 'UID2-sdk-identity', + cookieName: UID2.COOKIE_NAME, + }; + } static setupGoogleTag() { UID2.setupGoogleSecureSignals(); @@ -424,7 +414,39 @@ export class UID2 extends UID2SdkBase { existingCallbacks: Uid2CallbackHandler[] | undefined = undefined, callbackContainer: CallbackContainer = {} ) { - super(existingCallbacks, callbackContainer, 'UID2'); + super(existingCallbacks, UID2.Uid2Details); + const runCallbacks = () => { + this._callbackManager.runCallbacks(EventType.SdkLoaded, {}); + }; + if (window.__uid2 instanceof UID2) { + runCallbacks(); + } else { + // Need to defer running callbacks until this is assigned to the window global + callbackContainer.callback = runCallbacks; + } + } + + public async setIdentityFromPhone(phone: string, opts: ClientSideIdentityOptions) { + this.throwIfInitNotComplete('Cannot set identity before calling init.'); + isClientSideIdentityOptionsOrThrow(opts); + + if (!isNormalizedPhone(phone)) { + throw new Error('Invalid phone number'); + } + + const phoneHash = await hashAndEncodeIdentifier(phone); + await this.callCstgAndSetIdentity({ phoneHash: phoneHash }, opts); + } + + public async setIdentityFromPhoneHash(phoneHash: string, opts: ClientSideIdentityOptions) { + this.throwIfInitNotComplete('Cannot set identity before calling init.'); + isClientSideIdentityOptionsOrThrow(opts); + + if (!isBase64Hash(phoneHash)) { + throw new Error('Invalid hash'); + } + + await this.callCstgAndSetIdentity({ phoneHash: phoneHash }, opts); } } diff --git a/src/uid2StorageManager.ts b/src/uid2StorageManager.ts index 021d9ff3..00466759 100644 --- a/src/uid2StorageManager.ts +++ b/src/uid2StorageManager.ts @@ -8,10 +8,10 @@ export class UID2StorageManager { private _localStorageManager: UID2LocalStorageManager; private _opts: Uid2Options; - constructor(opts: Uid2Options) { + constructor(opts: Uid2Options, cookieName: string, localStorageKey: string) { this._opts = opts; - this._cookieManager = new UID2CookieManager({ ...opts }); - this._localStorageManager = new UID2LocalStorageManager(); + this._cookieManager = new UID2CookieManager({ ...opts }, cookieName); + this._localStorageManager = new UID2LocalStorageManager(localStorageKey); } public loadIdentityWithFallback(): Uid2Identity | null { diff --git a/src/unitTests/uid2ApiClient.test.ts b/src/unitTests/uid2ApiClient.test.ts index c2c9ce9b..1d6a3bdb 100644 --- a/src/unitTests/uid2ApiClient.test.ts +++ b/src/unitTests/uid2ApiClient.test.ts @@ -1,6 +1,6 @@ import { NAME_CURVE, XhrMock, makeCstgOption, makeIdentityV2 } from '../mocks'; import { Uid2ApiClient } from '../uid2ApiClient'; -import { base64ToBytes, bytesToBase64 } from '../uid2Base64'; +import { base64ToBytes, bytesToBase64 } from '../encoding/uid2Base64'; import { sdkWindow } from '../uid2Sdk'; describe('UID2 API client tests', () => { @@ -24,7 +24,7 @@ describe('UID2 API client tests', () => { }); beforeEach(() => { - uid2ApiClient = new Uid2ApiClient({}); + uid2ApiClient = new Uid2ApiClient({}, 'https://prod.uidapi.com', 'UID2'); xhrMock = new XhrMock(sdkWindow); }); diff --git a/src/unitTests/uid2Base64.test.ts b/src/unitTests/uid2Base64.test.ts index 2812001a..22a4781c 100644 --- a/src/unitTests/uid2Base64.test.ts +++ b/src/unitTests/uid2Base64.test.ts @@ -1,36 +1,36 @@ -import { base64ToBytes, bytesToBase64 } from "../uid2Base64"; +import { base64ToBytes, bytesToBase64 } from '../encoding/uid2Base64'; -describe("uid2Base64 tests", () => { - describe("#bytesToBase64", () => { - it("should convert bytes to base64", () => { +describe('uid2Base64 tests', () => { + describe('#bytesToBase64', () => { + it('should convert bytes to base64', () => { const bytes = new Uint8Array([72, 101, 108, 108, 111]); const base64 = bytesToBase64(bytes); - expect(base64).toBe("SGVsbG8="); + expect(base64).toBe('SGVsbG8='); }); - it("should handle an empty input", () => { + it('should handle an empty input', () => { const bytes = new Uint8Array([]); const base64 = bytesToBase64(bytes); - expect(base64).toBe(""); + expect(base64).toBe(''); }); }); - describe("#base64ToBytes", () => { - it("should convert base64 to bytes", () => { - const base64 = "SGVsbG8="; + describe('#base64ToBytes', () => { + it('should convert base64 to bytes', () => { + const base64 = 'SGVsbG8='; const bytes = base64ToBytes(base64); expect(Array.from(bytes)).toEqual([72, 101, 108, 108, 111]); }); - it("should handle an empty input", () => { - const base64 = ""; + it('should handle an empty input', () => { + const base64 = ''; const bytes = base64ToBytes(base64); expect(Array.from(bytes)).toEqual([]); }); }); - it("should convert a base64 string to bytes and back", () => { - const originalBase64 = "SGVsbG8gV29ybGQh"; + it('should convert a base64 string to bytes and back', () => { + const originalBase64 = 'SGVsbG8gV29ybGQh'; const bytes = base64ToBytes(originalBase64); const convertedBase64 = bytesToBase64(bytes); diff --git a/src/unitTests/uid2CstgBox.test.ts b/src/unitTests/uid2CstgBox.test.ts index cff009a7..7da6de3a 100644 --- a/src/unitTests/uid2CstgBox.test.ts +++ b/src/unitTests/uid2CstgBox.test.ts @@ -1,5 +1,5 @@ import { NAME_CURVE, decryptClientRequest, encryptServerMessage, makeIdentityV2 } from '../mocks'; -import { bytesToBase64 } from '../uid2Base64'; +import { bytesToBase64 } from '../encoding/uid2Base64'; import { UID2CstgBox } from '../uid2CstgBox'; import { exportPublicKey } from '../uid2CstgCrypto'; diff --git a/src/unitTests/uid2CstgCrypto.test.ts b/src/unitTests/uid2CstgCrypto.test.ts index 7f88aa4b..6e013fe5 100644 --- a/src/unitTests/uid2CstgCrypto.test.ts +++ b/src/unitTests/uid2CstgCrypto.test.ts @@ -1,4 +1,4 @@ -import { bytesToBase64 } from '../uid2Base64'; +import { bytesToBase64 } from '../encoding/uid2Base64'; import { decrypt, deriveKey, diff --git a/src/unitTests/uid2HashedDii.test.ts b/src/unitTests/uid2HashedDii.test.ts index b7acc03a..e26f1539 100644 --- a/src/unitTests/uid2HashedDii.test.ts +++ b/src/unitTests/uid2HashedDii.test.ts @@ -1,4 +1,4 @@ -import { bytesToBase64 } from '../uid2Base64'; +import { bytesToBase64 } from '../encoding/uid2Base64'; import { isBase64Hash } from '../uid2HashedDii'; describe('#isBase64Hash tests', () => { From 79c5f8b65fcd7ed6c28058975860703a5d07d869 Mon Sep 17 00:00:00 2001 From: Lionell Pack Date: Fri, 9 Feb 2024 18:19:39 +1100 Subject: [PATCH 2/3] Update naming based on discussion. --- src/uid2Sdk.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uid2Sdk.ts b/src/uid2Sdk.ts index a61eab53..55915de6 100644 --- a/src/uid2Sdk.ts +++ b/src/uid2Sdk.ts @@ -125,10 +125,10 @@ export abstract class UID2SdkBase { // Deprecated public isLoginRequired() { - return this.isSetIdentityRequired(); + return this.hasIdentity(); } - public isSetIdentityRequired() { + public hasIdentity() { if (!this._initComplete) return undefined; return !(this.isLoggedIn() || this._apiClient?.hasActiveRequests()); } From d3ba0e36a81ca68c84971ccced1396d9af918516 Mon Sep 17 00:00:00 2001 From: Lionell Pack Date: Tue, 13 Feb 2024 11:10:46 +1100 Subject: [PATCH 3/3] Remove some unneeded imports. Use JSDoc format for the deprecated function. --- src/uid2CookieManager.ts | 1 - src/uid2Sdk.ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uid2CookieManager.ts b/src/uid2CookieManager.ts index 41cac511..04cb0dde 100644 --- a/src/uid2CookieManager.ts +++ b/src/uid2CookieManager.ts @@ -1,4 +1,3 @@ -import { UID2 } from './uid2Sdk'; import { isValidIdentity, Uid2Identity } from './Uid2Identity'; export type UID2CookieOptions = { diff --git a/src/uid2Sdk.ts b/src/uid2Sdk.ts index 55915de6..13dd79e0 100644 --- a/src/uid2Sdk.ts +++ b/src/uid2Sdk.ts @@ -4,7 +4,6 @@ import { IdentityStatus, notifyInitCallback } from './Uid2InitCallbacks'; import { Uid2Options, isUID2OptionsOrThrow } from './Uid2Options'; import { Logger, MakeLogger } from './sdk/logger'; import { Uid2ApiClient } from './uid2ApiClient'; -import { bytesToBase64 } from './encoding/uid2Base64'; import { EventType, Uid2CallbackHandler, Uid2CallbackManager } from './uid2CallbackManager'; import { ClientSideIdentityOptions, @@ -123,7 +122,9 @@ export abstract class UID2SdkBase { return this._tokenPromiseHandler.createMaybeDeferredPromise(token ?? null); } - // Deprecated + /** + * Deprecated + */ public isLoginRequired() { return this.hasIdentity(); }