Skip to content

feat: Add ODP/ATS VuidManager #776

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

Merged
merged 35 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e2e0960
Add VuidManager tests and empty class file
mikechu-optimizely Aug 9, 2022
72aa5e8
Move tests location, Typescript, & jest
mikechu-optimizely Aug 9, 2022
448984e
Initial implementation VuidManager (1 test WIP)
mikechu-optimizely Aug 9, 2022
31cdc94
Add persistence cache
mikechu-optimizely Aug 11, 2022
006db60
Implement cache into VuidManager
mikechu-optimizely Aug 11, 2022
48e38db
Correct persistentKeyValueCache's types
mikechu-optimizely Aug 11, 2022
1c57e42
Update persistent cache func descriptions
mikechu-optimizely Aug 11, 2022
985807d
Adjust VuidManager tests
mikechu-optimizely Aug 11, 2022
c8d684b
Refactor class name of InMemoryAsyncStorageCache
mikechu-optimizely Aug 11, 2022
af52684
Add React Native and browser persistence classes
mikechu-optimizely Aug 11, 2022
9f626bb
Add React Native and browser persistence classes
mikechu-optimizely Aug 12, 2022
59951ef
Testing for ReactNativeAsyncStorageCache
mikechu-optimizely Aug 12, 2022
47324b8
More testing for ReactNativeAsyncStorageCache
mikechu-optimizely Aug 15, 2022
da44358
Merge branch 'master' into mike/ats-vuid
mikechu-optimizely Aug 15, 2022
862e340
Testing for InMemoryAsyncStorageCache
mikechu-optimizely Aug 15, 2022
0c84ccd
Update to a getter instance()
mikechu-optimizely Aug 15, 2022
d047abe
BrowserAsyncStorageCache tests
mikechu-optimizely Aug 15, 2022
77d507f
Bug fix VuidManager and tests
mikechu-optimizely Aug 15, 2022
a27d26b
Add @react-native-async-storage/async-storage
mikechu-optimizely Aug 15, 2022
4bfbff5
Update packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts
mikechu-optimizely Aug 16, 2022
10b4764
Add test around and correct load()
mikechu-optimizely Aug 16, 2022
0962dc3
Delete inMemoryAsyncStorageCache
mikechu-optimizely Aug 17, 2022
30a42e7
Add dev packages react-native-async-storage & jest-ts-auto-mock
mikechu-optimizely Aug 17, 2022
0284883
Refactor to use async/await + simplifying returns
mikechu-optimizely Aug 17, 2022
507b961
Change to ts-mockito for stubs/mocks
mikechu-optimizely Aug 17, 2022
e279d2a
Add _reset() for testing
mikechu-optimizely Aug 17, 2022
37c338b
Refactor VUID Manager tests using ts-mockito
mikechu-optimizely Aug 17, 2022
b36a359
Un-nest vuid stored in cache
mikechu-optimizely Aug 17, 2022
13294b7
Add JSDoc
mikechu-optimizely Aug 17, 2022
ed1f8eb
Corrections to accessibility and interface
mikechu-optimizely Aug 17, 2022
90ce31a
Corrections to tests
mikechu-optimizely Aug 17, 2022
9984fc2
Merge branch 'master' into mike/ats-vuid
mikechu-optimizely Aug 17, 2022
1e450a1
Update to package-lock
mikechu-optimizely Aug 18, 2022
44543f3
Fix to AsyncStorageCache types and tests
mikechu-optimizely Aug 18, 2022
a1a1eee
Innocuous formatting change to force build
mikechu-optimizely Aug 18, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import PersistentKeyValueCache from './persistentKeyValueCache';

