Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

token retrieval without init #102

Merged
merged 27 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c0ef4a1
initial testing for token retrieval without init
ashleysmithTTD Aug 27, 2024
e8639d4
usecookie can be undefined or false
ashleysmithTTD Aug 27, 2024
0a33657
better way of defining load identity
ashleysmithTTD Aug 27, 2024
bd98e5f
has identity doesn't need to see active requests
ashleysmithTTD Aug 27, 2024
3af320e
isloginrequired is deprecated function
ashleysmithTTD Aug 27, 2024
b352521
removed extra check for active requests
ashleysmithTTD Aug 27, 2024
2367236
changes and testing to has identity
ashleysmithTTD Aug 27, 2024
e41238c
finalizing test for token
ashleysmithTTD Aug 27, 2024
0698d87
adding back is login required
ashleysmithTTD Aug 27, 2024
33d978c
new function to get identity if init has not occurred
ashleysmithTTD Aug 28, 2024
1fad72d
legacy cookie addition
ashleysmithTTD Aug 28, 2024
3026d31
remove error cannot set identity before init
ashleysmithTTD Aug 28, 2024
a46d20f
fixed getting identity when needed
ashleysmithTTD Aug 30, 2024
4a21945
fixed removevalue
ashleysmithTTD Aug 30, 2024
a866a10
remove unused import
ashleysmithTTD Aug 30, 2024
fdfbd2b
has identity for is login required
ashleysmithTTD Aug 30, 2024
c080e41
should stil throw init errors in set
ashleysmithTTD Aug 30, 2024
ee870a0
any set function errors if no init yet
ashleysmithTTD Aug 30, 2024
4dd3792
testing the tests
ashleysmithTTD Aug 30, 2024
fd5a423
added missing await keyword in async test
ashleysmithTTD Aug 30, 2024
bea4d2f
no initializing state anymore
ashleysmithTTD Aug 30, 2024
3888be8
load identity no legacy naming change
ashleysmithTTD Aug 30, 2024
08c4c70
using both types of identity storage for testing
ashleysmithTTD Aug 30, 2024
25b73d0
edited description for test
ashleysmithTTD Aug 30, 2024
a4ea126
addressing code review
ashleysmithTTD Sep 3, 2024
5e60441
getvalue not being used anymore
ashleysmithTTD Sep 3, 2024
c607112
opts use cookie check not needed
ashleysmithTTD Sep 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions setupJest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@ expect.extend({
});

