Skip to content

Commit 7efc331

Browse files
committed
fix(subscribe): fix long-poll request cancellation
Fix long-poll request cancellation caused by APM packages monkey patching 'fetch' and try to use 'native' implementation instead of patched.
1 parent 0d424f9 commit 7efc331

File tree

5 files changed

+104
-9
lines changed

5 files changed

+104
-9
lines changed

dist/web/pubnub.js

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,14 +3370,24 @@
33703370
/**
33713371
* Create and configure transport provider for Web and Rect environments.
33723372
*
3373+
* @param originalFetch - Pointer to the original (not monkey patched) `fetch` implementation.
33733374
* @param [keepAlive] - Whether client should try to keep connections open for reuse or not.
33743375
* @param logVerbosity - Whether verbose logs should be printed or not.
33753376
*
33763377
* @internal
33773378
*/
3378-
constructor(keepAlive = false, logVerbosity) {
3379+
constructor(originalFetch, keepAlive = false, logVerbosity = false) {
33793380
this.keepAlive = keepAlive;
33803381
this.logVerbosity = logVerbosity;
3382+
WebReactNativeTransport.originalFetch = originalFetch;
3383+
// Check whether `fetch` has been monkey patched or not.
3384+
if (logVerbosity && this.isFetchMonkeyPatched()) {
3385+
console.warn("[PubNub] Native Web Fetch API 'fetch' function monkey patched.");
3386+
if (!this.isFetchMonkeyPatched(WebReactNativeTransport.originalFetch))
3387+
console.info("[PubNub] Use native Web Fetch API 'fetch' implementation from iframe as APM workaround.");
3388+
else
3389+
console.warn('[PubNub] Unable receive native Web Fetch API. There can be issues with subscribe long-poll cancellation');
3390+
}
33813391
}
33823392
makeSendable(req) {
33833393
let controller;
@@ -3407,7 +3417,11 @@
34073417
}, req.timeout * 1000);
34083418
});
34093419
return Promise.race([
3410-
fetch(request, { signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal, credentials: 'omit', cache: 'no-cache' }),
3420+
WebReactNativeTransport.originalFetch(request, {
3421+
signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal,
3422+
credentials: 'omit',
3423+
cache: 'no-cache',
3424+
}),
34113425
requestTimeout,
34123426
])
34133427
.then((response) => response.arrayBuffer().then((arrayBuffer) => [response, arrayBuffer]))
@@ -3522,6 +3536,17 @@
35223536
console.log('-----');
35233537
}
35243538
}
3539+
/**
3540+
* Check whether original `fetch` has been monkey patched or not.
3541+
*
3542+
* @returns `true` if original `fetch` has been patched.
3543+
*
3544+
* @internal
3545+
*/
3546+
isFetchMonkeyPatched(oFetch) {
3547+
const fetchString = (oFetch !== null && oFetch !== void 0 ? oFetch : fetch).toString();
3548+
return !fetchString.includes('[native code]') && fetch.name !== 'fetch';
3549+
}
35253550
}
35263551
/**
35273552
* Service {@link ArrayBuffer} response decoder.
@@ -14544,7 +14569,7 @@
1454414569
let cryptography;
1454514570
cryptography = new WebCryptography();
1454614571
// Setup transport provider.
14547-
let transport = new WebReactNativeTransport(clientConfiguration.keepAlive, clientConfiguration.logVerbosity);
14572+
let transport = new WebReactNativeTransport(PubNub.originalFetch(), clientConfiguration.keepAlive, clientConfiguration.logVerbosity);
1454814573
{
1454914574
if (configurationCopy.subscriptionWorkerUrl) {
1455014575
// Inject subscription worker into transport provider stack.
@@ -14593,6 +14618,19 @@
1459314618
this.listenerManager.announceNetworkUp();
1459414619
this.reconnect();
1459514620
}
14621+
static originalFetch() {
14622+
let iframe = document.querySelector('iframe[name="pubnub-context-unpatched-fetch"]');
14623+
if (!iframe) {
14624+
iframe = document.createElement('iframe');
14625+
iframe.style.display = 'none';
14626+
iframe.name = 'pubnub-context-unpatched-fetch';
14627+
iframe.src = 'about:blank';
14628+
document.body.appendChild(iframe);
14629+
}
14630+
if (iframe.contentWindow)
14631+
return iframe.contentWindow.fetch.bind(iframe.contentWindow);
14632+
return fetch;
14633+
}
1459614634
}
1459714635
/**
1459814636
* Data encryption / decryption module constructor.

dist/web/pubnub.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/react_native/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export default class PubNub extends PubNubCore<null, PubNubFileParameters> {
7272
const transportMiddleware = new PubNubMiddleware({
7373
clientConfiguration,
7474
tokenManager,
75-
transport: new WebReactNativeTransport(clientConfiguration.keepAlive, clientConfiguration.logVerbosity!),
75+
transport: new WebReactNativeTransport(fetch, clientConfiguration.keepAlive, clientConfiguration.logVerbosity!),
7676
});
7777

7878
super({

src/transport/web-react-native-transport.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ import { queryStringFromObject } from '../core/utils';
1717
* @internal
1818
*/
1919
export class WebReactNativeTransport implements Transport {
20+
/**
21+
* Pointer to the "clean" `fetch` function.
22+
*
23+
* This protects against APM which overload implementation and may break crucial functionality.
24+
*
25+
* @internal
26+
*/
27+
private static originalFetch: typeof fetch;
28+
2029
/**
2130
* Service {@link ArrayBuffer} response decoder.
2231
*
@@ -27,15 +36,30 @@ export class WebReactNativeTransport implements Transport {
2736
/**
2837
* Create and configure transport provider for Web and Rect environments.
2938
*
39+
* @param originalFetch - Pointer to the original (not monkey patched) `fetch` implementation.
3040
* @param [keepAlive] - Whether client should try to keep connections open for reuse or not.
3141
* @param logVerbosity - Whether verbose logs should be printed or not.
3242
*
3343
* @internal
3444
*/
3545
constructor(
46+
originalFetch: unknown,
3647
private keepAlive: boolean = false,
37-
private readonly logVerbosity: boolean,
38-
) {}
48+
private readonly logVerbosity: boolean = false,
49+
) {
50+
WebReactNativeTransport.originalFetch = originalFetch as typeof fetch;
51+
52+
// Check whether `fetch` has been monkey patched or not.
53+
if (logVerbosity && this.isFetchMonkeyPatched()) {
54+
console.warn("[PubNub] Native Web Fetch API 'fetch' function monkey patched.");
55+
if (!this.isFetchMonkeyPatched(WebReactNativeTransport.originalFetch))
56+
console.info("[PubNub] Use native Web Fetch API 'fetch' implementation from iframe as APM workaround.");
57+
else
58+
console.warn(
59+
'[PubNub] Unable receive native Web Fetch API. There can be issues with subscribe long-poll cancellation',
60+
);
61+
}
62+
}
3963