export default class BrowserAsyncStorageCache implements PersistentKeyValueCache {
async contains(key: string): Promise<boolean> {
return localStorage.getItem(key) !== null;
}

async get(key: string): Promise<string | null> {
return localStorage.getItem(key);
}

async remove(key: string): Promise<boolean> {
if (await this.contains(key)) {
localStorage.removeItem(key);
return true;
} else {
return false;
}
}

async set(key: string, val: string): Promise<void> {
return localStorage.setItem(key, val);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* An Interface to implement a persistent key value cache which supports strings as keys and value objects.
*/
export default interface PersistentKeyValueCache {
/**
* Checks if a key exists in the cache
* @param key
* Resolves promise with
* 1. true if the key exists
* 2. false if the key does not exist
* Rejects the promise in case of an error
*/
contains(key: string): Promise<boolean>;

/**
* Returns value stored against a key or undefined if not found.
* @param key
* @returns
* Resolves promise with
* 1. object as value if found.
* 2. undefined if the key does not exist in the cache.
* Rejects the promise in case of an error
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(key: string): Promise<any>;

/**
* Removes the key value pair from cache.
* @param key *
* @returns
* Resolves promise with
* 1. true if key-value was removed or
* 2. false if key not found
* Rejects the promise in case of an error
*/
remove(key: string): Promise<boolean>;

/**
* Stores any object in the persistent cache against a key
* @param key
* @param val
* @returns
* Resolves promise without a value if successful
* Rejects the promise in case of an error
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set(key: string, val: any): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2020, 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import AsyncStorage from '@react-native-async-storage/async-storage';
import PersistentKeyValueCache from './persistentKeyValueCache';

export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache {
async contains(key: string): Promise<boolean> {
return await AsyncStorage.getItem(key) !== null;
}

async get(key: string): Promise<string | null> {
return await AsyncStorage.getItem(key);
}

async remove(key: string): Promise<boolean> {
if (await this.contains(key)) {
await AsyncStorage.removeItem(key);
return true;
}
return false;
}

set(key: string, val: string): Promise<void> {
return AsyncStorage.setItem(key, val);
}
}
136 changes: 136 additions & 0 deletions packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* Copyright 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { uuid } from '../../utils/fns';
import PersistentKeyValueCache from '../key_value_cache/persistentKeyValueCache';

export interface IVuidManager {
readonly vuid: string;
}

/**
* Manager for creating, persisting, and retrieving a Visitor Unique Identifier
*/
export class VuidManager implements IVuidManager {
/**
* Unique key used within the persistent value cache against which to
* store the VUID
* @private
*/
private _keyForVuid = 'optimizely-vuid';

/**
* Prefix used as part of the VUID format
* @private
*/
private readonly _prefix: string = `vuid_`;

/**
* Current VUID value being used
* @private
*/
private _vuid: string;

/**
* Get the current VUID value being used
*/
public get vuid(): string {
return this._vuid;
}

private constructor() {
this._vuid = '';
}

/**
* Instance of the VUID Manager
* @private
*/
private static _instance: VuidManager;

/**
* Gets the current instance of the VUID Manager, initializing if needed
* @param cache Caching mechanism to use for persisting the VUID outside working memory *
* @returns An instance of VuidManager
*/
public static async instance(cache: PersistentKeyValueCache): Promise<VuidManager> {
if (!this._instance) {
this._instance = new VuidManager();
}

if (!this._instance._vuid) {
await this._instance.load(cache);
}

return this._instance;
}

/**
* Attempts to load a VUID from persistent cache or generates a new VUID
* @param cache Caching mechanism to use for persisting the VUID outside working memory
* @returns Current VUID stored in the VuidManager
*/
private async load(cache: PersistentKeyValueCache): Promise<string> {
const cachedValue = await cache.get(this._keyForVuid);
if (cachedValue && this.isVuid(cachedValue)) {
this._vuid = cachedValue;
} else {
this._vuid = this.makeVuid();
await this.save(this._vuid, cache);
}

return this._vuid;
}

/**
* Creates a new VUID
* @returns A new visitor unique identifier
*/
private makeVuid(): string {
const maxLength = 32; // required by ODP server

// make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details.
const uuidV4 = uuid();
const formatted = uuidV4.replace(/-/g, '').toLowerCase();
const vuidFull = `${(this._prefix)}${formatted}`;

return (vuidFull.length <= maxLength) ? vuidFull : vuidFull.substring(0, maxLength);
}

/**
* Saves a VUID to a persistent cache
* @param vuid VUID to be stored
* @param cache Caching mechanism to use for persisting the VUID outside working memory
*/
private async save(vuid: string, cache: PersistentKeyValueCache): Promise<void> {
await cache.set(this._keyForVuid, vuid);
}

/**
* Validates the format of a Visitor Unique Identifier
* @param vuid VistorId to check
* @returns *true* if the VisitorId is valid otherwise *false* for invalid
*/
private isVuid = (vuid: string): boolean => vuid.startsWith(this._prefix);

/**
* Function used in unit testing to reset the VuidManager
* **Important**: This should not to be used in production code
*/
private static _reset(): void {
this._instance._vuid = '';
}
}
Loading