Skip to content

Commit a077817

Browse files
authored
chore: ban URL due to incompatibilities with React Native's Hermes engine (#249)
1 parent e498e0a commit a077817

File tree

3 files changed

+24
-29
lines changed

3 files changed

+24
-29
lines changed

.eslintrc.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,16 @@ module.exports = {
4848
{
4949
name: 'atob',
5050
message:
51-
"'atob' unavailable in React Native 0.72. Use 'decodeBase64' helper in src/obfuscation.ts instead",
51+
"'atob' unavailable in React Native's Hermes JS engine. Use 'decodeBase64' helper in src/obfuscation.ts instead",
5252
},
5353
{
5454
name: 'btoa',
5555
message:
56-
"'btoa' unavailable in React Native 0.72. Use 'encodeBase64' helper in src/obfuscation.ts instead",
56+
"'btoa' unavailable in React Native's Hermes JS engine. Use 'encodeBase64' helper in src/obfuscation.ts instead",
57+
},
58+
{
59+
name: 'URL',
60+
message: "URL is improperly implemented in React Native's Hermes JS engine.",
5761
},
5862
{
5963
name: 'Buffer',

src/api-endpoints.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,26 @@ export default class ApiEndpoints {
1717
this.params.baseUrl = params.baseUrl ?? DEFAULT_BASE_URL;
1818
}
1919

20-
endpoint(resource: string): URL {
21-
const url = new URL(this.params.baseUrl + resource);
22-
Object.entries(this.params.queryParams ?? {}).forEach(([key, value]) =>
23-
url.searchParams.append(key, value),
24-
);
25-
return url;
20+
endpoint(resource: string): string {
21+
const endpointUrl = `${this.params.baseUrl}${resource}`;
22+
const queryParams = this.params.queryParams;
23+
if (!queryParams) {
24+
return endpointUrl;
25+
}
26+
const urlSearchParams = new URLSearchParams();
27+
Object.entries(queryParams).forEach(([key, value]) => urlSearchParams.append(key, value));
28+
return `${endpointUrl}?${urlSearchParams}`;
2629
}
2730

28-
ufcEndpoint(): URL {
31+
ufcEndpoint(): string {
2932
return this.endpoint(UFC_ENDPOINT);
3033
}
3134

32-
banditParametersEndpoint(): URL {
35+
banditParametersEndpoint(): string {
3336
return this.endpoint(BANDIT_ENDPOINT);
3437
}
3538

36-
precomputedFlagsEndpoint(): URL {
39+
precomputedFlagsEndpoint(): string {
3740
return this.endpoint(PRECOMPUTED_FLAGS_ENDPOINT);
3841
}
3942
}

src/http-client.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,14 @@ 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-
6149
export interface IHttpClient {
6250
getUniversalFlagConfiguration(): Promise<IUniversalFlagConfigResponse | undefined>;
6351
getBanditParameters(): Promise<IBanditParametersResponse | undefined>;
6452
getPrecomputedFlags(
6553
payload: PrecomputedFlagsPayload,
6654
): Promise<IObfuscatedPrecomputedConfigurationResponse | undefined>;
67-
rawGet<T>(url: URL): Promise<T | undefined>;
68-
rawPost<T, P>(url: URL, payload: P): Promise<T | undefined>;
55+
rawGet<T>(url: string): Promise<T | undefined>;
56+
rawPost<T, P>(url: string, payload: P): Promise<T | undefined>;
6957
}
7058

7159
export default class FetchHttpClient implements IHttpClient {
@@ -94,14 +82,14 @@ export default class FetchHttpClient implements IHttpClient {
9482
);
9583
}
9684

97-
async rawGet<T>(url: URL): Promise<T | undefined> {
85+
async rawGet<T>(url: string): Promise<T | undefined> {
9886
try {
9987
// Canonical implementation of abortable fetch for interrupting when request takes longer than desired.
10088
// https://developer.chrome.com/blog/abortable-fetch/#reacting_to_an_aborted_fetch
10189
const controller = new AbortController();
10290
const signal = controller.signal;
10391
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
104-
const response = await fetch(urlWithNoTrailingSlash(url), { signal });
92+
const response = await fetch(url, { signal });
10593
// Clear timeout when response is received within the budget.
10694
clearTimeout(timeoutId);
10795

@@ -120,13 +108,13 @@ export default class FetchHttpClient implements IHttpClient {
120108
}
121109
}
122110

123-
async rawPost<T, P>(url: URL, payload: P): Promise<T | undefined> {
111+
async rawPost<T, P>(url: string, payload: P): Promise<T | undefined> {
124112
try {
125113
const controller = new AbortController();
126114
const signal = controller.signal;
127115
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
128116

129-
const response = await fetch(urlWithNoTrailingSlash(url), {
117+
const response = await fetch(url, {
130118
method: 'POST',
131119
headers: {
132120
'Content-Type': 'application/json',

0 commit comments

Comments
 (0)