Skip to content

Commit decdc0c

Browse files
authored
chore: confirm getPrecomputedAssignments functionality in node (#89)
* Add test for getPrecomputedAssignments * Update common dependency version again * Move precomputed test above bandits tests * Monkey patch getPrecomputedConfiguration * Allow salt to be an empty string if provided by the user * Update common sdk dependency * Fix lint warnings * v3.7.0-alpha.0
1 parent d235e52 commit decdc0c

File tree

6 files changed

+89
-12
lines changed

6 files changed

+89
-12
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/node-server-sdk",
3-
"version": "3.6.2",
3+
"version": "3.7.0-alpha.0",
44
"description": "Eppo node server SDK",
55
"main": "dist/index.js",
66
"files": [
@@ -29,7 +29,7 @@
2929
},
3030
"homepage": "https://github.com/Eppo-exp/node-server-sdk#readme",
3131
"dependencies": {
32-
"@eppo/js-client-sdk-common": "^4.6.3"
32+
"@eppo/js-client-sdk-common": "4.8.0-alpha.1"
3333
},
3434
"devDependencies": {
3535
"@google-cloud/storage": "^6.9.3",

src/index.spec.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import {
88
VariationType,
99
IBanditEvent,
1010
IBanditLogger,
11+
IConfigurationWire,
12+
ContextAttributes,
13+
Attributes,
14+
decodePrecomputedFlag,
15+
BanditParameters,
16+
BanditVariation,
1117
} from '@eppo/js-client-sdk-common';
12-
import { BanditParameters, BanditVariation } from '@eppo/js-client-sdk-common/dist/interfaces';
13-
import { ContextAttributes } from '@eppo/js-client-sdk-common/dist/types';
14-
import { Attributes } from '@eppo/js-client-sdk-common/src/types';
18+
import * as base64 from 'js-base64';
1519
import * as td from 'testdouble';
1620

1721
import apiServer, { TEST_BANDIT_API_KEY, TEST_SERVER_PORT } from '../test/mockApiServer';
@@ -258,6 +262,49 @@ describe('EppoClient E2E test', () => {
258262
});
259263
});
260264

