Skip to content

Commit cb062eb

Browse files
committed
chore: inspect field to determine whether config is obfuscated
1 parent f6c3d4f commit cb062eb

File tree

5 files changed

+49
-15
lines changed

5 files changed

+49
-15
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/js-client-sdk-common",
3-
"version": "4.9.0",
3+
"version": "4.9.1",
44
"description": "Common library for Eppo JavaScript SDKs (web, react native, and node)",
55
"main": "dist/index.js",
66
"files": [

src/client/eppo-client.spec.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import * as td from 'testdouble';
44

55
import {
66
ASSIGNMENT_TEST_DATA_DIR,
7+
getTestAssignments,
78
IAssignmentTestCase,
89
MOCK_UFC_RESPONSE_FILE,
910
OBFUSCATED_MOCK_UFC_RESPONSE_FILE,
10-
SubjectTestCase,
11-
getTestAssignments,
1211
readMockUFCResponse,
12+
SubjectTestCase,
1313
testCasesByFileName,
1414
validateTestAssignments,
1515
} from '../../test/testHelpers';
@@ -21,13 +21,13 @@ import {
2121
} from '../configuration';
2222
import { IConfigurationStore } from '../configuration-store/configuration-store';
2323
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store';
24-
import { MAX_EVENT_QUEUE_SIZE, DEFAULT_POLL_INTERVAL_MS, POLL_JITTER_PCT } from '../constants';
24+
import { DEFAULT_POLL_INTERVAL_MS, MAX_EVENT_QUEUE_SIZE, POLL_JITTER_PCT } from '../constants';
2525
import { decodePrecomputedFlag } from '../decoding';
26-
import { Flag, ObfuscatedFlag, VariationType } from '../interfaces';
26+
import { Flag, FormatEnum, ObfuscatedFlag, VariationType } from '../interfaces';
2727
import { getMD5Hash } from '../obfuscation';
2828
import { AttributeType } from '../types';
2929

30-
import EppoClient, { FlagConfigurationRequestParameters, checkTypeMatch } from './eppo-client';
30+
import EppoClient, { checkTypeMatch, FlagConfigurationRequestParameters } from './eppo-client';
3131
import { initConfiguration } from './test-utils';
3232

3333
// Use a known salt to produce deterministic hashes
@@ -428,6 +428,7 @@ describe('EppoClient E2E test', () => {
428428
const mockLogger = td.object<IAssignmentLogger>();
429429

430430
storage.setEntries({ [flagKey]: mockFlag });
431+
storage.setFormat(FormatEnum.SERVER)
431432
const client = new EppoClient({ flagConfigurationStore: storage });
432433
client.setAssignmentLogger(mockLogger);
433434

src/client/eppo-client.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import { LRUInMemoryAssignmentCache } from '../cache/lru-in-memory-assignment-ca
1515
import { NonExpiringInMemoryAssignmentCache } from '../cache/non-expiring-in-memory-cache-assignment';
1616
import { TLRUInMemoryAssignmentCache } from '../cache/tlru-in-memory-assignment-cache';
1717
import {
18-
IConfigurationWire,
1918
ConfigurationWireV1,
19+
IConfigurationWire,
2020
IPrecomputedConfiguration,
2121
PrecomputedConfiguration,
2222
} from '../configuration';
@@ -27,6 +27,7 @@ import {
2727
DEFAULT_POLL_CONFIG_REQUEST_RETRIES,
2828
DEFAULT_POLL_INTERVAL_MS,
2929
DEFAULT_REQUEST_TIMEOUT_MS,
30+
OBFUSCATED_FORMATS,
3031
} from '../constants';
3132
import { decodeFlag } from '../decoding';
3233
import { EppoValue } from '../eppo_value';
@@ -46,6 +47,7 @@ import {
4647
BanditVariation,
4748
ConfigDetails,
4849
Flag,
50+
getFormatFromString,
4951
IPrecomputedBandit,
5052
ObfuscatedFlag,
5153
PrecomputedFlag,
@@ -101,6 +103,9 @@ export type EppoClientParameters = {
101103
banditVariationConfigurationStore?: IConfigurationStore<BanditVariation[]>;
102104
banditModelConfigurationStore?: IConfigurationStore<BanditParameters>;
103105
configurationRequestParameters?: FlagConfigurationRequestParameters;
106+
/**
107+
* @deprecated obfuscation is determined by inspecting the `format` field of the UFC response.
108+
*/
104109
isObfuscated?: boolean;
105110
};
106111

@@ -121,7 +126,7 @@ export default class EppoClient {
121126
private assignmentCache?: AssignmentCache;
122127
// whether to suppress any errors and return default values instead
123128
private isGracefulFailureMode = true;
124-
private isObfuscated: boolean;
129+
private expectObfuscated: boolean;
125130
private requestPoller?: IPoller;
126131
private readonly evaluator = new Evaluator();
127132

@@ -138,7 +143,18 @@ export default class EppoClient {
138143
this.banditVariationConfigurationStore = banditVariationConfigurationStore;
139144
this.banditModelConfigurationStore = banditModelConfigurationStore;
140145
this.configurationRequestParameters = configurationRequestParameters;
141-
this.isObfuscated = isObfuscated;
146+
this.expectObfuscated = isObfuscated;
147+
}
148+
149+
private isObfuscated() {
150+
const configFormat = getFormatFromString(this.flagConfigurationStore.getFormat());
151+
const configObfuscated = OBFUSCATED_FORMATS.includes(configFormat);
152+
if (configObfuscated !== this.expectObfuscated) {
153+
logger.warn(
154+
`[Eppo SDK] configuration obfuscation [${configObfuscated}] does not match expected [${this.expectObfuscated}]`,
155+
);
156+
}
157+
return configObfuscated;
142158
}
143159

144160
setConfigurationRequestParameters(
@@ -188,8 +204,12 @@ export default class EppoClient {
188204
}
189205

190206
// noinspection JSUnusedGlobalSymbols
207+
/**
208+
* @deprecated The client determines whether the configuration is obfuscated by inspection
209+
* @param isObfuscated
210+
*/
191211
setIsObfuscated(isObfuscated: boolean) {
192-
this.isObfuscated = isObfuscated;
212+
this.expectObfuscated = isObfuscated;
193213
}
194214

195215
async fetchFlagConfigurations() {
@@ -854,7 +874,7 @@ export default class EppoClient {
854874
configDetails,
855875
subjectKey,
856876
subjectAttributes,
857-
this.isObfuscated,
877+
this.isObfuscated(),
858878
);
859879

860880
// allocationKey is set along with variation when there is a result. this check appeases typescript below
@@ -993,15 +1013,16 @@ export default class EppoClient {
9931013
);
9941014
}
9951015

1016+
const isObfuscated = this.isObfuscated();
9961017
const result = this.evaluator.evaluateFlag(
9971018
flag,
9981019
configDetails,
9991020
subjectKey,
10001021
subjectAttributes,
1001-
this.isObfuscated,
1022+
isObfuscated,
10021023
expectedVariationType,
10031024
);
1004-
if (this.isObfuscated) {
1025+
if (isObfuscated) {
10051026
// flag.key is obfuscated, replace with requested flag key
10061027
result.flagKey = flagKey;
10071028
}
@@ -1052,7 +1073,7 @@ export default class EppoClient {
10521073
}
10531074

10541075
private getFlag(flagKey: string): Flag | null {
1055-
return this.isObfuscated
1076+
return this.isObfuscated()
10561077
? this.getObfuscatedFlag(flagKey)
10571078
: this.flagConfigurationStore.get(flagKey);
10581079
}
@@ -1220,7 +1241,7 @@ export default class EppoClient {
12201241

12211242
private buildLoggerMetadata(): Record<string, unknown> {
12221243
return {
1223-
obfuscated: this.isObfuscated,
1244+
obfuscated: this.isObfuscated(),
12241245
sdkLanguage: 'javascript',
12251246
sdkLibVersion: LIB_VERSION,
12261247
};

src/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { FormatEnum } from './interfaces';
2+
13
export const DEFAULT_REQUEST_TIMEOUT_MS = 5000;
24
export const REQUEST_TIMEOUT_MILLIS = DEFAULT_REQUEST_TIMEOUT_MS; // for backwards compatibility
35
export const DEFAULT_POLL_INTERVAL_MS = 30000;
@@ -15,3 +17,8 @@ export const NULL_SENTINEL = 'EPPO_NULL';
1517
export const MAX_EVENT_QUEUE_SIZE = 100;
1618
export const BANDIT_ASSIGNMENT_SHARDS = 10000;
1719
export const DEFAULT_TLRU_TTL_MS = 600_000;
20+
21+
/**
22+
* UFC Configuration formats which are obfuscated.
23+
*/
24+
export const OBFUSCATED_FORMATS = [FormatEnum.CLIENT, FormatEnum.PRECOMPUTED];

src/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ export enum FormatEnum {
148148
CLIENT = 'CLIENT',
149149
PRECOMPUTED = 'PRECOMPUTED',
150150
}
151+
// enums are a little sticky to work with in conjunction with serialization.
152+
export function getFormatFromString(str: string | null): FormatEnum {
153+
// default to SERVER. Should always be set, but the ConfigurationStore allows null.
154+
return Object.values(FormatEnum).find((val) => val === str) ?? FormatEnum.SERVER;
155+
}
151156

152157
export type BasePrecomputedFlag = {
153158
flagKey?: string;

0 commit comments

Comments
 (0)