-
Notifications
You must be signed in to change notification settings - Fork 1
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
ajy-UID2-1667-Local-storage-for-v3-js-sdk #21
Conversation
- Migrate legacy cookie to new cookie - Add useCookie option - use localStorage by default
@@ -71,12 +71,20 @@ export class UID2CookieManager { | |||
} | |||
} | |||
|
|||
private migrateLegacyCookie(identity: LegacyUid2SDKCookie, now: number): Uid2Identity { | |||
const newCookie = enrichIdentity(identity, now); | |||
this.setCookie(newCookie); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the publisher is close to the cookie size limit, there's a chance this will fail - I think it'd be better to stick with the existing approach of enriching it and returning it, rather than storing the enriched cookie.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The enriched cookie is just the new cookie right? How do we plan to migrate from legacy cookie to the new (enriched) cookie if not by storing the enriched cookie? Can we just have them use local storage if they are close to the cookie size limit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a good chance we'd be going from the legacy cookie straight into local storage, so if we just return it enriched here and store it in whatever is trying to upgrade it, then the common case won't ever increase the cookie size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually - my comment's not quite right.
A legacy cookie will eventually get replaced by a new one - the next time the identity is updated (probably when it gets refreshed). So if we just enrich it when we read it (and don't write it back), it'll eventually be stored in the new format - hopefully in local storage instead. But that then breaks the load, because the migrated value will look newer than anything in local storage. Hmmm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking through a few options, I think maybe your initial approach is the right move here - just upgrade it and store it back immediately.
src/uid2LocalStorageManager.ts
Outdated
export class UID2LocalStorageManager { | ||
public setValue(identity: Uid2Identity) { | ||
const value = JSON.stringify(identity); | ||
localStorage.setItem("identity", value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
identity
probably has a high chance of conflicting with other things - I think it'd be a good idea to choose a localstorage ID that's prefixed with UID2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true. would UID2
itself also run the risk of conflicts? Or is it just too vague? Perhaps UID2-identity
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, cookies use __uid_2
. Would it make sense to use the same name or would that be more confusing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We sometimes find publishers store things for UID2 as well - I think making it clear that it's stored by the SDK would be helpful in a lot of cases. Maybe something like UID2-sdk-identity
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
src/uid2Sdk.ts
Outdated
this._cookieManager.setCookie(validity.identity); | ||
} | ||
else { | ||
this._cookieManager.removeCookie(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of removing the cookie here, but we'll need to be very sure there aren't any cases where the cookie might be removed if we haven't loaded it properly yet - we don't want to remove a valid cookie if we aren't definitely going to store it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be extra defensive, I could change getValue
to a public method and do:
this._localStorageManager.setValue(validity.identity);
if (this._localStorageManager.getValue()) this._cookieManager.removeCookie();
Would that address your concern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think that's reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
src/uid2Sdk.ts
Outdated
identity = this._opts.identity; | ||
} else { | ||
const localStorageIdentity = this._localStorageManager.loadIdentityFromLocalStorage(); | ||
identity = localStorageIdentity !== null ? localStorageIdentity : this._cookieManager.loadIdentityFromCookie(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could probably use ?? to simplify this a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed how this works
src/uid2Sdk.ts
Outdated
} else { | ||
const localStorageIdentity = this._localStorageManager.loadIdentityFromLocalStorage(); | ||
const cookieIdentity = this._cookieManager.loadIdentityFromCookie(); | ||
const shouldUseCookie = cookieIdentity && (!localStorageIdentity || cookieIdentity.identity_expires > localStorageIdentity.identity_expires); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully this handles the case we talked about @lionell-pack-ttd, where the cookie is newer than the value in localStorage, so we should use the cookie instead.
src/Uid2Options.ts
Outdated
@@ -6,7 +6,7 @@ import { InitCallbackOptions } from "./Uid2InitCallbacks"; | |||
export type Uid2Options = BaseUid2Options & | |||
InitCallbackOptions & | |||
UID2CookieOptions & | |||
Uid2ApiClientOptions; | |||
Uid2ApiClientOptions & { useCookie?: boolean; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could add useCookie to the base options type - I think that's just there for options that are just for uid2sdk.ts
and not provided by one of the other classes/files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
src/uid2LocalStorageManager.ts
Outdated
export class UID2LocalStorageManager { | ||
public setValue(identity: Uid2Identity) { | ||
const value = JSON.stringify(identity); | ||
localStorage.setItem("UID2-identity", value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid magic strings. Make sure there's a single source of truth for UID2-identity
- in some cases using a type to enforce the string being correct everywhere is an option, but I don't think we can do that here so a constant is probably the move.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in another comment, we often end up with a few different UID2 values from different sources (i.e. the publisher storing their own things), so I think something like UID2-sdk-identity
would make it easier to trouble-shoot integrations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defined and exporting the const from this file now - I'm using it in the mocks as well (although I don't have to)
- Use const for localStorageKeyName - Move useCookie to base options - Check if value is in localStorage before removing cookie
Without this, only the first pass of the test with succeed, and the following ones would file since the window.cypto would not change
Wraps uid2CookieManager and uid2LocalStorageManager, so the sdk only needs the uid2StorageManager, offloading logic off the sdk class.
Also adjust the mock to return undefined instead of null for localStorage for parity with cookie mock
@@ -163,6 +177,20 @@ export function getUid2Cookie() { | |||
} | |||
} | |||
|
|||
export function removeUid2LocalStorage() { | |||
localStorage.removeItem(localStorageKeyName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks exact the same as removeValue
in UID2LocalStorageManager? Why we couldn't use that instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed!
|
||
export function setUid2LocalStorage(identity: any) { | ||
const value = JSON.stringify(identity); | ||
localStorage.setItem(localStorageKeyName, value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed!
src/mocks.ts
Outdated
|
||
export function getUid2LocalStorage() { | ||
const value = localStorage.getItem(localStorageKeyName); | ||
return value !== null ? JSON.parse(value) : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use loadIdentityFromLocalStorage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean, my current implementation seems like a bit of copy-paste work and would still mean tests would pass if the actual functionality broke (BAD).
Are you suggesting that this mocks file should have a
const uid2LocalStorageManager = new UID2LocalStorageManager();
and then be
export function getUid2LocalStorage() {
return uid2LocalStorageManager.loadIdentityFromLocalStorage();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we need them in the mocks.ts?
Could we do const uid2LocalStorageManager = new UID2LocalStorageManager();
in test file and just call uid2LocalStorageManager functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I think that makes sense. In fact I think I can actually use
const uid2StorageManager = new UID2StorageManager({});
then just do
uid2StorageManager.removeValues();
rather than having
removeUid2Cookie();
removeUid2LocalStorage();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some changes, though I still have getUid2LocalStorage()
as a mock so that it will return undefined
instead of null
. Alternatively, I could change loadIdentityFromLocalStorage()
to return Uid2Identity | undefined
instead of Uid2Identity | null
and then remove the need for getUid2LocalStorage()
I initially based it off loadIdentityFromCookie(): Uid2Identity | null
but I think maybe we can use undefined
instead of null
. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tbh I am getting a bit confused myself. - perhaps @lionell-pack-ttd could chime in next week. I see in getUid2Cookie() we can return undefined
(so this is what the tests expect), whereas in loadIdentityFromCookie() we can return null
. Would it make sense to just use null
or undefined
everywhere, across SDK, tests, cookies & local storage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are we achieving by switching from the original approach to loadIdentityFromLocalStorage
? Test infrastructure shouldn't rely on the things it's testing. If the goal is to test whether a value is set in local storage (or clear the value from local storage), then using the UID2LocalStorageManager to do that is a problem.
For example, with this approach, if I change UID2LocalStorageManager to this:
The tests all still pass, even though UID2LocalStorageManager doesn't use localStorage at all and would be horribly broken in a real context (it would never persist any data and always start as null on each page load).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the feedback Lionell, I've reverted the change so that I'm using getUid2LocalStorage
again. One thing I wanted to check with you again @lionell-pack-ttd - are you okay with the implementation of getUid2LocalStorage
?
Specifically, the : undefined
is necessary because the mock method getUid2Cookie
returns undefined
, and we have tests that run for both local storage and cookie that assert on toBeUndefined()
.
Other alternatives includes:
- Changing
getUid2Cookie
to returnnull
if!docCookie
, then changegetUid2LocalStorage
to returnnull
instead ofundefined
- Changing the assertions to be
toBeFalsy()
instead oftoBeUndefined()
. The issue with this is that it also includesfalse
,0
and''
which I don't particularly like - as far as I can tell that is the only way to covertoBeNull() || toBeUndefined()
I'd lean towards the first alternative unless you feel strongly about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first approach seems like a good one to me - but I don't really feel strongly here, whichever you think is best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've gone with this approach:
Changing getUid2Cookie to return null if !docCookie, then change getUid2LocalStorage to return null instead of undefined
src/uid2Sdk.ts
Outdated
@@ -17,6 +17,8 @@ import { | |||
isClientSideIdentityOptionsOrThrow, | |||
} from "./uid2ClientSideIdentityOptions"; | |||
import { bytesToBase64 } from "./uid2Base64"; | |||
import { UID2LocalStorageManager } from "./uid2LocalStorageManager"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this import is never used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed!
src/uid2StorageManager.ts
Outdated
@@ -0,0 +1,47 @@ | |||
import { UID2 } from "./uid2Sdk"; | |||
import { isValidIdentity, Uid2Identity } from "./Uid2Identity"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: isValidIdentity and UID2 is not used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed!
src/uid2StorageManager.ts
Outdated
this._localStorageManager = new UID2LocalStorageManager(); | ||
} | ||
|
||
public loadIdentityFromPreferredStorageWithFallback(): Uid2Identity | null { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably deserves some tests
- Clean up imports - Rename usage of loadIdentityFromPreferredStorageWithFallback - Return early in `setValue` method
|
||
testCookieAndLocalStorage(() => { | ||
describe("when getAdvertisingTokenAsync is called before init", () => { | ||
describe("when initialising with a valid identity", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we cover test cases for when initialising without identity? (which calls the loadIdentityWithFallback?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any test that testing if the refreshed token has been stored to the correct storage?
}); | ||
const p = uid2.getAdvertisingTokenAsync(); | ||
uid2.init({ identity: originalIdentity, useCookie: useCookie }); | ||
xhrMock.responseText = btoa( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe we could wrap xhrMock.responseText and xhrMock.onreadystatechange into a mockResponse(response)
function?
- Use localStorageManager methods in mocks or in tests directly - Remove ununsed imports
src/uid2StorageManager.ts
Outdated
import { Uid2Options } from "./Uid2Options"; | ||
|
||
export class UID2StorageManager { | ||
private _cookieManager: UID2CookieManager | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dropped you a DM about this - I think the coverage is showing as low on this file because of these | undefined
, and these values will always be set once the constructor has run.
Drop | undefined from managers inside UID2StorageManager and associted null coalescing. Was not necessary and gave confusing code coverage insights
This reverts commit f9d20ae.
useCookie
option forinit
.useCookie
is not provided)uid2StorageManager
to manage cookie vs local storageTests
testCookieAndLocalStorage
.