265+
describe('get precomputed assignments', () => {
266+
it('obfuscates assignments', async () => {
267+
const client = await init({
268+
apiKey,
269+
baseUrl: `http://127.0.0.1:${TEST_SERVER_PORT}`,
270+
assignmentLogger: mockLogger,
271+
});
272+
const subjectAttributes = { id: 'zach' };
273+
const expectedAssignment = client.getStringAssignment(
274+
'new-user-onboarding',
275+
'subject',
276+
subjectAttributes,
277+
'default-value',
278+
);
279+
expect(expectedAssignment).toBe('purple');
280+
const salt = base64.fromUint8Array(new Uint8Array([7, 53, 17, 78]));
281+
const encodedPrecomputedWire = client.getPrecomputedConfiguration(
282+
'subject',
283+
subjectAttributes,
284+
{},
285+
salt,
286+
);
287+
const { precomputed } = JSON.parse(encodedPrecomputedWire) as IConfigurationWire;
288+
if (!precomputed) {
289+
fail('Precomputed data not in Configuration response');
290+
}
291+
const precomputedResponse = JSON.parse(precomputed.response);
292+
expect(precomputedResponse).toBeTruthy();
293+
expect(precomputedResponse.salt).toEqual('BzURTg==');
294+
const precomputedFlags = precomputedResponse?.flags ?? {};
295+
expect(Object.keys(precomputedFlags)).toContain('f0da9e751eb86ad80968df152390fa4f'); // 'new-user-onboarding', md5 hashed
296+
const decodedFirstFlag = decodePrecomputedFlag(
297+
precomputedFlags['f0da9e751eb86ad80968df152390fa4f'],
298+
);
299+
expect(decodedFirstFlag.flagKey).toEqual('f0da9e751eb86ad80968df152390fa4f');
300+
expect(decodedFirstFlag.variationType).toEqual(VariationType.STRING);
301+
expect(decodedFirstFlag.variationKey).toEqual('purple');
302+
expect(decodedFirstFlag.variationValue).toEqual('purple');
303+
expect(decodedFirstFlag.doLog).toEqual(false);
304+
expect(decodedFirstFlag.extraLogging).toEqual({});
305+
});
306+
});
307+
261308
describe('Shared Bandit Test Cases', () => {
262309
beforeAll(async () => {
263310
const dummyBanditLogger: IBanditLogger = {
@@ -367,6 +414,7 @@ describe('EppoClient E2E test', () => {
367414
client.useExpiringInMemoryBanditAssignmentCache(2);
368415

369416
// Let's say someone is rage refreshing - we want to log assignment only once
417+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
370418
for (const _ of Array(3).keys()) {
371419
client.getBanditAction(flagKey, bobKey, bobAttributes, bobActions, 'default');
372420
}

src/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import {
2+
Attributes,
3+
ContextAttributes,
24
EppoClient,
35
Flag,
46
FlagConfigurationRequestParameters,
57
MemoryOnlyConfigurationStore,
68
newDefaultEventDispatcher,
79
} from '@eppo/js-client-sdk-common';
810
import { BanditParameters, BanditVariation } from '@eppo/js-client-sdk-common/dist/interfaces';
9-
import { Event } from '@eppo/js-client-sdk-common/src/events/event-dispatcher';
11+
import { BanditActions, FlagKey } from '@eppo/js-client-sdk-common/dist/types';
12+
import Event from '@eppo/js-client-sdk-common/src/events/event';
1013

1114
import FileBackedNamedEventQueue from './events/file-backed-named-event-queue';
1215
import { IClientConfig } from './i-client-config';
1316
import { sdkName, sdkVersion } from './sdk-data';
17+
import { generateSalt } from './util';
1418

1519
export {
1620
IAssignmentDetails,
@@ -87,6 +91,23 @@ export async function init(config: IClientConfig): Promise<EppoClient> {
8791
// Fetch configurations (which will also start regular polling per requestConfiguration)
8892
await clientInstance.fetchFlagConfigurations();
8993

94+
// Monkey patch the function to use a generated salt if none is provided
95+
const originalGetPrecomputedConfiguration = clientInstance.getPrecomputedConfiguration;
96+
clientInstance.getPrecomputedConfiguration = (
97+
subjectKey: string,
98+
subjectAttributes: Attributes | ContextAttributes = {},
99+
banditActions: Record<FlagKey, BanditActions> = {},
100+
salt?: string,
101+
) => {
102+
return originalGetPrecomputedConfiguration.call(
103+
clientInstance,
104+
subjectKey,
105+
subjectAttributes,
106+
banditActions,
107+
salt ?? generateSalt(),
108+
);
109+
};
110+
90111
return clientInstance;
91112
}
92113

src/util.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { randomBytes } from 'crypto';
2+
3+
import * as base64 from 'js-base64';
4+
15
/* Returns elements from arr until the predicate returns false. */
26
export function takeWhile<T>(arr: T[], predicate: (item: T) => boolean): T[] {
37
const result = [];
@@ -9,3 +13,8 @@ export function takeWhile<T>(arr: T[], predicate: (item: T) => boolean): T[] {
913
}
1014
return result;
1115
}
16+
17+
export function generateSalt(length = 16): string {
18+
const getRandomValues = (length: number) => new Uint8Array(randomBytes(length));
19+
return base64.fromUint8Array(getRandomValues(length));
20+
}

test/testHelpers.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as fs from 'fs';
22

3-
import { VariationType, AttributeType } from '@eppo/js-client-sdk-common';
4-
import { ContextAttributes } from '@eppo/js-client-sdk-common/dist/types';
3+
import { VariationType, AttributeType, ContextAttributes } from '@eppo/js-client-sdk-common';
54

65
export const TEST_DATA_DIR = './test/data/ufc/';
76
export const ASSIGNMENT_TEST_DATA_DIR = TEST_DATA_DIR + 'tests/';

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,10 +460,10 @@
460460
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
461461
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
462462

463-
"@eppo/js-client-sdk-common@^4.6.3":
464-
version "4.6.3"
465-
resolved "https://registry.yarnpkg.com/@eppo/js-client-sdk-common/-/js-client-sdk-common-4.6.3.tgz#89267a1c247bc04725b7701cdfb573613c133ddc"
466-
integrity sha512-e2nSvzONjqUiAYUjBMIIk1jWuKPOmBl5AlbiNjsoJAd6dZKN1RWpTmy83hNk6ff/syjAEWTcplls2cU37VbyiQ==
463+
"@eppo/js-client-sdk-common@4.8.0-alpha.1":
464+
version "4.8.0-alpha.1"
465+
resolved "https://registry.yarnpkg.com/@eppo/js-client-sdk-common/-/js-client-sdk-common-4.8.0-alpha.1.tgz#3c5c5f9fad5aa4ba582f0f9ea09b5ee60456e4cf"
466+
integrity sha512-sB/3D/aj4TvTw44mxb2hWNGB22FrJzvHrQxQ6eGGeTBZkrBJz4wnl3WcvVjiiNPKBJpKxYGs5C0kM/++IHfH6A==
467467
dependencies:
468468
buffer "npm:@eppo/[email protected]"
469469
js-base64 "^3.7.7"

0 commit comments

Comments
 (0)