expect.extend({
toBeInInitialisingState(uid2) {
expect(uid2.getAdvertisingToken()).toBeUndefined();
expect(uid2.isLoginRequired()).toBeUndefined();

return {
pass: true,
message: () =>
'Expected getAdvertisingToken() returns undefined and isLoginRequired() returns undefined',
};
},

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This state was only used in one test, and isn't really relevant anymore since an advertising token can be found without init

toBeInAvailableState(uid2, expectedAdvertisingToken) {
if (expectedAdvertisingToken) {
expect(uid2.getAdvertisingToken()).toBe(expectedAdvertisingToken);
Expand Down Expand Up @@ -68,7 +57,7 @@ expect.extend({
return {
pass: true,
message: () =>
'Expected getAdvertisingToken() returns undefined and isLoginRequired() returns false',
'Expected getAdvertisingToken() returns undefined and isLoginRequired() returns false',
};
},
});
Expand Down
30 changes: 23 additions & 7 deletions src/cookieManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@ function enrichIdentity(identity: LegacySDKCookie, now: number) {
};
}

function getCookie(cookieName: string) {
const docCookie = document.cookie;
if (docCookie) {
const payload = docCookie.split('; ').find((row) => row.startsWith(cookieName + '='));
if (payload) {
return decodeURIComponent(payload.split('=')[1]);
}
}
}

export function loadIdentityFromCookieNoLegacy(
cookieName: string
): Identity | OptoutIdentity | null {
const payload = getCookie(cookieName);
if (payload) {
const result = JSON.parse(payload) as unknown;
if (isValidIdentity(result)) return result;
if (isOptoutIdentity(result)) return result;
}
return null;
}

Copy link
Contributor Author

@ashleysmithTTD ashleysmithTTD Aug 30, 2024

Choose a reason for hiding this comment

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

Spoke with Lionell about making this a separate function from loadIdentityFromCookie in the CookieManager since we don't want them doing any setting (including migrating and setting the legacy cookie) without init, just getting

export class CookieManager {
private _opts: CookieOptions;
private _cookieName: string;
Expand Down Expand Up @@ -63,13 +85,7 @@ export class CookieManager {
';expires=Tue, 1 Jan 1980 23:59:59 GMT';
}
private getCookie() {
const docCookie = document.cookie;
if (docCookie) {
const payload = docCookie.split('; ').find((row) => row.startsWith(this._cookieName + '='));
if (payload) {
return decodeURIComponent(payload.split('=')[1]);
}
}
return getCookie(this._cookieName);
Copy link
Contributor Author

@ashleysmithTTD ashleysmithTTD Aug 30, 2024

Choose a reason for hiding this comment

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

Not sure if this is conventional or not to have these two functions be the same name - one inside the class and one outside the class - happy to change the name if needed

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't love it. It obviously works but it adds a little extra confusion.

}

private migrateLegacyCookie(identity: LegacySDKCookie, now: number): Identity {
Expand Down
37 changes: 27 additions & 10 deletions src/integrationTests/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,6 @@ testCookieAndLocalStorage(() => {
});
});

describe('initial state before init() is called', () => {
test('should be in initialising state', () => {
(expect(uid2) as any).toBeInInitialisingState();
});

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);
Expand Down Expand Up @@ -1103,3 +1093,30 @@ describe('SDK bootstraps itself if init has already been completed', () => {
}).rejects.toThrow();
});
});

describe('Public functions can be called without init', () => {
const makeIdentity = mocks.makeIdentityV2;
const email = 'test@test.com';
const emailHash = 'lz3+Rj7IV4X1+Vr1ujkG7tstkxwk5pgkqJ6mXbpOgTs=';

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

test('should be able to find token from previous init', async () => {
const identity = { ...makeIdentity(), refresh_from: Date.now() + 100 };

mocks.setUid2LocalStorage(identity);
mocks.setUid2Cookie(identity);

expect(uid2.getAdvertisingToken()).toBe(identity.advertising_token);
expect(uid2.getIdentity()).toStrictEqual(identity);
expect(uid2.getAdvertisingTokenAsync()).resolves.toBe(identity.advertising_token);
expect(async () => {
await uid2.setIdentityFromEmail(email, mocks.makeCstgOption());
}).rejects.toThrow();
expect(async () => {
await uid2.setIdentityFromEmailHash(emailHash, mocks.makeCstgOption());
}).rejects.toThrow();
});
});
24 changes: 16 additions & 8 deletions src/localStorageManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import { isOptoutIdentity, isValidIdentity, OptoutIdentity, Identity } from './Identity';

export function loadIdentityFromLocalStorage(storageKey: string): Identity | OptoutIdentity | null {
const payload = getValue(storageKey);
if (payload) {
const result = JSON.parse(payload) as unknown;
if (isValidIdentity(result)) return result;
if (isOptoutIdentity(result)) return result;
}
return null;
}

function getValue(storageKey: string) {
return localStorage.getItem(storageKey);
}

