Skip to content

Commit

Permalink
Handle special characters when decoding obfuscated configurations (#187)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
aarsilv authored Jan 2, 2025
1 parent ead7863 commit ec2c453
Show file tree
Hide file tree
Showing 9 changed files with 37 additions and 26 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eppo/js-client-sdk-common",
"version": "4.7.0",
"version": "4.7.1",
"description": "Common library for Eppo JavaScript SDKs (web, react native, and node)",
"main": "dist/index.js",
"files": [
Expand Down
12 changes: 3 additions & 9 deletions src/configuration-requestor.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { IConfigurationStore } from './configuration-store/configuration-store';
import { hydrateConfigurationStore } from './configuration-store/configuration-store-utils';
import { IHttpClient } from './http-client';
import {
BanditVariation,
BanditParameters,
Flag,
BanditReference,
} from './interfaces';

type Entry = Flag | BanditVariation[] | BanditParameters;
import { BanditVariation, BanditParameters, Flag, BanditReference } from './interfaces';

// Requests AND stores flag configurations
export default class ConfigurationRequestor {
Expand Down Expand Up @@ -63,7 +56,8 @@ export default class ConfigurationRequestor {
entries: banditResponse.bandits,
environment: configResponse.environment,
createdAt: configResponse.createdAt,
format: configResponse.format,});
format: configResponse.format,
});

this.banditModelVersions = this.getLoadedBanditModelVersionsFromStore(
this.banditModelConfigurationStore,
Expand Down
1 change: 1 addition & 0 deletions src/evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export class Evaluator {
configDetails.configFormat,
);
} catch (err: any) {
console.error('>>>>', err);
const flagEvaluationDetails = flagEvaluationDetailsBuilder.gracefulBuild(
'ASSIGNMENT_ERROR',
`Assignment Error: ${err.message}`,
Expand Down
3 changes: 3 additions & 0 deletions src/events/default-event-dispatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ global.fetch = jest.fn();

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

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

it('resumes delivery when back online', async () => {
let isOffline = true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let cb = (_: boolean) => null as unknown as void;
const networkStatusListener = {
isOffline: () => isOffline,
Expand Down
1 change: 1 addition & 0 deletions src/events/no-op-event-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Event from './event';
import EventDispatcher from './event-dispatcher';

export default class NoOpEventDispatcher implements EventDispatcher {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
dispatch(_: Event): void {
// Do nothing
}
Expand Down
2 changes: 1 addition & 1 deletion src/flag-evaluation-details-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const flagEvaluationCodes = [
'BANDIT_ERROR',
] as const;

export type FlagEvaluationCode = typeof flagEvaluationCodes[number];
export type FlagEvaluationCode = (typeof flagEvaluationCodes)[number];

export enum AllocationEvaluationCode {
UNEVALUATED = 'UNEVALUATED',
Expand Down
11 changes: 11 additions & 0 deletions src/obfuscation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ describe('obfuscation', () => {
});
});

it('encodes/decodes special characters', () => {
const strings = ['kümmert', 'піклуватися', '照顾', '🤗🌸'];

strings.forEach((string) => {
expect(decodeBase64(encodeBase64(string))).toEqual(string);
expect(decodeBase64(encodeBase64(string))).toEqual(string);
});

expect(decodeBase64('a8O8bW1lcnQ=')).toEqual('kümmert');
});

describe('salt', () => {
it('converts from bytes to base64 string', () => {
const chars = new Uint8Array([101, 112, 112, 111]); // eppo
Expand Down
4 changes: 2 additions & 2 deletions src/obfuscation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export function getMD5Hash(input: string, salt = ''): string {
}

export function encodeBase64(input: string) {
return base64.btoaPolyfill(input);
return base64.encode(input);
}

export function decodeBase64(input: string) {
return base64.atobPolyfill(input);
return base64.decode(input);
}

export function obfuscatePrecomputedFlags(
Expand Down
27 changes: 14 additions & 13 deletions test/testHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as fs from 'fs';

import { AttributeType, VariationType } from '../src';
import { IAssignmentDetails } from '../src/client/eppo-client';
import { isEqual } from 'lodash';

import { AttributeType, ContextAttributes, IAssignmentDetails, VariationType } from '../src';
import { IFlagEvaluationDetails } from '../src/flag-evaluation-details-builder';
import { IBanditParametersResponse, IUniversalFlagConfigResponse } from '../src/http-client';
import { ContextAttributes } from '../src/types';

export const TEST_DATA_DIR = './test/data/ufc/';
export const ASSIGNMENT_TEST_DATA_DIR = TEST_DATA_DIR + 'tests/';
Expand Down Expand Up @@ -125,16 +125,17 @@ export function validateTestAssignments(
flag: string,
) {
for (const { subject, assignment } of assignments) {
if (typeof assignment !== 'object') {
// the expect works well for objects, but this comparison does not
if (assignment !== subject.assignment) {
throw new Error(
`subject ${
subject.subjectKey
} was assigned ${assignment?.toString()} when expected ${subject.assignment?.toString()} for flag ${flag}`,
);
}
if (!isEqual(assignment, subject.assignment)) {
// More friendly error message
console.error(
`subject ${subject.subjectKey} was assigned ${JSON.stringify(
assignment,
undefined,
2,
)} when expected ${JSON.stringify(subject.assignment, undefined, 2)} for flag ${flag}`,
);
}
expect(subject.assignment).toEqual(assignment);

expect(assignment).toEqual(subject.assignment);
}
}

0 comments on commit ec2c453

Please sign in to comment.