From cb062eb5021b274a340c39f6090bdd5aa5222a3a Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Thu, 13 Feb 2025 15:00:48 -0700 Subject: [PATCH 1/6] chore: inspect field to determine whether config is obfuscated --- package.json | 2 +- src/client/eppo-client.spec.ts | 11 +++++----- src/client/eppo-client.ts | 39 ++++++++++++++++++++++++++-------- src/constants.ts | 7 ++++++ src/interfaces.ts | 5 +++++ 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 2f6f66f..7116a90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@eppo/js-client-sdk-common", - "version": "4.9.0", + "version": "4.9.1", "description": "Common library for Eppo JavaScript SDKs (web, react native, and node)", "main": "dist/index.js", "files": [ diff --git a/src/client/eppo-client.spec.ts b/src/client/eppo-client.spec.ts index 707d567..d17f36d 100644 --- a/src/client/eppo-client.spec.ts +++ b/src/client/eppo-client.spec.ts @@ -4,12 +4,12 @@ import * as td from 'testdouble'; import { ASSIGNMENT_TEST_DATA_DIR, + getTestAssignments, IAssignmentTestCase, MOCK_UFC_RESPONSE_FILE, OBFUSCATED_MOCK_UFC_RESPONSE_FILE, - SubjectTestCase, - getTestAssignments, readMockUFCResponse, + SubjectTestCase, testCasesByFileName, validateTestAssignments, } from '../../test/testHelpers'; @@ -21,13 +21,13 @@ import { } from '../configuration'; import { IConfigurationStore } from '../configuration-store/configuration-store'; import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store'; -import { MAX_EVENT_QUEUE_SIZE, DEFAULT_POLL_INTERVAL_MS, POLL_JITTER_PCT } from '../constants'; +import { DEFAULT_POLL_INTERVAL_MS, MAX_EVENT_QUEUE_SIZE, POLL_JITTER_PCT } from '../constants'; import { decodePrecomputedFlag } from '../decoding'; -import { Flag, ObfuscatedFlag, VariationType } from '../interfaces'; +import { Flag, FormatEnum, ObfuscatedFlag, VariationType } from '../interfaces'; import { getMD5Hash } from '../obfuscation'; import { AttributeType } from '../types'; -import EppoClient, { FlagConfigurationRequestParameters, checkTypeMatch } from './eppo-client'; +import EppoClient, { checkTypeMatch, FlagConfigurationRequestParameters } from './eppo-client'; import { initConfiguration } from './test-utils'; // Use a known salt to produce deterministic hashes @@ -428,6 +428,7 @@ describe('EppoClient E2E test', () => { const mockLogger = td.object(); storage.setEntries({ [flagKey]: mockFlag }); + storage.setFormat(FormatEnum.SERVER) const client = new EppoClient({ flagConfigurationStore: storage }); client.setAssignmentLogger(mockLogger); diff --git a/src/client/eppo-client.ts b/src/client/eppo-client.ts index 20e8861..5c98f92 100644 --- a/src/client/eppo-client.ts +++ b/src/client/eppo-client.ts @@ -15,8 +15,8 @@ import { LRUInMemoryAssignmentCache } from '../cache/lru-in-memory-assignment-ca import { NonExpiringInMemoryAssignmentCache } from '../cache/non-expiring-in-memory-cache-assignment'; import { TLRUInMemoryAssignmentCache } from '../cache/tlru-in-memory-assignment-cache'; import { - IConfigurationWire, ConfigurationWireV1, + IConfigurationWire, IPrecomputedConfiguration, PrecomputedConfiguration, } from '../configuration'; @@ -27,6 +27,7 @@ import { DEFAULT_POLL_CONFIG_REQUEST_RETRIES, DEFAULT_POLL_INTERVAL_MS, DEFAULT_REQUEST_TIMEOUT_MS, + OBFUSCATED_FORMATS, } from '../constants'; import { decodeFlag } from '../decoding'; import { EppoValue } from '../eppo_value'; @@ -46,6 +47,7 @@ import { BanditVariation, ConfigDetails, Flag, + getFormatFromString, IPrecomputedBandit, ObfuscatedFlag, PrecomputedFlag, @@ -101,6 +103,9 @@ export type EppoClientParameters = { banditVariationConfigurationStore?: IConfigurationStore; banditModelConfigurationStore?: IConfigurationStore; configurationRequestParameters?: FlagConfigurationRequestParameters; + /** + * @deprecated obfuscation is determined by inspecting the `format` field of the UFC response. + */ isObfuscated?: boolean; }; @@ -121,7 +126,7 @@ export default class EppoClient { private assignmentCache?: AssignmentCache; // whether to suppress any errors and return default values instead private isGracefulFailureMode = true; - private isObfuscated: boolean; + private expectObfuscated: boolean; private requestPoller?: IPoller; private readonly evaluator = new Evaluator(); @@ -138,7 +143,18 @@ export default class EppoClient { this.banditVariationConfigurationStore = banditVariationConfigurationStore; this.banditModelConfigurationStore = banditModelConfigurationStore; this.configurationRequestParameters = configurationRequestParameters; - this.isObfuscated = isObfuscated; + this.expectObfuscated = isObfuscated; + } + + private isObfuscated() { + const configFormat = getFormatFromString(this.flagConfigurationStore.getFormat()); + const configObfuscated = OBFUSCATED_FORMATS.includes(configFormat); + if (configObfuscated !== this.expectObfuscated) { + logger.warn( + `[Eppo SDK] configuration obfuscation [${configObfuscated}] does not match expected [${this.expectObfuscated}]`, + ); + } + return configObfuscated; } setConfigurationRequestParameters( @@ -188,8 +204,12 @@ export default class EppoClient { } // noinspection JSUnusedGlobalSymbols + /** + * @deprecated The client determines whether the configuration is obfuscated by inspection + * @param isObfuscated + */ setIsObfuscated(isObfuscated: boolean) { - this.isObfuscated = isObfuscated; + this.expectObfuscated = isObfuscated; } async fetchFlagConfigurations() { @@ -854,7 +874,7 @@ export default class EppoClient { configDetails, subjectKey, subjectAttributes, - this.isObfuscated, + this.isObfuscated(), ); // allocationKey is set along with variation when there is a result. this check appeases typescript below @@ -993,15 +1013,16 @@ export default class EppoClient { ); } + const isObfuscated = this.isObfuscated(); const result = this.evaluator.evaluateFlag( flag, configDetails, subjectKey, subjectAttributes, - this.isObfuscated, + isObfuscated, expectedVariationType, ); - if (this.isObfuscated) { + if (isObfuscated) { // flag.key is obfuscated, replace with requested flag key result.flagKey = flagKey; } @@ -1052,7 +1073,7 @@ export default class EppoClient { } private getFlag(flagKey: string): Flag | null { - return this.isObfuscated + return this.isObfuscated() ? this.getObfuscatedFlag(flagKey) : this.flagConfigurationStore.get(flagKey); } @@ -1220,7 +1241,7 @@ export default class EppoClient { private buildLoggerMetadata(): Record { return { - obfuscated: this.isObfuscated, + obfuscated: this.isObfuscated(), sdkLanguage: 'javascript', sdkLibVersion: LIB_VERSION, }; diff --git a/src/constants.ts b/src/constants.ts index cac895f..14f31fe 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,5 @@ +import { FormatEnum } from './interfaces'; + export const DEFAULT_REQUEST_TIMEOUT_MS = 5000; export const REQUEST_TIMEOUT_MILLIS = DEFAULT_REQUEST_TIMEOUT_MS; // for backwards compatibility export const DEFAULT_POLL_INTERVAL_MS = 30000; @@ -15,3 +17,8 @@ export const NULL_SENTINEL = 'EPPO_NULL'; export const MAX_EVENT_QUEUE_SIZE = 100; export const BANDIT_ASSIGNMENT_SHARDS = 10000; export const DEFAULT_TLRU_TTL_MS = 600_000; + +/** + * UFC Configuration formats which are obfuscated. + */ +export const OBFUSCATED_FORMATS = [FormatEnum.CLIENT, FormatEnum.PRECOMPUTED]; diff --git a/src/interfaces.ts b/src/interfaces.ts index c4cbe94..9f22f99 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -148,6 +148,11 @@ export enum FormatEnum { CLIENT = 'CLIENT', PRECOMPUTED = 'PRECOMPUTED', } +// enums are a little sticky to work with in conjunction with serialization. +export function getFormatFromString(str: string | null): FormatEnum { + // default to SERVER. Should always be set, but the ConfigurationStore allows null. + return Object.values(FormatEnum).find((val) => val === str) ?? FormatEnum.SERVER; +} export type BasePrecomputedFlag = { flagKey?: string; From 8aee6089d9ee27416dee983292d43106c96dfc0b Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Thu, 13 Feb 2025 20:46:42 -0700 Subject: [PATCH 2/6] really fix tests --- src/client/eppo-client.spec.ts | 51 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/client/eppo-client.spec.ts b/src/client/eppo-client.spec.ts index d17f36d..4cb2997 100644 --- a/src/client/eppo-client.spec.ts +++ b/src/client/eppo-client.spec.ts @@ -45,6 +45,18 @@ describe('EppoClient E2E test', () => { }) as jest.Mock; const storage = new MemoryOnlyConfigurationStore(); + /** + * Use this helper instead of directly setting entries on the `storage` ConfigurationStore. + * This method ensures the format field is set as it is required for parsing. + * @param entries + */ + function setUnobfuscatedFlagEntries( + entries: Record, + ): Promise { + storage.setFormat(FormatEnum.SERVER); + return storage.setEntries(entries); + } + beforeAll(async () => { await initConfiguration(storage); }); @@ -88,7 +100,7 @@ describe('EppoClient E2E test', () => { let client: EppoClient; beforeAll(() => { - storage.setEntries({ [flagKey]: mockFlag }); + setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); client = new EppoClient({ flagConfigurationStore: storage }); td.replace(EppoClient.prototype, 'getAssignmentDetail', function () { @@ -144,7 +156,7 @@ describe('EppoClient E2E test', () => { describe('setLogger', () => { beforeAll(() => { - storage.setEntries({ [flagKey]: mockFlag }); + setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); }); it('Invokes logger for queued events', () => { @@ -192,7 +204,7 @@ describe('EppoClient E2E test', () => { describe('precomputed flags', () => { beforeAll(() => { - storage.setEntries({ + setUnobfuscatedFlagEntries({ [flagKey]: mockFlag, disabledFlag: { ...mockFlag, enabled: false }, anotherFlag: { @@ -424,11 +436,10 @@ describe('EppoClient E2E test', () => { ); }); - it('logs variation assignment and experiment key', () => { + it('logs variation assignment and experiment key', async () => { const mockLogger = td.object(); - storage.setEntries({ [flagKey]: mockFlag }); - storage.setFormat(FormatEnum.SERVER) + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); const client = new EppoClient({ flagConfigurationStore: storage }); client.setAssignmentLogger(mockLogger); @@ -450,11 +461,11 @@ describe('EppoClient E2E test', () => { expect(loggedAssignmentEvent.allocation).toEqual(mockFlag.allocations[0].key); }); - it('handles logging exception', () => { + it('handles logging exception', async () => { const mockLogger = td.object(); td.when(mockLogger.logAssignment(td.matchers.anything())).thenThrow(new Error('logging error')); - storage.setEntries({ [flagKey]: mockFlag }); + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); const client = new EppoClient({ flagConfigurationStore: storage }); client.setAssignmentLogger(mockLogger); @@ -469,8 +480,8 @@ describe('EppoClient E2E test', () => { expect(assignment).toEqual('variation-a'); }); - it('exports flag configuration', () => { - storage.setEntries({ [flagKey]: mockFlag }); + it('exports flag configuration', async () => { + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); const client = new EppoClient({ flagConfigurationStore: storage }); expect(client.getFlagConfigurations()).toEqual({ [flagKey]: mockFlag }); }); @@ -479,10 +490,10 @@ describe('EppoClient E2E test', () => { let client: EppoClient; let mockLogger: IAssignmentLogger; - beforeEach(() => { + beforeEach(async () => { mockLogger = td.object(); - storage.setEntries({ [flagKey]: mockFlag }); + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); client = new EppoClient({ flagConfigurationStore: storage }); client.setAssignmentLogger(mockLogger); }); @@ -537,7 +548,7 @@ describe('EppoClient E2E test', () => { }); it('logs for each unique flag', async () => { - await storage.setEntries({ + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag, 'flag-2': { ...mockFlag, @@ -564,10 +575,10 @@ describe('EppoClient E2E test', () => { expect(td.explain(mockLogger.logAssignment).callCount).toEqual(3); }); - it('logs twice for the same flag when allocations change', () => { + it('logs twice for the same flag when allocations change', async () => { client.useNonExpiringInMemoryAssignmentCache(); - storage.setEntries({ + setUnobfuscatedFlagEntries({ [flagKey]: { ...mockFlag, @@ -588,7 +599,7 @@ describe('EppoClient E2E test', () => { }); client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); - storage.setEntries({ + await setUnobfuscatedFlagEntries({ [flagKey]: { ...mockFlag, allocations: [ @@ -614,13 +625,13 @@ describe('EppoClient E2E test', () => { client.useNonExpiringInMemoryAssignmentCache(); // original configuration version - storage.setEntries({ [flagKey]: mockFlag }); + setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // log this assignment client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // cache hit, don't log // change the variation - storage.setEntries({ + setUnobfuscatedFlagEntries({ [flagKey]: { ...mockFlag, allocations: [ @@ -643,13 +654,13 @@ describe('EppoClient E2E test', () => { client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // cache hit, don't log // change the flag again, back to the original - storage.setEntries({ [flagKey]: mockFlag }); + setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // important: log this assignment client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // cache hit, don't log // change the allocation - storage.setEntries({ + setUnobfuscatedFlagEntries({ [flagKey]: { ...mockFlag, allocations: [ From e01f3b379f03d580b4e3071e509913e08d9c3aaf Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Thu, 13 Feb 2025 20:52:40 -0700 Subject: [PATCH 3/6] o(1) enum conversion --- src/interfaces.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 9f22f99..f694e25 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -148,10 +148,10 @@ export enum FormatEnum { CLIENT = 'CLIENT', PRECOMPUTED = 'PRECOMPUTED', } -// enums are a little sticky to work with in conjunction with serialization. + export function getFormatFromString(str: string | null): FormatEnum { // default to SERVER. Should always be set, but the ConfigurationStore allows null. - return Object.values(FormatEnum).find((val) => val === str) ?? FormatEnum.SERVER; + return FormatEnum[str as keyof typeof FormatEnum] ?? FormatEnum.SERVER; } export type BasePrecomputedFlag = { From 5ebe36e967e4e256132dcf737be6e8b3b696d6e1 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Tue, 18 Feb 2025 09:40:49 -0700 Subject: [PATCH 4/6] cache obfuscated checks and await in tests --- src/client/eppo-client.spec.ts | 24 ++++++++++++------------ src/client/eppo-client.ts | 32 +++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/client/eppo-client.spec.ts b/src/client/eppo-client.spec.ts index 4cb2997..a686351 100644 --- a/src/client/eppo-client.spec.ts +++ b/src/client/eppo-client.spec.ts @@ -99,8 +99,8 @@ describe('EppoClient E2E test', () => { describe('error encountered', () => { let client: EppoClient; - beforeAll(() => { - setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); + beforeAll(async () => { + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); client = new EppoClient({ flagConfigurationStore: storage }); td.replace(EppoClient.prototype, 'getAssignmentDetail', function () { @@ -155,8 +155,8 @@ describe('EppoClient E2E test', () => { }); describe('setLogger', () => { - beforeAll(() => { - setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); + beforeAll(async () => { + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); }); it('Invokes logger for queued events', () => { @@ -203,8 +203,8 @@ describe('EppoClient E2E test', () => { }); describe('precomputed flags', () => { - beforeAll(() => { - setUnobfuscatedFlagEntries({ + beforeAll(async () => { + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag, disabledFlag: { ...mockFlag, enabled: false }, anotherFlag: { @@ -578,7 +578,7 @@ describe('EppoClient E2E test', () => { it('logs twice for the same flag when allocations change', async () => { client.useNonExpiringInMemoryAssignmentCache(); - setUnobfuscatedFlagEntries({ + await setUnobfuscatedFlagEntries({ [flagKey]: { ...mockFlag, @@ -621,17 +621,17 @@ describe('EppoClient E2E test', () => { expect(td.explain(mockLogger.logAssignment).callCount).toEqual(2); }); - it('logs the same subject/flag/variation after two changes', () => { + it('logs the same subject/flag/variation after two changes', async () => { client.useNonExpiringInMemoryAssignmentCache(); // original configuration version - setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // log this assignment client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // cache hit, don't log // change the variation - setUnobfuscatedFlagEntries({ + await setUnobfuscatedFlagEntries({ [flagKey]: { ...mockFlag, allocations: [ @@ -654,13 +654,13 @@ describe('EppoClient E2E test', () => { client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // cache hit, don't log // change the flag again, back to the original - setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); + await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag }); client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // important: log this assignment client.getStringAssignment(flagKey, 'subject-10', {}, 'default'); // cache hit, don't log // change the allocation - setUnobfuscatedFlagEntries({ + await setUnobfuscatedFlagEntries({ [flagKey]: { ...mockFlag, allocations: [ diff --git a/src/client/eppo-client.ts b/src/client/eppo-client.ts index 5c98f92..cd974c2 100644 --- a/src/client/eppo-client.ts +++ b/src/client/eppo-client.ts @@ -104,6 +104,9 @@ export type EppoClientParameters = { banditModelConfigurationStore?: IConfigurationStore; configurationRequestParameters?: FlagConfigurationRequestParameters; /** + * Setting this value will have no side effects other than triggering a warning when the actual + * configuration's obfuscated does not match the value set here. + * * @deprecated obfuscation is determined by inspecting the `format` field of the UFC response. */ isObfuscated?: boolean; @@ -127,6 +130,8 @@ export default class EppoClient { // whether to suppress any errors and return default values instead private isGracefulFailureMode = true; private expectObfuscated: boolean; + private obfuscationMismatchWarningIssued = false; + private configObfuscatedCache?: boolean; private requestPoller?: IPoller; private readonly evaluator = new Evaluator(); @@ -146,15 +151,27 @@ export default class EppoClient { this.expectObfuscated = isObfuscated; } - private isObfuscated() { - const configFormat = getFormatFromString(this.flagConfigurationStore.getFormat()); - const configObfuscated = OBFUSCATED_FORMATS.includes(configFormat); - if (configObfuscated !== this.expectObfuscated) { + private maybeWarnAboutObfuscationMismatch(configObfuscated: boolean) { + // Don't warn again if we did on the last check. + if (configObfuscated !== this.expectObfuscated && !this.obfuscationMismatchWarningIssued) { + this.obfuscationMismatchWarningIssued = true; logger.warn( `[Eppo SDK] configuration obfuscation [${configObfuscated}] does not match expected [${this.expectObfuscated}]`, ); + } else if (configObfuscated === this.expectObfuscated) { + // Reset the warning to false in case the client configuration (re-)enters a mismatched state. + this.obfuscationMismatchWarningIssued = false; + } + } + + private isObfuscated() { + if (this.configObfuscatedCache === undefined) { + this.configObfuscatedCache = OBFUSCATED_FORMATS.includes( + getFormatFromString(this.flagConfigurationStore.getFormat()), + ); } - return configObfuscated; + this.maybeWarnAboutObfuscationMismatch(this.configObfuscatedCache); + return this.configObfuscatedCache; } setConfigurationRequestParameters( @@ -166,6 +183,7 @@ export default class EppoClient { // noinspection JSUnusedGlobalSymbols setFlagConfigurationStore(flagConfigurationStore: IConfigurationStore) { this.flagConfigurationStore = flagConfigurationStore; + this.configObfuscatedCache = undefined; } // noinspection JSUnusedGlobalSymbols @@ -205,6 +223,9 @@ export default class EppoClient { // noinspection JSUnusedGlobalSymbols /** + * Setting this value will have no side effects other than triggering a warning when the actual + * configuration's obfuscated does not match the value set here. + * * @deprecated The client determines whether the configuration is obfuscated by inspection * @param isObfuscated */ @@ -256,6 +277,7 @@ export default class EppoClient { const pollingCallback = async () => { if (await this.flagConfigurationStore.isExpired()) { + this.configObfuscatedCache = undefined; return configurationRequestor.fetchAndStoreConfigurations(); } }; From 04d8a431a9bf510e17e69a904a4a0f5062b1e825 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Wed, 19 Feb 2025 11:55:20 -0700 Subject: [PATCH 5/6] chore: drop STR to Enum and lean into string enum shape goodness --- src/client/eppo-client.ts | 4 ++-- src/constants.ts | 2 +- src/interfaces.ts | 5 ----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/client/eppo-client.ts b/src/client/eppo-client.ts index f667a8f..0dd16c2 100644 --- a/src/client/eppo-client.ts +++ b/src/client/eppo-client.ts @@ -47,7 +47,7 @@ import { BanditVariation, ConfigDetails, Flag, - getFormatFromString, + FormatEnum, IPrecomputedBandit, ObfuscatedFlag, PrecomputedFlag, @@ -180,7 +180,7 @@ export default class EppoClient { private isObfuscated() { if (this.configObfuscatedCache === undefined) { this.configObfuscatedCache = OBFUSCATED_FORMATS.includes( - getFormatFromString(this.flagConfigurationStore.getFormat()), + this.flagConfigurationStore.getFormat() ?? FormatEnum.SERVER, ); } this.maybeWarnAboutObfuscationMismatch(this.configObfuscatedCache); diff --git a/src/constants.ts b/src/constants.ts index 14f31fe..99a8fd4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -21,4 +21,4 @@ export const DEFAULT_TLRU_TTL_MS = 600_000; /** * UFC Configuration formats which are obfuscated. */ -export const OBFUSCATED_FORMATS = [FormatEnum.CLIENT, FormatEnum.PRECOMPUTED]; +export const OBFUSCATED_FORMATS: string[] = [FormatEnum.CLIENT, FormatEnum.PRECOMPUTED]; diff --git a/src/interfaces.ts b/src/interfaces.ts index f694e25..c4cbe94 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -149,11 +149,6 @@ export enum FormatEnum { PRECOMPUTED = 'PRECOMPUTED', } -export function getFormatFromString(str: string | null): FormatEnum { - // default to SERVER. Should always be set, but the ConfigurationStore allows null. - return FormatEnum[str as keyof typeof FormatEnum] ?? FormatEnum.SERVER; -} - export type BasePrecomputedFlag = { flagKey?: string; allocationKey?: string; From 0786d715d93d294ef8e144bba85e24fc802fcd7c Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Wed, 19 Feb 2025 11:58:44 -0700 Subject: [PATCH 6/6] comments --- src/constants.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/constants.ts b/src/constants.ts index 99a8fd4..f7c3b30 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -20,5 +20,8 @@ export const DEFAULT_TLRU_TTL_MS = 600_000; /** * UFC Configuration formats which are obfuscated. + * + * We use string[] instead of FormatEnum[] to allow easy interaction with this value in its wire type (string). + * Converting from string to enum requires a map lookup or array iteration and is much more awkward than the inverse. */ export const OBFUSCATED_FORMATS: string[] = [FormatEnum.CLIENT, FormatEnum.PRECOMPUTED];