-
Notifications
You must be signed in to change notification settings - Fork 1
feat: inspect format field to determine whether config is obfuscated #218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cb062eb
8aee608
e01f3b3
5ebe36e
f067c3b
04d8a43
0786d71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
FormatEnum, | ||
IPrecomputedBandit, | ||
ObfuscatedFlag, | ||
PrecomputedFlag, | ||
|
@@ -101,6 +103,12 @@ export type EppoClientParameters = { | |
banditVariationConfigurationStore?: IConfigurationStore<BanditVariation[]>; | ||
banditModelConfigurationStore?: IConfigurationStore<BanditParameters>; | ||
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. | ||
*/ | ||
typotter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
isObfuscated?: boolean; | ||
}; | ||
|
||
|
@@ -122,7 +130,9 @@ 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 obfuscationMismatchWarningIssued = false; | ||
private configObfuscatedCache?: boolean; | ||
private requestPoller?: IPoller; | ||
private readonly evaluator = new Evaluator(); | ||
|
||
|
@@ -151,7 +161,30 @@ export default class EppoClient { | |
this.banditModelConfigurationStore = banditModelConfigurationStore; | ||
this.overrideStore = overrideStore; | ||
this.configurationRequestParameters = configurationRequestParameters; | ||
this.isObfuscated = isObfuscated; | ||
this.expectObfuscated = isObfuscated; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🐛 This looks like it sets |
||
} | ||
|
||
private maybeWarnAboutObfuscationMismatch(configObfuscated: boolean) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason we develop elaborate warning for a deprecated flag?
|
||
// 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( | ||
this.flagConfigurationStore.getFormat() ?? FormatEnum.SERVER, | ||
); | ||
} | ||
this.maybeWarnAboutObfuscationMismatch(this.configObfuscatedCache); | ||
return this.configObfuscatedCache; | ||
Comment on lines
+181
to
+187
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: maintaining this cache is probably as costly as doing two comparisons (we actually only need one as precomputed format is not valid here). major: cache invalidation is also dubious and there seems to be a bug lurking in it. Cache is reset before issues a configuration fetch. However, as fetch is async, the cache will get re-populated if evaluation is requested, and subsequent configuration fetch success does not invalidate the cache. This may cause an evaluation bug if initial configuration obfuscation does not match remote configuration obfuscation (e.g., client initialized with non-obfuscated server config first and fetches an obfuscated configuration later) If we want to cache obfuscation status, configuration itself is the right place to do that |
||
} | ||
|
||
setConfigurationRequestParameters( | ||
|
@@ -163,6 +196,7 @@ export default class EppoClient { | |
// noinspection JSUnusedGlobalSymbols | ||
setFlagConfigurationStore(flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>) { | ||
this.flagConfigurationStore = flagConfigurationStore; | ||
this.configObfuscatedCache = undefined; | ||
} | ||
|
||
// noinspection JSUnusedGlobalSymbols | ||
|
@@ -201,8 +235,15 @@ 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 | ||
typotter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
setIsObfuscated(isObfuscated: boolean) { | ||
this.isObfuscated = isObfuscated; | ||
this.expectObfuscated = isObfuscated; | ||
} | ||
|
||
setOverrideStore(store: ISyncStore<Variation>): void { | ||
|
@@ -267,6 +308,7 @@ export default class EppoClient { | |
|
||
const pollingCallback = async () => { | ||
if (await this.flagConfigurationStore.isExpired()) { | ||
this.configObfuscatedCache = undefined; | ||
return configurationRequestor.fetchAndStoreConfigurations(); | ||
} | ||
}; | ||
|
@@ -885,7 +927,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 | ||
|
@@ -1035,15 +1077,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; | ||
} | ||
|
@@ -1094,7 +1137,7 @@ export default class EppoClient { | |
} | ||
|
||
private getFlag(flagKey: string): Flag | null { | ||
return this.isObfuscated | ||
return this.isObfuscated() | ||
typotter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
? this.getObfuscatedFlag(flagKey) | ||
: this.flagConfigurationStore.get(flagKey); | ||
} | ||
|
@@ -1262,7 +1305,7 @@ export default class EppoClient { | |
|
||
private buildLoggerMetadata(): Record<string, unknown> { | ||
return { | ||
obfuscated: this.isObfuscated, | ||
obfuscated: this.isObfuscated(), | ||
sdkLanguage: 'javascript', | ||
sdkLibVersion: LIB_VERSION, | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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,11 @@ 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. | ||
* | ||
* 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]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Making this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I don't think this is actually true as (Even though there's no performance penalty for casting string to enum, doing so is type-unsafe as the string could be anything, including values not listed in enum.) |
Uh oh!
There was an error while loading. Please reload this page.