Skip to content

Commit 782234c

Browse files
authored
Merge pull request #218 from thedanchez/xmlhttprequest
feat: replace xmlhttprequest with fetch
2 parents 2577042 + 4682897 commit 782234c

File tree

5 files changed

+156
-168
lines changed

5 files changed

+156
-168
lines changed

examples/todo-server-tests/features/support/world.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ export class CustomWorld {
152152
addRequestIdHeaderToFeatureUpdater(): FeatureUpdater {
153153
const updater = new FeatureUpdater(Config.fhConfig);
154154
(updater.manager as NodejsFeaturePostUpdater).modifyRequestFunction = (req) => {
155-
// @ts-expect-error - TODO: revisit this later
156155
req.headers["Baggage"] = `cuke-req-id=${reqIdPrefix}${requestId}`;
157156
requestId++;
158157
};

packages/js/src/analytics.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ export interface AnalyticsCollector {
1212
export interface GoogleAnalyticsApiClient {
1313
cid(other: Map<string, string>): string;
1414

15-
postBatchUpdate(batchData: string): void;
15+
postBatchUpdate(batchData: string): Promise<Response> | Promise<void>;
1616
}
1717

1818
class BrowserGoogleAnalyticsApiClient implements GoogleAnalyticsApiClient {
1919
cid(other: Map<string, string>): string {
2020
return other.get("cid") || "";
2121
}
2222

23-
postBatchUpdate(batchData: string): void {
24-
const req = new XMLHttpRequest();
25-
req.open("POST", "https://www.google-analytics.com/batch");
26-
req.send(batchData);
27-
}
23+
postBatchUpdate = async (batchData: string) => {
24+
return fetch("https://www.google-analytics.com/batch", {
25+
method: "POST",
26+
body: batchData,
27+
});
28+
};
2829
}
2930

3031
type GoogleAnalyticsApiClientProvider = () => GoogleAnalyticsApiClient;

packages/js/src/polling_sdk.ts

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,6 @@ export interface BrowserOptions {
123123
export class BrowserPollingService extends PollingBase implements PollingService {
124124
private localStorageLastUrl?: string;
125125

126-
// override this with a replacement if you need to, for example to add any headers.
127-
static httpRequestor = () => {
128-
return new XMLHttpRequest();
129-
};
130-
131126
// override this in React Native - for example use AsyncStorage
132127
static localStorageRequestor = () => {
133128
if (window.localStorage) {
@@ -169,7 +164,7 @@ export class BrowserPollingService extends PollingBase implements PollingService
169164
}
170165
}
171166

172-
public poll(): Promise<void> {
167+
public async poll(): Promise<void> {
173168
if (this._busy) {
174169
return new Promise((resolve, reject) => {
175170
this._outstandingPromises.push({ resolve: resolve, reject: reject } as PromiseLikeData);
@@ -182,62 +177,45 @@ export class BrowserPollingService extends PollingBase implements PollingService
182177
});
183178
}
184179

185-
return new Promise((resolve, reject) => {
186-
const calculatedUrl = `${this.url}&contextSha=${this._shaHeader}`;
180+
// check in case we have a cached copy of it
181+
this.loadLocalState(this.url);
187182

188-
// check in case we have a cached copy of it
189-
this.loadLocalState(this.url);
183+
const headers: Record<string, string> = {
184+
"Content-type": "application/json",
185+
...(this._etag ? { "if-none-match": this._etag } : {}),
186+
...(this._header ? { "x-featurehub": this._header } : {}),
187+
};
190188

191-
const req = BrowserPollingService.httpRequestor();
192-
req.open("GET", calculatedUrl);
193-
req.setRequestHeader("Content-type", "application/json");
189+
const response = await fetch(`${this.url}&contextSha=${this._shaHeader}`, { headers });
194190

195-
if (this._etag) {
196-
req.setRequestHeader("if-none-match", this._etag);
197-
}
191+
if (response.status === 304) {
192+
this._busy = false;
193+
this.resolveOutstanding();
194+
return;
195+
} else if (!response.ok) {
196+
this._busy = false;
197+
this.rejectOutstanding(response.status);
198+
throw new Error(`Failed to fetch features: ${response.statusText}`);
199+
}
198200

199-
if (this._header) {
200-
req.setRequestHeader("x-featurehub", this._header);
201-
}
201+
this._etag = response.headers.get("etag");
202+
this.parseCacheControl(response.headers.get("cache-control"));
202203

203-
req.send();
204+
const environments = JSON.parse(await response.text()) as FeatureEnvironmentCollection[];
204205

205-
req.onreadystatechange = () => {
206-
if (req.readyState === 4) {
207-
if (req.status === 200 || req.status == 236) {
208-
this._etag = req.getResponseHeader("etag");
209-
this.parseCacheControl(req.getResponseHeader("cache-control"));
206+
try {
207+
BrowserPollingService.localStorageRequestor().setItem(
208+
this.url,
209+
JSON.stringify({ e: environments }),
210+
);
211+
} catch (_) {
212+
fhLog.error("featurehub: unable to cache features");
213+
}
210214

211-
const environments = JSON.parse(
212-
req.responseText,
213-
) as Array<FeatureEnvironmentCollection>;
214-
try {
215-
BrowserPollingService.localStorageRequestor().setItem(
216-
this.url,
217-
JSON.stringify({ e: environments }),
218-
);
219-
} catch (_) {
220-
fhLog.error("featurehub: unable to cache features");
221-
}
222-
this._callback(environments);
223-
224-
this._stopped = req.status === 236;
225-
this._busy = false;
226-
this.resolveOutstanding();
227-
resolve();
228-
} else if (req.status == 304) {
229-
// no change
230-
this._busy = false;
231-
this.resolveOutstanding();
232-
resolve();
233-
} else {
234-
this._busy = false;
235-
this.rejectOutstanding(req.status);
236-
reject(req.status);
237-
}
238-
}
239-
};
240-
});
215+
this._callback(environments);
216+
this._stopped = response.status === 236;
217+
this._busy = false;
218+
this.resolveOutstanding();
241219
}
242220
}
243221

packages/js/src/test_sdk.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ export interface FeatureUpdatePostManager {
77

88
class BrowserFeaturePostUpdater implements FeatureUpdatePostManager {
99
post(url: string, update: FeatureStateUpdate): Promise<boolean> {
10-
return new Promise<boolean>((resolve) => {
11-
const req = new XMLHttpRequest();
12-
req.open("PUT", url);
13-
req.setRequestHeader("Content-type", "application/json");
14-
req.send(JSON.stringify(update));
15-
req.onreadystatechange = function () {
16-
if (req.readyState === 4) {
17-
resolve(req.status >= 200 && req.status < 300);
18-
}
19-
};
20-
});
10+
return fetch(url, {
11+
method: "PUT",
12+
headers: {
13+
"Content-type": "application/json",
14+
},
15+
body: JSON.stringify(update),
16+
})
17+
.then((res) => res.status >= 200 && res.status < 300)
18+
.catch(() => false);
2119
}
2220
}
2321

0 commit comments

Comments
 (0)