Skip to content

Commit 8a51588

Browse files
authored
chore: remove dependency on crypto module in common (#191)
* Revert "Add react-native-get-random-values" This reverts commit 12f2983. * Add [Eppo SDK] to logged message * Allow salt as argument to getPrecomputedConfiguration * Remove webpack exclusion of crypto module in the browser bundle, no longer necessary * Add to exports * Keep salt as optional, but require it if being used directly from the common sdk * Better to not throw an error
1 parent 8bd0229 commit 8a51588

12 files changed

+36
-101
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"buffer": "npm:@eppo/[email protected]",
7474
"js-base64": "^3.7.7",
7575
"pino": "^9.5.0",
76-
"react-native-get-random-values": "^1.11.0",
7776
"semver": "^7.5.4",
7877
"spark-md5": "^3.0.2",
7978
"uuid": "^8.3.2"

src/client/eppo-client-with-bandits.spec.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as base64 from 'js-base64';
2+
13
import {
24
readMockUFCResponse,
35
MOCK_BANDIT_MODELS_RESPONSE_FILE,
@@ -24,7 +26,7 @@ import {
2426
} from '../flag-evaluation-details-builder';
2527
import FetchHttpClient from '../http-client';
2628
import { BanditVariation, BanditParameters, Flag } from '../interfaces';
27-
import { attributeEncodeBase64, setSaltOverrideForTests } from '../obfuscation';
29+
import { attributeEncodeBase64 } from '../obfuscation';
2830
import { Attributes, BanditActions, ContextAttributes } from '../types';
2931

3032
import EppoClient, { IAssignmentDetails } from './eppo-client';
@@ -658,10 +660,12 @@ describe('EppoClient Bandits E2E test', () => {
658660
subjectAttributes: ContextAttributes,
659661
banditActions: Record<string, BanditActions>,
660662
): IPrecomputedConfiguration {
663+
const salt = base64.fromUint8Array(new Uint8Array([101, 112, 112, 111]));
661664
const precomputedResults = client.getPrecomputedConfiguration(
662665
subjectKey,
663666
subjectAttributes,
664667
banditActions,
668+
salt,
665669
);
666670

667671
const { precomputed } = JSON.parse(precomputedResults) as IConfigurationWire;
@@ -672,14 +676,6 @@ describe('EppoClient Bandits E2E test', () => {
672676
}
673677

674678
describe('obfuscated results', () => {
675-
beforeEach(() => {
676-
setSaltOverrideForTests(new Uint8Array([101, 112, 112, 111])); // e p p o => "ZXBwbw=="
677-
});
678-
679-
afterAll(() => {
680-
setSaltOverrideForTests(null);
681-
});
682-
683679
it('obfuscates precomputed bandits', () => {
684680
const bannerBanditFlagMd5 = '3ac89e06235484aa6f2aec8c33109a02';
685681
const brandAffinityB64 = 'YnJhbmRfYWZmaW5pdHk=';

src/client/eppo-client.spec.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as base64 from 'js-base64';
12
import { times } from 'lodash';
23
import * as td from 'testdouble';
34

@@ -23,12 +24,15 @@ import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.stor
2324
import { MAX_EVENT_QUEUE_SIZE, DEFAULT_POLL_INTERVAL_MS, POLL_JITTER_PCT } from '../constants';
2425
import { decodePrecomputedFlag } from '../decoding';
2526
import { Flag, ObfuscatedFlag, VariationType } from '../interfaces';
26-
import { getMD5Hash, setSaltOverrideForTests } from '../obfuscation';
27+
import { getMD5Hash } from '../obfuscation';
2728
import { AttributeType } from '../types';
2829

2930
import EppoClient, { FlagConfigurationRequestParameters, checkTypeMatch } from './eppo-client';
3031
import { initConfiguration } from './test-utils';
3132

33+
// Use a known salt to produce deterministic hashes
34+
const salt = base64.fromUint8Array(new Uint8Array([7, 53, 17, 78]));
35+
3236
describe('EppoClient E2E test', () => {
3337
global.fetch = jest.fn(() => {
3438
const ufc = readMockUFCResponse(MOCK_UFC_RESPONSE_FILE);
@@ -215,12 +219,8 @@ describe('EppoClient E2E test', () => {
215219
client = new EppoClient({ flagConfigurationStore: storage });
216220
});
217221

218-
afterEach(() => {
219-
setSaltOverrideForTests(null);
220-
});
221-
222222
it('skips disabled flags', () => {
223-
const encodedPrecomputedWire = client.getPrecomputedConfiguration('subject', {});
223+
const encodedPrecomputedWire = client.getPrecomputedConfiguration('subject', {}, {}, salt);
224224
const { precomputed } = JSON.parse(encodedPrecomputedWire) as IConfigurationWire;
225225
if (!precomputed) {
226226
fail('Precomputed data not in Configuration response');
@@ -231,7 +231,6 @@ describe('EppoClient E2E test', () => {
231231

232232
expect(precomputedResponse).toBeTruthy();
233233
const precomputedFlags = precomputedResponse?.flags ?? {};
234-
const salt = precomputedResponse.salt;
235234

236235
expect(Object.keys(precomputedFlags)).toHaveLength(2);
237236
expect(Object.keys(precomputedFlags)).toContain(getMD5Hash('anotherFlag', salt));
@@ -240,15 +239,14 @@ describe('EppoClient E2E test', () => {
240239
});
241240

242241
it('evaluates and returns assignments', () => {
243-
const encodedPrecomputedWire = client.getPrecomputedConfiguration('subject', {});
242+
const encodedPrecomputedWire = client.getPrecomputedConfiguration('subject', {}, {}, salt);
244243
const { precomputed } = JSON.parse(encodedPrecomputedWire) as IConfigurationWire;
245244
if (!precomputed) {
246245
fail('Precomputed data not in Configuration response');
247246
}
248247
const precomputedResponse = JSON.parse(
249248
precomputed.response,
250249
) as IObfuscatedPrecomputedConfigurationResponse;
251-
const salt = precomputedResponse.salt;
252250

253251
expect(precomputedResponse).toBeTruthy();
254252
const precomputedFlags = precomputedResponse?.flags ?? {};
@@ -259,10 +257,7 @@ describe('EppoClient E2E test', () => {
259257
});
260258

261259
it('obfuscates assignments', () => {
262-
// Use a known salt to produce deterministic hashes
263-
setSaltOverrideForTests(new Uint8Array([7, 53, 17, 78]));
264-
265-
const encodedPrecomputedWire = client.getPrecomputedConfiguration('subject', {}, {});
260+
const encodedPrecomputedWire = client.getPrecomputedConfiguration('subject', {}, {}, salt);
266261
const { precomputed } = JSON.parse(encodedPrecomputedWire) as IConfigurationWire;
267262
if (!precomputed) {
268263
fail('Precomputed data not in Configuration response');

src/client/eppo-client.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,12 +866,14 @@ export default class EppoClient {
866866
*
867867
* @param subjectKey an identifier of the experiment subject, for example a user ID.
868868
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
869-
* @param banditActions
869+
* @param banditActions optional attributes associated with the bandit actions
870+
* @param salt a salt to use for obfuscation
870871
*/
871872
getPrecomputedConfiguration(
872873
subjectKey: string,
873874
subjectAttributes: Attributes | ContextAttributes = {},
874875
banditActions: Record<FlagKey, BanditActions> = {},
876+
salt?: string,
875877
): string {
876878
const configDetails = this.getConfigDetails();
877879

@@ -890,6 +892,7 @@ export default class EppoClient {
890892
subjectKey,
891893
flags,
892894
bandits,
895+
salt ?? '', // no salt if not provided
893896
subjectContextualAttributes,
894897
configDetails.configEnvironment,
895898
);

src/configuration.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ import {
55
IPrecomputedBandit,
66
PrecomputedFlag,
77
} from './interfaces';
8-
import {
9-
generateSalt,
10-
obfuscatePrecomputedBanditMap,
11-
obfuscatePrecomputedFlags,
12-
} from './obfuscation';
8+
import { obfuscatePrecomputedBanditMap, obfuscatePrecomputedFlags } from './obfuscation';
139
import { ContextAttributes, FlagKey, HashedFlagKey } from './types';
1410

1511
// Base interface for all configuration responses
@@ -69,13 +65,15 @@ export class PrecomputedConfiguration implements IPrecomputedConfiguration {
6965
subjectKey: string,
7066
flags: Record<FlagKey, PrecomputedFlag>,
7167
bandits: Record<FlagKey, IPrecomputedBandit>,
68+
salt: string,
7269
subjectAttributes?: ContextAttributes,
7370
environment?: Environment,
7471
): IPrecomputedConfiguration {
7572
const response = new ObfuscatedPrecomputedConfigurationResponse(
7673
subjectKey,
7774
flags,
7875
bandits,
76+
salt,
7977
subjectAttributes,
8078
environment,
8179
);
@@ -132,12 +130,13 @@ export class ObfuscatedPrecomputedConfigurationResponse
132130
subjectKey: string,
133131
flags: Record<FlagKey, PrecomputedFlag>,
134132
bandits: Record<FlagKey, IPrecomputedBandit>,
133+
salt: string,
135134
subjectAttributes?: ContextAttributes,
136135
environment?: Environment,
137136
) {
138137
super(subjectKey, subjectAttributes, environment);
139138

140-
this.salt = generateSalt();
139+
this.salt = salt;
141140
this.bandits = obfuscatePrecomputedBanditMap(this.salt, bandits);
142141
this.flags = obfuscatePrecomputedFlags(this.salt, flags);
143142
}

src/index.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,15 @@ import EventDispatcher from './events/event-dispatcher';
4747
import NamedEventQueue from './events/named-event-queue';
4848
import NetworkStatusListener from './events/network-status-listener';
4949
import HttpClient from './http-client';
50-
import { PrecomputedFlag, Flag, ObfuscatedFlag, VariationType, FormatEnum } from './interfaces';
51-
import { setSaltOverrideForTests } from './obfuscation';
50+
import {
51+
PrecomputedFlag,
52+
Flag,
53+
ObfuscatedFlag,
54+
VariationType,
55+
FormatEnum,
56+
BanditParameters,
57+
BanditVariation,
58+
} from './interfaces';
5259
import {
5360
AttributeType,
5461
Attributes,
@@ -108,6 +115,8 @@ export {
108115
ContextAttributes,
109116
BanditSubjectAttributes,
110117
BanditActions,
118+
BanditVariation,
119+
BanditParameters,
111120
Subject,
112121
FormatEnum,
113122

@@ -129,6 +138,5 @@ export {
129138
PrecomputedFlag,
130139

131140
// Test helpers
132-
setSaltOverrideForTests,
133141
decodePrecomputedFlag,
134142
};

src/obfuscation.spec.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { decodeBase64, encodeBase64, generateSalt, setSaltOverrideForTests } from './obfuscation';
1+
import { decodeBase64, encodeBase64 } from './obfuscation';
22

33
describe('obfuscation', () => {
44
it('encodes strings to base64', () => {
@@ -27,14 +27,4 @@ describe('obfuscation', () => {
2727

2828
expect(decodeBase64('a8O8bW1lcnQ=')).toEqual('kümmert');
2929
});
30-
31-
describe('salt', () => {
32-
it('converts from bytes to base64 string', () => {
33-
const chars = new Uint8Array([101, 112, 112, 111]); // eppo
34-
setSaltOverrideForTests(chars);
35-
36-
const salt64 = generateSalt();
37-
expect(salt64).toEqual('ZXBwbw==');
38-
});
39-
});
4030
});

src/obfuscation.ts

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,9 @@
11
import base64 = require('js-base64');
22
import * as SparkMD5 from 'spark-md5';
33

4-
import { logger } from './application-logger';
54
import { IObfuscatedPrecomputedBandit, IPrecomputedBandit, PrecomputedFlag } from './interfaces';
65
import { Attributes, AttributeType, Base64String, MD5String } from './types';
76

8-
// Import randomBytes according to the environment
9-
let getRandomValues: (length: number) => Uint8Array;
10-
if (typeof window !== 'undefined' && window.crypto) {
11-
// Browser environment
12-
getRandomValues = (length: number) => window.crypto.getRandomValues(new Uint8Array(length));
13-
} else if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
14-
// React Native environment
15-
require('react-native-get-random-values');
16-
getRandomValues = (length: number) => {
17-
const array = new Uint8Array(length);
18-
return window.crypto.getRandomValues(array);
19-
};
20-
} else {
21-
// Node.js environment
22-
import('crypto')
23-
.then((crypto) => {
24-
getRandomValues = (length: number) => new Uint8Array(crypto.randomBytes(length));
25-
return;
26-
})
27-
.catch((error) => {
28-
logger.error('[Eppo SDK] Failed to load crypto module:', error);
29-
});
30-
}
31-
327
export function getMD5Hash(input: string, salt = ''): string {
338
return new SparkMD5().append(salt).append(input).end();
349
}
@@ -110,12 +85,3 @@ export function obfuscatePrecomputedFlags(
11085
});
11186
return response;
11287
}
113-
114-
let saltOverrideBytes: Uint8Array | null;
115-
export function setSaltOverrideForTests(salt: Uint8Array | null) {
116-
saltOverrideBytes = salt ? salt : null;
117-
}
118-
119-
export function generateSalt(length = 16): string {
120-
return base64.fromUint8Array(saltOverrideBytes ? saltOverrideBytes : getRandomValues(length));
121-
}

src/react-native-get-random-values.d.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

tsconfig.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
"outDir": "dist",
1010
"noImplicitAny": true,
1111
"strict": true,
12-
"strictPropertyInitialization": true,
12+
"strictPropertyInitialization": true
1313
},
14-
"include": [
15-
"src/**/*.ts"
16-
],
14+
"include": ["src/**/*.ts"],
1715
"exclude": [
1816
"node_modules",
1917
"dist",

0 commit comments

Comments
 (0)