Skip to content

Commit 110a983

Browse files
committed
merge main
2 parents 4981b15 + 23ef6e9 commit 110a983

File tree

3 files changed

+166
-38
lines changed

3 files changed

+166
-38
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import {
2+
MOCK_BANDIT_MODELS_RESPONSE_FILE,
3+
MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE,
4+
readMockUFCResponse,
5+
} from '../test/testHelpers';
6+
7+
import { ConfigurationWireV1, deflateResponse, inflateResponse } from './configuration-wire-types';
8+
import { IUniversalFlagConfigResponse, IBanditParametersResponse } from './http-client';
9+
import { FormatEnum } from './interfaces';
10+
11+
describe('Response String Type Safety', () => {
12+
const mockFlagConfig: IUniversalFlagConfigResponse = readMockUFCResponse(
13+
MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE,
14+
) as IUniversalFlagConfigResponse;
15+
const mockBanditConfig: IBanditParametersResponse = readMockUFCResponse(
16+
MOCK_BANDIT_MODELS_RESPONSE_FILE,
17+
) as IBanditParametersResponse;
18+
19+
describe('deflateResponse and inflateResponse', () => {
20+
it('should correctly serialize and deserialize flag config', () => {
21+
const serialized = deflateResponse(mockFlagConfig);
22+
const deserialized = inflateResponse(serialized);
23+
24+
expect(deserialized).toEqual(mockFlagConfig);
25+
});
26+
27+
it('should correctly serialize and deserialize bandit config', () => {
28+
const serialized = deflateResponse(mockBanditConfig);
29+
const deserialized = inflateResponse(serialized);
30+
31+
expect(deserialized).toEqual(mockBanditConfig);
32+
});
33+
34+
it('should maintain type information through serialization', () => {
35+
const serialized = deflateResponse(mockFlagConfig);
36+
const deserialized = inflateResponse(serialized);
37+
38+
// TypeScript compilation check: these should work
39+
expect(deserialized.format).toBe(FormatEnum.SERVER);
40+
expect(deserialized.environment).toStrictEqual({ name: 'Test' });
41+
});
42+
});
43+
44+
describe('ConfigurationWireV1', () => {
45+
it('should create configuration with flag config', () => {
46+
const wirePacket = ConfigurationWireV1.fromResponses(mockFlagConfig);
47+
48+
expect(wirePacket.version).toBe(1);
49+
expect(wirePacket.config).toBeDefined();
50+
expect(wirePacket.bandits).toBeUndefined();
51+
52+
// Verify we can deserialize the response
53+
expect(wirePacket.config).toBeTruthy();
54+
if (!wirePacket.config) {
55+
fail('Flag config not present in ConfigurationWire');
56+
}
57+
const deserializedConfig = inflateResponse(wirePacket.config.response);
58+
expect(deserializedConfig).toEqual(mockFlagConfig);
59+
});
60+
61+
it('should create configuration with both flag and bandit configs', () => {
62+
const wirePacket = ConfigurationWireV1.fromResponses(
63+
mockFlagConfig,
64+
mockBanditConfig,
65+
'flag-etag',
66+
'bandit-etag',
67+
);
68+
69+
if (!wirePacket.config) {
70+
fail('Flag config not present in ConfigurationWire');
71+
}
72+
if (!wirePacket.bandits) {
73+
fail('Bandit Model Parameters not present in ConfigurationWire');
74+
}
75+
76+
expect(wirePacket.version).toBe(1);
77+
expect(wirePacket.config).toBeDefined();
78+
expect(wirePacket.bandits).toBeDefined();
79+
expect(wirePacket.config.etag).toBe('flag-etag');
80+
expect(wirePacket.bandits.etag).toBe('bandit-etag');
81+
82+
// Verify we can deserialize both responses
83+
const deserializedConfig = inflateResponse(wirePacket.config.response);
84+
const deserializedBandits = inflateResponse(wirePacket.bandits.response);
85+
86+
expect(deserializedConfig).toEqual(mockFlagConfig);
87+
expect(deserializedBandits).toEqual(mockBanditConfig);
88+
});
89+
90+
it('should create empty configuration', () => {
91+
const config = ConfigurationWireV1.empty();
92+
93+
expect(config.version).toBe(1);
94+
expect(config.config).toBeUndefined();
95+
expect(config.bandits).toBeUndefined();
96+
expect(config.precomputed).toBeUndefined();
97+
});
98+
99+
it('should include fetchedAt timestamps', () => {
100+
const wirePacket = ConfigurationWireV1.fromResponses(mockFlagConfig, mockBanditConfig);
101+
102+
if (!wirePacket.config) {
103+
fail('Flag config not present in ConfigurationWire');
104+
}
105+
if (!wirePacket.bandits) {
106+
fail('Bandit Model Parameters not present in ConfigurationWire');
107+
}
108+
expect(wirePacket.config.fetchedAt).toBeDefined();
109+
expect(Date.parse(wirePacket.config.fetchedAt ?? '')).not.toBeNaN();
110+
expect(Date.parse(wirePacket.bandits.fetchedAt ?? '')).not.toBeNaN();
111+
});
112+
});
113+
});

