Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
108 changes: 108 additions & 0 deletions packages/sdk/browser/contract-tests/entity/src/ClientEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { CreateInstanceParams, SDKConfigParams } from './ConfigParams';
import { makeLogger } from './makeLogger';
import TestHook from './TestHook';

interface ListenerEntry {
eventName: string;
handler: (...args: any[]) => void;
}

export const badCommandError = new Error('unsupported command');
export const malformedCommand = new Error('command was malformed');

Expand Down Expand Up @@ -45,6 +50,30 @@ function makeSdkConfig(options: SDKConfigParams, tag: string) {
cf.streamInitialReconnectDelay = maybeTime(options.streaming.initialRetryDelayMs);
}

if (options.dataSystem) {
if (options.dataSystem.initializers) {
options.dataSystem.initializers.forEach((initializer) => {
if (initializer.polling?.baseUri) {
cf.baseUri = initializer.polling.baseUri;
}
});
}
if (options.dataSystem.synchronizers) {
options.dataSystem.synchronizers.forEach((synchronizer) => {
if (synchronizer.streaming?.baseUri) {
cf.streamUri = synchronizer.streaming.baseUri;
cf.streaming = true;
cf.streamInitialReconnectDelay = maybeTime(synchronizer.streaming.initialRetryDelayMs);
if (!cf.baseUri) {
cf.baseUri = synchronizer.streaming.baseUri;
}
} else if (synchronizer.polling?.baseUri) {
cf.baseUri = synchronizer.polling.baseUri;
}
});
}
}

if (options.events) {
if (options.events.baseUri) {
cf.eventsUri = options.events.baseUri;
Expand Down Expand Up @@ -81,12 +110,18 @@ function makeDefaultInitialContext() {
}

export class ClientEntity {
private readonly _listeners = new Map<string, ListenerEntry>();

constructor(
private readonly _client: LDClient,
private readonly _logger: LDLogger,
) {}

close() {
this._listeners.forEach((entry) => {
this._client.off(entry.eventName, entry.handler);
});
this._listeners.clear();
this._client.close();
this._logger.info('Test ended');
}
Expand Down Expand Up @@ -186,6 +221,79 @@ export class ClientEntity {
this._client.flush();
return undefined;

case CommandType.RegisterFlagChangeListener: {
const p = params.registerFlagChangeListener;
if (!p) {
throw malformedCommand;
}
const eventName = p.flagKey ? `change:${p.flagKey}` : 'change';

const handler = (...args: any[]) => {
if (!p.flagKey && args.length >= 2) {
const flagKeys = args[1] as string[];
flagKeys.forEach((key) => {
fetch(p.callbackUri, {
method: 'POST',
body: JSON.stringify({ listenerId: p.listenerId, flagKey: key }),
}).catch(() => {});
});
return;
}
fetch(p.callbackUri, {
method: 'POST',
body: JSON.stringify({ listenerId: p.listenerId, flagKey: p.flagKey }),
}).catch(() => {});
};

this._listeners.set(p.listenerId, { eventName, handler });
this._client.on(eventName, handler);
return undefined;
}

case CommandType.RegisterFlagValueChangeListener: {
const p = params.registerFlagValueChangeListener;
if (!p) {
throw malformedCommand;
}
const eventName = `change:${p.flagKey}`;

let oldValue = this._client.variation(p.flagKey, p.defaultValue);

const handler = () => {
const newValue = this._client.variation(p.flagKey, p.defaultValue);
if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
const previousValue = oldValue;
oldValue = newValue;
fetch(p.callbackUri, {
method: 'POST',
body: JSON.stringify({
listenerId: p.listenerId,
flagKey: p.flagKey,
oldValue: previousValue,
newValue,
}),
}).catch(() => {});
}
};

this._listeners.set(p.listenerId, { eventName, handler });
this._client.on(eventName, handler);
return undefined;
}

case CommandType.UnregisterListener: {
const p = params.unregisterListener;
if (!p) {
throw malformedCommand;
}
const entry = this._listeners.get(p.listenerId);
if (entry) {
this._client.off(entry.eventName, entry.handler);
this._listeners.delete(p.listenerId);
}
return undefined;
}

default:
throw badCommandError;
}
Expand Down
23 changes: 23 additions & 0 deletions packages/sdk/browser/contract-tests/entity/src/CommandParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export enum CommandType {
ContextConvert = 'contextConvert',
ContextComparison = 'contextComparison',
SecureModeHash = 'secureModeHash',
RegisterFlagChangeListener = 'registerFlagChangeListener',
RegisterFlagValueChangeListener = 'registerFlagValueChangeListener',
UnregisterListener = 'unregisterListener',
}

export enum ValueType {
Expand All @@ -31,6 +34,9 @@ export interface CommandParams {
contextConvert?: ContextConvertParams;
contextComparison?: ContextComparisonPairParams;
secureModeHash?: SecureModeHashParams;
registerFlagChangeListener?: RegisterFlagChangeListenerParams;
registerFlagValueChangeListener?: RegisterFlagValueChangeListenerParams;
unregisterListener?: UnregisterListenerParams;
}

export interface EvaluateFlagParams {
Expand Down Expand Up @@ -137,6 +143,23 @@ export interface SecureModeHashResponse {
result: string;
}

export interface RegisterFlagChangeListenerParams {
listenerId: string;
flagKey: string;
callbackUri: string;
}

export interface RegisterFlagValueChangeListenerParams {
listenerId: string;
flagKey: string;
defaultValue: unknown;
callbackUri: string;
}

export interface UnregisterListenerParams {
listenerId: string;
}

export enum HookStage {
BeforeEvaluation = 'beforeEvaluation',
AfterEvaluation = 'afterEvaluation',
Expand Down
25 changes: 25 additions & 0 deletions packages/sdk/browser/contract-tests/entity/src/ConfigParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface SDKConfigParams {
clientSide?: SDKConfigClientSideParams;
hooks?: SDKConfigHooksParams;
wrapper?: SDKConfigWrapper;
dataSystem?: SDKConfigDataSystemParams;
}

export interface SDKConfigTLSParams {
Expand Down Expand Up @@ -87,4 +88,28 @@ export interface SDKConfigWrapper {
version: string;
}

export interface SDKConfigDataSystemParams {
initializers?: SDKDataSystemInitializerParams[];
synchronizers?: SDKDataSystemSynchronizerParams[];
}

export interface SDKDataSystemInitializerParams {
polling?: SDKDataSourcePollingParams;
}

export interface SDKDataSystemSynchronizerParams {
streaming?: SDKDataSourceStreamingParams;
polling?: SDKDataSourcePollingParams;
}

export interface SDKDataSourceStreamingParams {
baseUri?: string;
initialRetryDelayMs?: number;
}

export interface SDKDataSourcePollingParams {
baseUri?: string;
pollIntervalMs?: number;
}

export type HookStage = 'beforeEvaluation' | 'afterEvaluation';
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default class TestHarnessWebSocket {
'client-prereq-events',
'client-per-context-summaries',
'track-hooks',
'flag-change-listeners',
];

break;
Expand Down