Skip to content

Commit 8feb1c0

Browse files
authored
[FSSDK-10867] update vuid manager interface (#954)
1 parent d20228b commit 8feb1c0

File tree

8 files changed

+136
-59
lines changed

8 files changed

+136
-59
lines changed

lib/index.browser.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ import Optimizely from './optimizely';
3333
import { IUserAgentParser } from './core/odp/user_agent_parser';
3434
import { getUserAgentParser } from './plugins/odp/user_agent_parser/index.browser';
3535
import * as commonExports from './common_exports';
36-
import { VuidManager } from './plugins/vuid_manager';
37-
import BrowserAsyncStorageCache from './plugins/key_value_cache/browserAsyncStorageCache';
38-
import { VuidManagerOptions } from './plugins/vuid_manager';
36+
import { vuidManager } from './plugins/vuid_manager/index.browser';
3937

4038
const logger = getLogger();
4139
logHelper.setLogHandler(loggerPlugin.createLogger());
@@ -136,11 +134,6 @@ const createInstance = function(config: Config): Client | null {
136134

137135
const { clientEngine, clientVersion } = config;
138136

139-
const cache = new BrowserAsyncStorageCache();
140-
const vuidManagerOptions: VuidManagerOptions = {
141-
enableVuid: config.vuidOptions?.enableVuid || false,
142-
}
143-
144137
const optimizelyOptions: OptimizelyOptions = {
145138
clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE,
146139
...config,
@@ -154,7 +147,8 @@ const createInstance = function(config: Config): Client | null {
154147
isValidInstance,
155148
odpManager: odpExplicitlyOff ? undefined
156149
: BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }),
157-
vuidManager: new VuidManager(cache, vuidManagerOptions, logger),
150+
vuidOptions: config.vuidOptions,
151+
vuidManager,
158152
};
159153

160154
const optimizely = new Optimizely(optimizelyOptions);

lib/index.react_native.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ import { OptimizelyDecideOption, Client, Config } from './shared_types';
2828
import { createHttpPollingDatafileManager } from './plugins/datafile_manager/react_native_http_polling_datafile_manager';
2929
import { BrowserOdpManager } from './plugins/odp_manager/index.browser';
3030
import * as commonExports from './common_exports';
31-
import { VuidManager, VuidManagerOptions } from './plugins/vuid_manager';
32-
import ReactNativeAsyncStorageCache from './plugins/key_value_cache/reactNativeAsyncStorageCache';
31+
import { vuidManager } from './plugins/vuid_manager/index.react_native';
3332
import 'fast-text-encoding';
3433
import 'react-native-get-random-values';
3534

@@ -109,11 +108,6 @@ const createInstance = function (config: Config): Client | null {
109108

110109
const { clientEngine, clientVersion } = config;
111110

112-
const cache = new ReactNativeAsyncStorageCache();
113-
const vuidManagerOptions: VuidManagerOptions = {
114-
enableVuid: config.vuidOptions?.enableVuid || false,
115-
}
116-
117111
const optimizelyOptions = {
118112
clientEngine: enums.REACT_NATIVE_JS_CLIENT_ENGINE,
119113
...config,
@@ -133,7 +127,8 @@ const createInstance = function (config: Config): Client | null {
133127
isValidInstance: isValidInstance,
134128
odpManager: odpExplicitlyOff ? undefined
135129
: BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }),
136-
vuidManager: new VuidManager(cache, vuidManagerOptions, logger),
130+
vuidOptions: config.vuidOptions,
131+
vuidManager,
137132
};
138133

139134
// If client engine is react, convert it to react native.

lib/optimizely/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
FeatureVariableValue,
3838
OptimizelyDecision,
3939
Client,
40+
VuidOptions,
4041
} from '../shared_types';
4142
import { newErrorDecision } from '../optimizely_decision';
4243
import OptimizelyUserContext from '../optimizely_user_context';
@@ -98,6 +99,7 @@ export default class Optimizely implements Client {
9899
private eventProcessor: EventProcessor;
99100
private defaultDecideOptions: { [key: string]: boolean };
100101
protected odpManager?: IOdpManager;
102+
private vuidOptions?: VuidOptions;
101103
protected vuidManager?: IVuidManager;
102104
public notificationCenter: NotificationCenter;
103105

@@ -114,6 +116,7 @@ export default class Optimizely implements Client {
114116
this.isOptimizelyConfigValid = config.isValidInstance;
115117
this.logger = config.logger;
116118
this.odpManager = config.odpManager;
119+
this.vuidOptions = config.vuidOptions;
117120
this.vuidManager = config.vuidManager;
118121

119122
let decideOptionsArray = config.defaultDecideOptions ?? [];
@@ -183,7 +186,7 @@ export default class Optimizely implements Client {
183186
projectConfigManagerReadyPromise,
184187
eventProcessorStartedPromise,
185188
config.odpManager ? config.odpManager.onReady() : Promise.resolve(),
186-
config.vuidManager ? config.vuidManager?.initialize() : Promise.resolve(),
189+
config.vuidManager ? config.vuidManager.configure(this.vuidOptions ?? { enableVuid: false }) : Promise.resolve(),
187190
]).then(promiseResults => {
188191
// Only return status from project config promise because event processor promise does not return any status.
189192
return promiseResults[0];
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright 2024, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { VuidManager } from ".";
18+
import BrowserAsyncStorageCache from "../key_value_cache/browserAsyncStorageCache";
19+
20+
export const vuidManager = new VuidManager(new BrowserAsyncStorageCache());
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright 2024, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { VuidManager } from ".";
18+
import ReactNativeAsyncStorageCache from "../key_value_cache/reactNativeAsyncStorageCache";
19+
20+
export const vuidManager = new VuidManager(new ReactNativeAsyncStorageCache());

lib/plugins/vuid_manager/index.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { LogHandler, LogLevel } from '../../modules/logging';
17+
import { LogHandler } from '../../modules/logging';
1818
import { uuid } from '../../utils/fns';
1919
import PersistentKeyValueCache from '../key_value_cache/persistentKeyValueCache';
2020

@@ -37,7 +37,8 @@ export interface IVuidManager {
3737
* Initialize the VuidManager
3838
* @returns Promise that resolves when the VuidManager is initialized
3939
*/
40-
initialize(): Promise<void>;
40+
configure(options: VuidManagerOptions): Promise<unknown>;
41+
setLogger(logger: LogHandler): void;
4142
}
4243

4344
/**
@@ -48,7 +49,7 @@ export class VuidManager implements IVuidManager {
4849
* Handler for recording execution logs
4950
* @private
5051
*/
51-
private readonly logger: LogHandler;
52+
private logger?: LogHandler;
5253

5354
/**
5455
* Prefix used as part of the VUID format
@@ -97,25 +98,39 @@ export class VuidManager implements IVuidManager {
9798
*/
9899
private readonly cache: PersistentKeyValueCache;
99100

100-
constructor(cache: PersistentKeyValueCache, options: VuidManagerOptions, logger: LogHandler) {
101+
private waitPromise: Promise<unknown> = Promise.resolve();
102+
103+
constructor(cache: PersistentKeyValueCache, logger?: LogHandler) {
101104
this.cache = cache;
102-
this._vuidEnabled = options.enableVuid;
105+
this.logger = logger;
106+
}
107+
108+
setLogger(logger: LogHandler): void {
103109
this.logger = logger;
104110
}
105111

106112
/**
107-
* Initialize the VuidManager
108-
* @returns Promise that resolves when the VuidManager is initialized
113+
* Configures the VuidManager
114+
* @returns Promise that resolves when the VuidManager is configured
109115
*/
110-
async initialize(): Promise<void> {
111-
if (!this.vuidEnabled) {
112-
await this.cache.remove(this._keyForVuid);
113-
return;
116+
async configure(options: VuidManagerOptions): Promise<unknown> {
117+
const configureFn = async () => {
118+
this._vuidEnabled = options.enableVuid;
119+
120+
if (!this.vuidEnabled) {
121+
await this.cache.remove(this._keyForVuid);
122+
this._vuid = undefined;
123+
return;
124+
}
125+
126+
if (!this._vuid) {
127+
await this.load(this.cache);
128+
}
114129
}
115130

116-
if (!this._vuid) {
117-
await this.load(this.cache);
118-
}
131+
this.waitPromise = this.waitPromise.then(configureFn, configureFn);
132+
this.waitPromise.catch(() => {});
133+
return this.waitPromise;
119134
}
120135

121136
/**

lib/shared_types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ export interface OptimizelyOptions {
305305
userProfileService?: UserProfileService | null;
306306
defaultDecideOptions?: OptimizelyDecideOption[];
307307
odpManager?: IOdpManager;
308+
vuidOptions?: VuidOptions,
308309
vuidManager?: IVuidManager;
309310
notificationCenter: NotificationCenterImpl;
310311
}

tests/vuidManager.spec.ts

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { VuidManager } from '../lib/plugins/vuid_manager';
2020
import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache';
2121
import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito';
2222
import { LogHandler } from '../lib/modules/logging/models';
23+
import { resolvablePromise } from '../lib/utils/promise/resolvablePromise';
2324

2425
describe('VuidManager', () => {
2526
let mockCache: PersistentKeyValueCache<string>;
@@ -41,7 +42,7 @@ describe('VuidManager', () => {
4142
});
4243

4344
it('should make a VUID', async () => {
44-
const manager = new VuidManager(instance(mockCache), { enableVuid: true }, instance(mockLogger));
45+
const manager = new VuidManager(instance(mockCache));
4546

4647
const vuid = manager['makeVuid']();
4748

@@ -56,40 +57,68 @@ describe('VuidManager', () => {
5657
expect(VuidManager.isVuid('123')).toBe(false);
5758
});
5859

59-
it('should handle no valid optimizely-vuid in the cache', async () => {
60-
when(mockCache.get(anyString())).thenResolve(undefined);
61-
const manager = new VuidManager(instance(mockCache), { enableVuid: true }, instance(mockLogger));
62-
63-
await manager.initialize();
64-
65-
verify(mockCache.get(anyString())).once();
66-
verify(mockCache.set(anyString(), anything())).once();
67-
expect(VuidManager.isVuid(manager.vuid)).toBe(true);
68-
});
69-
70-
it('should create a new vuid if old VUID from cache is not valid', async () => {
71-
when(mockCache.get(anyString())).thenResolve('vuid-not-valid');
72-
const manager = new VuidManager(instance(mockCache), { enableVuid: true }, instance(mockLogger));
73-
await manager.initialize();
74-
75-
verify(mockCache.get(anyString())).once();
76-
verify(mockCache.set(anyString(), anything())).once();
77-
expect(VuidManager.isVuid(manager.vuid)).toBe(true);
60+
describe('when configure with enableVuid = true', () => {
61+
it('should handle no valid optimizely-vuid in the cache', async () => {
62+
when(mockCache.get(anyString())).thenResolve(undefined);
63+
const manager = new VuidManager(instance(mockCache))
64+
await manager.configure({ enableVuid: true });
65+
66+
verify(mockCache.get(anyString())).once();
67+
verify(mockCache.set(anyString(), anything())).once();
68+
expect(VuidManager.isVuid(manager.vuid)).toBe(true);
69+
});
70+
71+
it('should create a new vuid if old VUID from cache is not valid', async () => {
72+
when(mockCache.get(anyString())).thenResolve('vuid-not-valid');
73+
const manager = new VuidManager(instance(mockCache));
74+
await manager.configure({ enableVuid: true });
75+
76+
verify(mockCache.get(anyString())).once();
77+
verify(mockCache.set(anyString(), anything())).once();
78+
expect(VuidManager.isVuid(manager.vuid)).toBe(true);
79+
});
80+
81+
it('should never call remove when enableVuid is true', async () => {
82+
const manager = new VuidManager(instance(mockCache));
83+
await manager.configure({ enableVuid: true });
84+
85+
verify(mockCache.remove(anyString())).never();
86+
expect(VuidManager.isVuid(manager.vuid)).toBe(true);
87+
});
7888
});
7989

8090
it('should call remove when vuid is disabled', async () => {
81-
const manager = new VuidManager(instance(mockCache), { enableVuid: false }, instance(mockLogger));
82-
await manager.initialize();
91+
const manager = new VuidManager(instance(mockCache));
92+
await manager.configure({ enableVuid: false });
8393

8494
verify(mockCache.remove(anyString())).once();
8595
expect(manager.vuid).toBeUndefined();
8696
});
8797

88-
it('should never call remove when enableVuid is true', async () => {
89-
const manager = new VuidManager(instance(mockCache), { enableVuid: true }, instance(mockLogger));
90-
await manager.initialize();
98+
it('should sequence configure calls', async() => {
99+
const mockCache = mock<PersistentKeyValueCache>();
100+
when(mockCache.contains(anyString())).thenResolve(true);
101+
when(mockCache.get(anyString())).thenResolve('');
102+
103+
const removePromise = resolvablePromise<boolean>();
104+
when(mockCache.remove(anyString())).thenReturn(removePromise.promise);
105+
when(mockCache.set(anyString(), anything())).thenResolve();
106+
107+
const manager = new VuidManager(instance(mockCache));
108+
109+
// this should try to remove vuid, which should stay pending
110+
manager.configure({ enableVuid: false });
111+
112+
// this should try to get the vuid from store
113+
manager.configure({ enableVuid: true });
114+
verify(mockCache.get(anyString())).never();
115+
116+
removePromise.resolve(true);
117+
//ensure micro task queue is exhaused
118+
for(let i = 0; i < 100; i++) {
119+
await Promise.resolve();
120+
}
91121

92-
verify(mockCache.remove(anyString())).never();
93-
expect(VuidManager.isVuid(manager.vuid)).toBe(true);
122+
verify(mockCache.get(anyString())).once()
94123
});
95124
});

0 commit comments

Comments
 (0)