From bf2a769b42874d41dbcf5a7798f6d47b3299d697 Mon Sep 17 00:00:00 2001 From: Kenny Yi Date: Mon, 5 Aug 2024 13:48:44 -0700 Subject: [PATCH] custom hash function plugin --- src/StatsigOnPrem.ts | 6 +++++- src/utils/HashUtils.ts | 30 +++++++++++++++++++----------- tests/HashPlugin.test.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 tests/HashPlugin.test.ts diff --git a/src/StatsigOnPrem.ts b/src/StatsigOnPrem.ts index 1ebbdb6..22bf0f4 100644 --- a/src/StatsigOnPrem.ts +++ b/src/StatsigOnPrem.ts @@ -17,7 +17,7 @@ import EntityFeatureGate from "./entities/EntityFeatureGate"; import { IDUtils } from "./utils/IDUtils"; import { SpecsCacheInterface } from "./interfaces/SpecsCacheInterface"; import StorageHandler from "./utils/StorageHandler"; -import HashUtils from "./utils/HashUtils"; +import HashUtils, { HashFn } from "./utils/HashUtils"; import { TargetAppNames } from "./types/TargetAppNames"; import CacheHandler from "./utils/CacheHandler"; import { SDKKeysCacheInterface } from "./interfaces/SDKKeyCacheInterface"; @@ -25,6 +25,7 @@ import { SDKKeysCacheInterface } from "./interfaces/SDKKeyCacheInterface"; type Plugins = Partial<{ specsCache: SpecsCacheInterface; sdkKeysCache: SDKKeysCacheInterface; + hash: HashFn; }>; export default class StatsigOnPrem implements StatsigInterface { @@ -36,6 +37,9 @@ export default class StatsigOnPrem implements StatsigInterface { specs: plugins?.specsCache, keys: plugins?.sdkKeysCache, }); + if (plugins?.hash !== undefined) { + HashUtils.setHashFn(plugins.hash); + } } public async initialize(): Promise { diff --git a/src/utils/HashUtils.ts b/src/utils/HashUtils.ts index 5292b56..750ac6e 100644 --- a/src/utils/HashUtils.ts +++ b/src/utils/HashUtils.ts @@ -1,19 +1,27 @@ +export type HashFn = (input: string) => string; + +let hash_fn: HashFn = djb2Hash; + export default class HashUtils { public static hashString(str: string): string { - return this.djb2Hash(str); + return hash_fn(str); } - private static fasthash(value: string): number { - let hash = 0; - for (let i = 0; i < value.length; i++) { - const character = value.charCodeAt(i); - hash = (hash << 5) - hash + character; - hash = hash & hash; // Convert to 32bit integer - } - return hash; + public static setHashFn(fn: HashFn): void { + hash_fn = fn; } +} - private static djb2Hash(value: string): string { - return String(this.fasthash(value) >>> 0); +function fasthash(value: string): number { + let hash = 0; + for (let i = 0; i < value.length; i++) { + const character = value.charCodeAt(i); + hash = (hash << 5) - hash + character; + hash = hash & hash; // Convert to 32bit integer } + return hash; +} + +function djb2Hash(value: string): string { + return String(fasthash(value) >>> 0); } diff --git a/tests/HashPlugin.test.ts b/tests/HashPlugin.test.ts new file mode 100644 index 0000000..8cbae94 --- /dev/null +++ b/tests/HashPlugin.test.ts @@ -0,0 +1,26 @@ +import StatsigStorageExample from "../examples/StatsigStorageExample"; +import SDKKeysCache from "../src/SDKKeysCache"; +import StatsigOnPrem from "../src/StatsigOnPrem"; + +describe("Hash plugin", () => { + const storage = new StatsigStorageExample(); + const sdkKeysCache = new SDKKeysCache(); + const hashFn = (input: string) => `hashed:${input}`; + const statsig = new StatsigOnPrem(storage, { sdkKeysCache, hash: hashFn }); + + beforeAll(async () => { + await statsig.initialize(); + }); + + afterAll(async () => { + storage.clearAll(); + await statsig.clearCache(); + }); + + it("Uses custom hash function", async () => { + await statsig.registerSDKKey("secret-123"); + expect(storage.get("statsig:sdkKey:hashed:secret-123")).resolves.toEqual( + "registered" + ); + }); +});