4064
makeSendable(req: TransportRequest): [Promise<TransportResponse>, CancellationController | undefined] {
4165
let controller: CancellationController | undefined;
@@ -71,7 +95,11 @@ export class WebReactNativeTransport implements Transport {
7195
});
7296

7397
return Promise.race([
74-
fetch(request, { signal: abortController?.signal, credentials: 'omit', cache: 'no-cache' }),
98+
WebReactNativeTransport.originalFetch(request, {
99+
signal: abortController?.signal,
100+
credentials: 'omit',
101+
cache: 'no-cache',
102+
}),
75103
requestTimeout,
76104
])
77105
.then((response): Promise<[Response, ArrayBuffer]> | [Response, ArrayBuffer] =>
@@ -198,4 +226,17 @@ export class WebReactNativeTransport implements Transport {
198226
console.log('-----');
199227
}
200228
}
229+
230+
/**
231+
* Check whether original `fetch` has been monkey patched or not.
232+
*
233+
* @returns `true` if original `fetch` has been patched.
234+
*
235+
* @internal
236+
*/
237+
private isFetchMonkeyPatched(oFetch?: typeof fetch): boolean {
238+
const fetchString = (oFetch ?? fetch).toString();
239+
240+
return !fetchString.includes('[native code]') && fetch.name !== 'fetch';
241+
}
201242
}

src/web/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export default class PubNub extends PubNubCore<ArrayBuffer | string, PubNubFileP
9393

9494
// Setup transport provider.
9595
let transport: Transport = new WebReactNativeTransport(
96+
PubNub.originalFetch(),
9697
clientConfiguration.keepAlive,
9798
clientConfiguration.logVerbosity!,
9899
);
@@ -150,4 +151,19 @@ export default class PubNub extends PubNubCore<ArrayBuffer | string, PubNubFileP
150151
this.listenerManager.announceNetworkUp();
151152
this.reconnect();
152153
}
154+
155+
private static originalFetch(): typeof fetch {
156+
let iframe = document.querySelector<HTMLIFrameElement>('iframe[name="pubnub-context-unpatched-fetch"]');
157+
158+
if (!iframe) {
159+
iframe = document.createElement('iframe');
160+
iframe.style.display = 'none';
161+
iframe.name = 'pubnub-context-unpatched-fetch';
162+
iframe.src = 'about:blank';
163+
document.body.appendChild(iframe);
164+
}
165+
166+
if (iframe.contentWindow) return iframe.contentWindow.fetch.bind(iframe.contentWindow);
167+
return fetch;
168+
}
153169
}

0 commit comments

Comments
 (0)