export class LocalStorageManager {
private _storageKey: string;
constructor(storageKey: string) {
Expand All @@ -13,16 +27,10 @@ export class LocalStorageManager {
localStorage.removeItem(this._storageKey);
}
private getValue() {
return localStorage.getItem(this._storageKey);
return getValue(this._storageKey);
}

public loadIdentityFromLocalStorage(): Identity | OptoutIdentity | null {
const payload = this.getValue();
if (payload) {
const result = JSON.parse(payload) as unknown;
if (isValidIdentity(result)) return result;
if (isOptoutIdentity(result)) return result;
}
return null;
return loadIdentityFromLocalStorage(this._storageKey);
}
}
25 changes: 16 additions & 9 deletions src/sdkBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { StorageManager } from './storageManager';
import { hashAndEncodeIdentifier } from './encoding/hash';
import { ProductDetails, ProductName } from './product';
import { storeConfig, updateConfig } from './configManager';
import { loadIdentityFromCookieNoLegacy } from './cookieManager';
import { loadIdentityFromLocalStorage } from './localStorageManager';

function hasExpired(expiry: number, now = Date.now()) {
return expiry <= now;
Expand Down Expand Up @@ -121,10 +123,12 @@ export abstract class SdkBase {
}

public getIdentity(): Identity | null {
return this._identity && !this.temporarilyUnavailable() && !isOptoutIdentity(this._identity)
? this._identity
const identity = this._identity ?? this.getIdentityNoInit();
return identity && !this.temporarilyUnavailable(identity) && !isOptoutIdentity(identity)
? identity
: null;
}

// When the SDK has been initialized, this function should return the token
// from the most recent refresh request, if there is a request, wait for the
// new token. Otherwise, returns a promise which will be resolved after init.
Expand Down Expand Up @@ -260,13 +264,9 @@ export abstract class SdkBase {
return this._identity && !hasExpired(this._identity.refresh_expires);
}

private temporarilyUnavailable() {
if (!this._identity && this._apiClient?.hasActiveRequests()) return true;
if (
this._identity &&
hasExpired(this._identity.identity_expires) &&
!hasExpired(this._identity.refresh_expires)
)
private temporarilyUnavailable(identity: Identity | OptoutIdentity | null | undefined) {
if (!identity && this._apiClient?.hasActiveRequests()) return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you just put a comment in here indicating that this means that the identity is expired but refreshable? Might help the next person.

if (identity && hasExpired(identity.identity_expires) && !hasExpired(identity.refresh_expires))
return true;
return false;
}
Expand Down Expand Up @@ -457,6 +457,13 @@ export abstract class SdkBase {
);
}

private getIdentityNoInit() {
return (
loadIdentityFromCookieNoLegacy(this._product.cookieName) ??
loadIdentityFromLocalStorage(this._product.localStorageKey)
);
}

protected async callCstgAndSetIdentity(
request: { emailHash: string } | { phoneHash: string },
opts: ClientSideIdentityOptions
Expand Down
12 changes: 5 additions & 7 deletions src/storageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ export class StorageManager {
}

public loadIdentity(): Identity | OptoutIdentity | null {
return this._opts.useCookie
? this._cookieManager.loadIdentityFromCookie()
: this._localStorageManager.loadIdentityFromLocalStorage();
return (
this._cookieManager.loadIdentityFromCookie() ??
this._localStorageManager.loadIdentityFromLocalStorage()
);
}

public setIdentity(identity: Identity) {
Expand Down Expand Up @@ -63,10 +64,7 @@ export class StorageManager {
}

this._localStorageManager.setValue(value);
if (
this._opts.useCookie === false &&
this._localStorageManager.loadIdentityFromLocalStorage()
) {
if (!this._opts.useCookie && this._localStorageManager.loadIdentityFromLocalStorage()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this._opts.useCookie will always be false here. It's checking for true and returning above on line 61.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can also be undefined and if it is undefined, we would still want the cookie to be removed I believe

Copy link
Contributor

Choose a reason for hiding this comment

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

sure. but above it's checking if (this._opts.useCookie) so if (!this._opts.useCookie) will necessarily be true at this point in the code. The previous code was specifically looking for 'false.' If you just remove the check it should also execute when undefined.

this._cookieManager.removeCookie(this._opts);
}
}
Expand Down