Skip to content

Commit ec2c453

Browse files
authored
Handle special characters when decoding obfuscated configurations (#187)
* make test output more helpful * failing test * use pure JS method * bump version * undo incorrect IDE suggestion for importing SparkMD5 as default * feedback from PR * fix linter issues
1 parent ead7863 commit ec2c453

9 files changed

+37
-26
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.7.0",
3+
"version": "4.7.1",
44
"description": "Common library for Eppo JavaScript SDKs (web, react native, and node)",
55
"main": "dist/index.js",
66
"files": [

src/configuration-requestor.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
import { IConfigurationStore } from './configuration-store/configuration-store';
22
import { hydrateConfigurationStore } from './configuration-store/configuration-store-utils';
33
import { IHttpClient } from './http-client';
4-
import {
5-
BanditVariation,
6-
BanditParameters,
7-
Flag,
8-
BanditReference,
9-
} from './interfaces';
10-
11-
type Entry = Flag | BanditVariation[] | BanditParameters;
4+
import { BanditVariation, BanditParameters, Flag, BanditReference } from './interfaces';
125

136
// Requests AND stores flag configurations
147
export default class ConfigurationRequestor {
@@ -63,7 +56,8 @@ export default class ConfigurationRequestor {
6356
entries: banditResponse.bandits,
6457
environment: configResponse.environment,
6558
createdAt: configResponse.createdAt,
66-
format: configResponse.format,});
59+
format: configResponse.format,
60+
});
6761

6862
this.banditModelVersions = this.getLoadedBanditModelVersionsFromStore(
6963
this.banditModelConfigurationStore,

src/evaluator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export class Evaluator {
142142
configDetails.configFormat,
143143
);
144144
} catch (err: any) {
145+
console.error('>>>>', err);
145146
const flagEvaluationDetails = flagEvaluationDetailsBuilder.gracefulBuild(
146147
'ASSIGNMENT_ERROR',
147148
`Assignment Error: ${err.message}`,

src/events/default-event-dispatcher.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ global.fetch = jest.fn();
1212

1313
const mockNetworkStatusListener = {
1414
isOffline: () => false,
15+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1516
onNetworkStatusChange: (_: (_: boolean) => void) => null as unknown as void,
1617
};
1718

@@ -154,6 +155,7 @@ describe('DefaultEventDispatcher', () => {
154155
describe('offline handling', () => {
155156
it('skips delivery when offline', async () => {
156157
let isOffline = false;
158+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
157159
let cb = (_: boolean) => null as unknown as void;
158160
const networkStatusListener = {
159161
isOffline: () => isOffline,
@@ -188,6 +190,7 @@ describe('DefaultEventDispatcher', () => {
188190

189191
it('resumes delivery when back online', async () => {
190192
let isOffline = true;
193+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
191194
let cb = (_: boolean) => null as unknown as void;
192195
const networkStatusListener = {
193196
isOffline: () => isOffline,

src/events/no-op-event-dispatcher.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Event from './event';
22
import EventDispatcher from './event-dispatcher';
33

44
export default class NoOpEventDispatcher implements EventDispatcher {
5+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
56
dispatch(_: Event): void {
67
// Do nothing
78
}

src/flag-evaluation-details-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const flagEvaluationCodes = [
1111
'BANDIT_ERROR',
1212
] as const;
1313

14-
export type FlagEvaluationCode = typeof flagEvaluationCodes[number];
14+
export type FlagEvaluationCode = (typeof flagEvaluationCodes)[number];
1515

1616
export enum AllocationEvaluationCode {
1717
UNEVALUATED = 'UNEVALUATED',

src/obfuscation.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ describe('obfuscation', () => {
1717
});
1818
});
1919

20+
it('encodes/decodes special characters', () => {
21+
const strings = ['kümmert', 'піклуватися', '照顾', '🤗🌸'];
22+
23+
strings.forEach((string) => {
24+
expect(decodeBase64(encodeBase64(string))).toEqual(string);
25+
expect(decodeBase64(encodeBase64(string))).toEqual(string);
26+
});
27+
28+
expect(decodeBase64('a8O8bW1lcnQ=')).toEqual('kümmert');
29+
});
30+
2031
describe('salt', () => {
2132
it('converts from bytes to base64 string', () => {
2233
const chars = new Uint8Array([101, 112, 112, 111]); // eppo

src/obfuscation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export function getMD5Hash(input: string, salt = ''): string {
88
}
99

1010
export function encodeBase64(input: string) {
11-
return base64.btoaPolyfill(input);
11+
return base64.encode(input);
1212
}
1313

1414
export function decodeBase64(input: string) {
15-
return base64.atobPolyfill(input);
15+
return base64.decode(input);
1616
}
1717

1818
export function obfuscatePrecomputedFlags(

test/testHelpers.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as fs from 'fs';
22

3-
import { AttributeType, VariationType } from '../src';
4-
import { IAssignmentDetails } from '../src/client/eppo-client';
3+
import { isEqual } from 'lodash';
4+
5+
import { AttributeType, ContextAttributes, IAssignmentDetails, VariationType } from '../src';
56
import { IFlagEvaluationDetails } from '../src/flag-evaluation-details-builder';
67
import { IBanditParametersResponse, IUniversalFlagConfigResponse } from '../src/http-client';
7-
import { ContextAttributes } from '../src/types';
88

99
export const TEST_DATA_DIR = './test/data/ufc/';
1010
export const ASSIGNMENT_TEST_DATA_DIR = TEST_DATA_DIR + 'tests/';
@@ -125,16 +125,17 @@ export function validateTestAssignments(
125125
flag: string,
126126
) {
127127
for (const { subject, assignment } of assignments) {
128-
if (typeof assignment !== 'object') {
129-
// the expect works well for objects, but this comparison does not
130-
if (assignment !== subject.assignment) {
131-
throw new Error(
132-
`subject ${
133-
subject.subjectKey
134-
} was assigned ${assignment?.toString()} when expected ${subject.assignment?.toString()} for flag ${flag}`,
135-
);
136-
}
128+
if (!isEqual(assignment, subject.assignment)) {
129+
// More friendly error message
130+
console.error(
131+
`subject ${subject.subjectKey} was assigned ${JSON.stringify(
132+
assignment,
133+
undefined,
134+
2,
135+
)} when expected ${JSON.stringify(subject.assignment, undefined, 2)} for flag ${flag}`,
136+
);
137137
}
138-
expect(subject.assignment).toEqual(assignment);
138+
139+
expect(assignment).toEqual(subject.assignment);
139140
}
140141
}

0 commit comments

Comments
 (0)