src/http-client.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ export interface IBanditParametersResponse {
4646
bandits: Record<string, BanditParameters>;
4747
}
4848

49+
/**
50+
* Fixes issues with url.toString() in older React Native versions
51+
* that leaves a trailing slash in pathname
52+
* @param url URL to stringify
53+
* @returns stringified url
54+
*/
55+
const urlWithNoTrailingSlash = (url: URL) => {
56+
// Note: URL.pathname does not exist in some React Native JS runtime
57+
// so we have to do string manipulation on the stringified URL
58+
return url.toString().replace(/\/\?/, '?').replace(/\/$/, '');
59+
};
60+
4961
export interface IHttpClient {
5062
getUniversalFlagConfiguration(): Promise<IUniversalFlagConfigResponse | undefined>;
5163
getBanditParameters(): Promise<IBanditParametersResponse | undefined>;
@@ -89,7 +101,7 @@ export default class FetchHttpClient implements IHttpClient {
89101
const controller = new AbortController();
90102
const signal = controller.signal;
91103
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
92-
const response = await fetch(url.toString(), { signal });
104+
const response = await fetch(urlWithNoTrailingSlash(url), { signal });
93105
// Clear timeout when response is received within the budget.
94106
clearTimeout(timeoutId);
95107

@@ -114,7 +126,7 @@ export default class FetchHttpClient implements IHttpClient {
114126
const signal = controller.signal;
115127
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
116128

117-
const response = await fetch(url.toString(), {
129+
const response = await fetch(urlWithNoTrailingSlash(url), {
118130
method: 'POST',
119131
headers: {
120132
'Content-Type': 'application/json',

yarn.lock

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@
776776
"@types/tough-cookie" "*"
777777
parse5 "^7.0.0"
778778

779-
"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
779+
"@types/json-schema@*", "@types/json-schema@^7.0.9":
780780
version "7.0.15"
781781
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
782782
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -1160,19 +1160,14 @@ ajv-formats@^2.1.1:
11601160
dependencies:
11611161
ajv "^8.0.0"
11621162

1163-
ajv-keywords@^3.5.2:
1164-
version "3.5.2"
1165-
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
1166-
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
1167-
11681163
ajv-keywords@^5.1.0:
11691164
version "5.1.0"
11701165
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
11711166
integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
11721167
dependencies:
11731168
fast-deep-equal "^3.1.3"
11741169

1175-
ajv@^6.12.4, ajv@^6.12.5:
1170+
ajv@^6.12.4:
11761171
version "6.12.6"
11771172
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
11781173
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -2029,18 +2024,17 @@ eslint-import-resolver-node@^0.3.9:
20292024
resolve "^1.22.4"
20302025

20312026
eslint-import-resolver-typescript@^3.7.0:
2032-
version "3.7.0"
2033-
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz#e69925936a771a9cb2de418ccebc4cdf6c0818aa"
2034-
integrity sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==
2027+
version "3.8.5"
2028+
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.8.5.tgz#ef7113c83d5b14d036efbf6bc354581b56afa8a5"
2029+
integrity sha512-0ZRnzOqKc7TRm85w6REOUkVLHevN6nWd/xZsmKhSD/dcDktoxQaQAg59e5EK/QEsGFf7o5JSpE6qTwCEz0WjTw==
20352030
dependencies:
20362031
"@nolyfill/is-core-module" "1.0.39"
20372032
debug "^4.3.7"
20382033
enhanced-resolve "^5.15.0"
2039-
fast-glob "^3.3.2"
2040-
get-tsconfig "^4.7.5"
2034+
get-tsconfig "^4.10.0"
20412035
is-bun-module "^1.0.2"
2042-
is-glob "^4.0.3"
20432036
stable-hash "^0.0.4"
2037+
tinyglobby "^0.2.12"
20442038

20452039
eslint-module-utils@^2.12.0:
20462040
version "2.12.0"
@@ -2248,7 +2242,7 @@ fast-diff@^1.1.2:
22482242
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
22492243
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
22502244

2251-
fast-glob@^3.2.9, fast-glob@^3.3.2:
2245+
fast-glob@^3.2.9:
22522246
version "3.3.3"
22532247
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
22542248
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
@@ -2298,6 +2292,11 @@ fb-watchman@^2.0.0:
22982292
dependencies:
22992293
bser "2.1.1"
23002294

2295+
fdir@^6.4.3:
2296+
version "6.4.3"
2297+
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72"
2298+
integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==
2299+
23012300
file-entry-cache@^6.0.1:
23022301
version "6.0.1"
23032302
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -2455,10 +2454,10 @@ get-symbol-description@^1.1.0:
24552454
es-errors "^1.3.0"
24562455
get-intrinsic "^1.2.6"
24572456

2458-
get-tsconfig@^4.7.5:
2459-
version "4.8.1"
2460-
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471"
2461-
integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==
2457+
get-tsconfig@^4.10.0:
2458+
version "4.10.0"
2459+
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.0.tgz#403a682b373a823612475a4c2928c7326fc0f6bb"
2460+
integrity sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==
24622461
dependencies:
24632462
resolve-pkg-maps "^1.0.0"
24642463

@@ -3886,6 +3885,11 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1:
38863885
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
38873886
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
38883887

3888+
picomatch@^4.0.2:
3889+
version "4.0.2"
3890+
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
3891+
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
3892+
38893893
pino-abstract-transport@^2.0.0:
38903894
version "2.0.0"
38913895
resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz#de241578406ac7b8a33ce0d77ae6e8a0b3b68a60"
@@ -4204,15 +4208,6 @@ saxes@^6.0.0:
42044208
dependencies:
42054209
xmlchars "^2.2.0"
42064210

4207-
schema-utils@^3.2.0:
4208-
version "3.3.0"
4209-
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
4210-
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
4211-
dependencies:
4212-
"@types/json-schema" "^7.0.8"
4213-
ajv "^6.12.5"
4214-
ajv-keywords "^3.5.2"
4215-
42164211
schema-utils@^4.3.0:
42174212
version "4.3.0"
42184213
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0"
@@ -4538,10 +4533,10 @@ tapable@^2.1.1, tapable@^2.2.0:
45384533
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
45394534
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
45404535

4541-
terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.3:
4542-
version "5.3.11"
4543-
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz#93c21f44ca86634257cac176f884f942b7ba3832"
4544-
integrity sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==
4536+
terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.3:
4537+
version "5.3.14"
4538+
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06"
4539+
integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==
45454540
dependencies:
45464541
"@jridgewell/trace-mapping" "^0.3.25"
45474542
jest-worker "^27.4.5"
@@ -4595,6 +4590,14 @@ thread-stream@^3.0.0:
45954590
dependencies:
45964591
real-require "^0.2.0"
45974592

4593+
tinyglobby@^0.2.12:
4594+
version "0.2.12"
4595+
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5"
4596+
integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==
4597+
dependencies:
4598+
fdir "^6.4.3"
4599+
picomatch "^4.0.2"
4600+
45984601
45994602
version "1.0.5"
46004603
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@@ -4909,9 +4912,9 @@ webpack-sources@^3.2.3:
49094912
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
49104913

49114914
webpack@^5.73.0:
4912-
version "5.97.1"
4913-
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.97.1.tgz#972a8320a438b56ff0f1d94ade9e82eac155fa58"
4914-
integrity sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==
4915+
version "5.98.0"
4916+
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.98.0.tgz#44ae19a8f2ba97537978246072fb89d10d1fbd17"
4917+
integrity sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==
49154918
dependencies:
49164919
"@types/eslint-scope" "^3.7.7"
49174920
"@types/estree" "^1.0.6"
@@ -4931,9 +4934,9 @@ webpack@^5.73.0:
49314934
loader-runner "^4.2.0"
49324935
mime-types "^2.1.27"
49334936
neo-async "^2.6.2"
4934-
schema-utils "^3.2.0"
4937+
schema-utils "^4.3.0"
49354938
tapable "^2.1.1"
4936-
terser-webpack-plugin "^5.3.10"
4939+
terser-webpack-plugin "^5.3.11"
49374940
watchpack "^2.4.1"
49384941
webpack-sources "^3.2.3"
49394942

0 commit comments

Comments
 (0)