Skip to content

Commit a204205

Browse files
authored
Fix long-poll request cancellation (#434)
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 a204205

File tree

13 files changed

+152
-21
lines changed

13 files changed

+152
-21
lines changed

.pubnub.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
---
22
changelog:
3+
- date: 2025-01-31
4+
version: v8.7.1
5+
changes:
6+
- type: bug
7+
text: "Fix long-poll request cancellation caused by APM packages monkey patching 'fetch' and try to use 'native' implementation instead of patched."
38
- date: 2025-01-30
49
version: v8.7.0
510
changes:
@@ -1118,7 +1123,7 @@ supported-platforms:
11181123
- 'Ubuntu 14.04 and up'
11191124
- 'Windows 7 and up'
11201125
version: 'Pubnub Javascript for Node'
1121-
version: '8.7.0'
1126+
version: '8.7.1'
11221127
sdks:
11231128
- full-name: PubNub Javascript SDK
11241129
short-name: Javascript
@@ -1134,7 +1139,7 @@ sdks:
11341139
- distribution-type: source
11351140
distribution-repository: GitHub release
11361141
package-name: pubnub.js
1137-
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.7.0.zip
1142+
location: https://github.com/pubnub/javascript/archive/refs/tags/v8.7.1.zip
11381143
requires:
11391144
- name: 'agentkeepalive'
11401145
min-version: '3.5.2'
@@ -1805,7 +1810,7 @@ sdks:
18051810
- distribution-type: library
18061811
distribution-repository: GitHub release
18071812
package-name: pubnub.js
1808-
location: https://github.com/pubnub/javascript/releases/download/v8.7.0/pubnub.8.7.0.js
1813+
location: https://github.com/pubnub/javascript/releases/download/v8.7.1/pubnub.8.7.1.js
18091814
requires:
18101815
- name: 'agentkeepalive'
18111816
min-version: '3.5.2'

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v8.7.1
2+
January 31 2025
3+
4+
#### Fixed
5+
- Fix long-poll request cancellation caused by APM packages monkey patching 'fetch' and try to use 'native' implementation instead of patched.
6+
17
## v8.7.0
28
January 30 2025
39

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ Watch [Getting Started with PubNub JS SDK](https://app.dashcam.io/replay/64ee0d2
2828
npm install pubnub
2929
```
3030
* or download one of our builds from our CDN:
31-
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.7.0.js
32-
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.7.0.min.js
31+
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.7.1.js
32+
* https://cdn.pubnub.com/sdk/javascript/pubnub.8.7.1.min.js
3333
3434
2. Configure your keys:
3535

dist/web/pubnub.js

Lines changed: 42 additions & 4 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.
@@ -3975,7 +4000,7 @@
39754000
return base.PubNubFile;
39764001
},
39774002
get version() {
3978-
return '8.7.0';
4003+
return '8.7.1';
39794004
},
39804005
getVersion() {
39814006
return this.version;
@@ -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.

lib/core/components/configuration.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ const makeConfiguration = (base, setupCryptoModule) => {
120120
return base.PubNubFile;
121121
},
122122
get version() {
123-
return '8.7.0';
123+
return '8.7.1';
124124
},
125125
getVersion() {
126126
return this.version;

lib/react_native/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class PubNub extends pubnub_common_1.PubNubCore {
6262
const transportMiddleware = new middleware_1.PubNubMiddleware({
6363
clientConfiguration,
6464
tokenManager,
65-
transport: new web_react_native_transport_1.WebReactNativeTransport(clientConfiguration.keepAlive, clientConfiguration.logVerbosity),
65+
transport: new web_react_native_transport_1.WebReactNativeTransport(fetch, clientConfiguration.keepAlive, clientConfiguration.logVerbosity),
6666
});
6767
super({
6868
configuration: clientConfiguration,

lib/transport/web-react-native-transport.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,24 @@ class WebReactNativeTransport {
2626
/**
2727
* Create and configure transport provider for Web and Rect environments.
2828
*
29+
* @param originalFetch - Pointer to the original (not monkey patched) `fetch` implementation.
2930
* @param [keepAlive] - Whether client should try to keep connections open for reuse or not.
3031
* @param logVerbosity - Whether verbose logs should be printed or not.
3132
*
3233
* @internal
3334
*/
34-
constructor(keepAlive = false, logVerbosity) {
35+
constructor(originalFetch, keepAlive = false, logVerbosity = false) {
3536
this.keepAlive = keepAlive;
3637
this.logVerbosity = logVerbosity;
38+
WebReactNativeTransport.originalFetch = originalFetch;
39+
// Check whether `fetch` has been monkey patched or not.
40+
if (logVerbosity && this.isFetchMonkeyPatched()) {
41+
console.warn("[PubNub] Native Web Fetch API 'fetch' function monkey patched.");
42+
if (!this.isFetchMonkeyPatched(WebReactNativeTransport.originalFetch))
43+
console.info("[PubNub] Use native Web Fetch API 'fetch' implementation from iframe as APM workaround.");
44+
else
45+
console.warn('[PubNub] Unable receive native Web Fetch API. There can be issues with subscribe long-poll cancellation');
46+
}
3747
}
3848
makeSendable(req) {
3949
let controller;
@@ -63,7 +73,11 @@ class WebReactNativeTransport {
6373
}, req.timeout * 1000);
6474
});
6575
return Promise.race([
66-
fetch(request, { signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal, credentials: 'omit', cache: 'no-cache' }),
76+
WebReactNativeTransport.originalFetch(request, {
77+
signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal,
78+
credentials: 'omit',
79+
cache: 'no-cache',
80+
}),
6781
requestTimeout,
6882
])
6983
.then((response) => response.arrayBuffer().then((arrayBuffer) => [response, arrayBuffer]))
@@ -178,6 +192,17 @@ class WebReactNativeTransport {
178192
console.log('-----');
179193
}
180194
}
195+
/**
196+
* Check whether original `fetch` has been monkey patched or not.
197+
*
198+
* @returns `true` if original `fetch` has been patched.
199+
*
200+
* @internal
201+
*/
202+
isFetchMonkeyPatched(oFetch) {
203+
const fetchString = (oFetch !== null && oFetch !== void 0 ? oFetch : fetch).toString();
204+
return !fetchString.includes('[native code]') && fetch.name !== 'fetch';
205+
}
181206
}
182207
exports.WebReactNativeTransport = WebReactNativeTransport;
183208
/**

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pubnub",
3-
"version": "8.7.0",
3+
"version": "8.7.1",
44
"author": "PubNub <[email protected]>",
55
"description": "Publish & Subscribe Real-time Messaging with PubNub",
66
"scripts": {

src/core/components/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export const makeConfiguration = (
178178
return base.PubNubFile;
179179
},
180180
get version(): string {
181-
return '8.7.0';
181+
return '8.7.1';
182182
},
183183
getVersion(): string {
184184
return this.version;

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)