diff --git a/packages/node/src/amplitude.ts b/packages/node/src/amplitude.ts index df6aaed..e11f02a 100644 --- a/packages/node/src/amplitude.ts +++ b/packages/node/src/amplitude.ts @@ -1,5 +1,4 @@ import { ExperimentUser } from './types/user'; -import { base64Decode } from './util/encode'; /** * This class provides utility functions for parsing and handling identity @@ -28,7 +27,7 @@ export class AmplitudeCookie { let user_id = undefined; if (values[1]) { try { - user_id = base64Decode(values[1]); + user_id = Buffer.from(values[1], 'base64').toString('utf-8'); } catch (e) { user_id = undefined; } diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index fb12d99..6c0d156 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -5,7 +5,6 @@ import { FetchHttpClient } from './transport/http'; import { HttpClient } from './types/transport'; import { ExperimentUser } from './types/user'; import { Variant, Variants } from './types/variant'; -import { urlSafeBase64Encode } from './util/encode'; import { performance } from './util/performance'; import { sleep } from './util/time'; @@ -75,16 +74,23 @@ export class ExperimentClient { ): Promise { const start = performance.now(); const userContext = this.addContext(user || {}); - const encodedContext = urlSafeBase64Encode(JSON.stringify(userContext)); - const endpoint = `${this.config.serverUrl}/sdk/vardata/${encodedContext}`; + const endpoint = `${this.config.serverUrl}/sdk/vardata`; const headers = { Authorization: `Api-Key ${this.apiKey}`, }; + const body = JSON.stringify(user); + // CDN can only cache requests where the body is < 8KB + if (body.length > 8000) { + console.warn( + `[Experiment] encoded user object length ${body.length} cannot be cached by CDN; must be < 8KB`, + ); + } + this.debug('[Experiment] Fetch variants for user: ', userContext); const response = await this.httpClient.request( endpoint, - 'GET', + 'POST', headers, - null, + body, timeoutMillis, ); if (response.status !== 200) { diff --git a/packages/node/src/transport/http.ts b/packages/node/src/transport/http.ts index 263f554..1734f35 100644 --- a/packages/node/src/transport/http.ts +++ b/packages/node/src/transport/http.ts @@ -1,6 +1,5 @@ import http from 'http'; import https from 'https'; -import querystring, { ParsedUrlQueryInput } from 'querystring'; import url from 'url'; import { SimpleResponse, HttpClient } from '../types/transport'; @@ -16,19 +15,15 @@ const request: HttpClient['request'] = ( requestUrl: string, method: string, headers: Record, - data?: Record, + data: string, timeoutMillis?: number, ): Promise => { const urlParams = url.parse(requestUrl); - if (method === 'GET' && data) { - urlParams.path = `${urlParams.path}?${querystring.encode( - data as ParsedUrlQueryInput, - )}`; - } const options = { ...urlParams, - method, - headers, + method: method, + headers: headers, + body: data, // Adds timeout to the socket connection, not the response. timeout: timeoutMillis, }; diff --git a/packages/node/src/types/transport.ts b/packages/node/src/types/transport.ts index ac3bb9c..eea383f 100644 --- a/packages/node/src/types/transport.ts +++ b/packages/node/src/types/transport.ts @@ -8,7 +8,7 @@ export interface HttpClient { requestUrl: string, method: string, headers: Record, - data?: Record, + body: string, timeoutMillis?: number, ): Promise; } diff --git a/packages/node/src/util/encode.ts b/packages/node/src/util/encode.ts deleted file mode 100644 index 4639480..0000000 --- a/packages/node/src/util/encode.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { TextEncoder } from 'util'; - -const encoder = new TextEncoder(); - -export const getUtf8Bytes = (str: string) => { - const result = []; - for (let i = 0; i < str.length; i++) { - const charCode = str.charCodeAt(i); - if (charCode < 0 || charCode > 127) { - return encoder.encode(str); - } - result.push(charCode); - } - - return result; -}; - -const base64Encode = (unencoded: string) => { - return Buffer.from(unencoded || '').toString('base64'); -}; - -export const urlSafeBase64Encode = (s: string): string => { - const encoded = base64Encode(s); - - return encoded.replace('+', '-').replace('/', '_').replace(/[=]+$/, ''); -}; - -export const base64Decode = (encoded: string): string => { - return Buffer.from(encoded, 'base64').toString('utf-8'); -}; diff --git a/packages/node/test/client.test.ts b/packages/node/test/client.test.ts new file mode 100644 index 0000000..0dc66e6 --- /dev/null +++ b/packages/node/test/client.test.ts @@ -0,0 +1,34 @@ +import { ExperimentClient } from 'src/client'; +import { ExperimentUser } from 'src/types/user'; + +const API_KEY = 'client-DvWljIjiiuqLbyjqdvBaLFfEBrAvGuA3'; + +const testUser: ExperimentUser = { user_id: 'test_user' }; + +const testClient: ExperimentClient = new ExperimentClient(API_KEY, {}); + +const testTimeoutNoRetriesClient = new ExperimentClient(API_KEY, { + fetchRetries: 0, + fetchTimeoutMillis: 1, +}); + +const testTimeoutRetrySuccessClient = new ExperimentClient(API_KEY, { + fetchTimeoutMillis: 1, +}); + +test('ExperimentClient.fetch, success', async () => { + const variants = await testClient.fetch(testUser); + const variant = variants['sdk-ci-test']; + expect(variant).toEqual({ value: 'on', payload: 'payload' }); +}); + +test('ExperimentClient.fetch, no retries, timeout failure', async () => { + const variants = await testTimeoutNoRetriesClient.fetch(testUser); + expect(variants).toEqual({}); +}); + +test('ExperimentClient.fetch, no retries, timeout failure', async () => { + const variants = await testTimeoutRetrySuccessClient.fetch(testUser); + const variant = variants['sdk-ci-test']; + expect(variant).toEqual({ value: 'on', payload: 'payload' }); +}); diff --git a/packages/node/test/util/encode.test.ts b/packages/node/test/util/encode.test.ts deleted file mode 100644 index 841d4ef..0000000 --- a/packages/node/test/util/encode.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getUtf8Bytes, urlSafeBase64Encode } from 'src/util/encode'; - -test('getUtf8Bytes', () => { - expect(getUtf8Bytes('My 🚀 is full of 🦎')).toEqual( - new Uint8Array([ - 77, - 121, - 32, - 240, - 159, - 154, - 128, - 32, - 105, - 115, - 32, - 102, - 117, - 108, - 108, - 32, - 111, - 102, - 32, - 240, - 159, - 166, - 142, - ]), - ); -}); - -test('urlSafeBase64Encode', () => { - expect(urlSafeBase64Encode('My 🚀 is full of 🦎')).toEqual( - 'TXkg8J-agCBpcyBmdWxsIG9mIPCfpo4', - ); -});