Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions dist/web/pubnub.js
Original file line number Diff line number Diff line change
Expand Up @@ -5948,13 +5948,22 @@
this.logger = logger;
this.transport = transport;
logger.debug('WebTransport', `Create with configuration:\n - transport: ${transport}`);
if (transport === 'fetch' && (!window || !window.fetch)) {
// Check for `fetch` availability directly (works in DOM and DOM-less contexts such as service workers).
// because a bare `window` reference would throw in a service worker, and a partial `window = self` shim can
// define `window` without `fetch`, so probe the actual `fetch` global via `typeof`.
if (transport === 'fetch' && typeof fetch === 'undefined') {
logger.warn('WebTransport', `'${transport}' not supported in this browser. Fallback to the 'xhr' transport.`);
this.transport = 'xhr';
}
if (this.transport !== 'fetch')
return;
// Storing reference on original `fetch` function implementation as protection against APM lib monkey patching.
//
// This is done unconditionally (not gated on `isFetchMonkeyPatched()`): APM libraries can defeat that
// detection by overriding stringification to report `[native code]` while still patching `fetch`, so we
// cannot rely on it to decide whether the workaround is needed. `getOriginalFetch()` itself decides how to
// obtain the reference based on the environment (iframe when a DOM is available, context `fetch` otherwise),
// which keeps construction safe in DOM-less contexts such as MV3 service workers.
WebTransport.originalFetch = WebTransport.getOriginalFetch();
// Check whether `fetch` has been monkey patched or not.
if (this.isFetchMonkeyPatched())
Expand Down Expand Up @@ -6259,6 +6268,12 @@
* @returns Reference to the `fetch` function.
*/
static getOriginalFetch() {
// The iframe-based APM workaround requires a DOM. In DOM-less contexts (e.g. MV3 service workers) there is
// no `document`, and `fetch` cannot be reached through a fresh browsing context — return the context `fetch`
// directly. This is safe there: without a DOM there is no APM page script to monkey patch `fetch` in the
// first place. This environment check is what keeps the unconditional call in the constructor safe.
if (typeof document === 'undefined' || !document.body)
return fetch;
let iframe = document.querySelector('iframe[name="pubnub-context-unpatched-fetch"]');
if (!iframe) {
iframe = document.createElement('iframe');
Expand Down Expand Up @@ -18462,7 +18477,9 @@
authenticationChangeHandler = (auth) => middleware.onTokenChange(auth);
userIdChangeHandler = (userId) => middleware.onUserIdChange(userId);
transport = middleware;
if (configurationCopy.subscriptionWorkerUnsubscribeOfflineClients) {
if (configurationCopy.subscriptionWorkerUnsubscribeOfflineClients &&
typeof window !== 'undefined' &&
window.addEventListener) {
window.addEventListener('pagehide', (event) => {
if (!event.persisted)
middleware.terminate();
Expand Down Expand Up @@ -18512,7 +18529,11 @@
};
}
}
if ((_a = configuration.listenToBrowserNetworkEvents) !== null && _a !== void 0 ? _a : true) {
// `window` is unavailable in DOM-less contexts (e.g. MV3 service workers); only attach network
// listeners when a `window` with event support actually exists.
if (((_a = configuration.listenToBrowserNetworkEvents) !== null && _a !== void 0 ? _a : true) &&
typeof window !== 'undefined' &&
window.addEventListener) {
window.addEventListener('offline', () => {
this.networkDownDetected();
});
Expand Down
4 changes: 2 additions & 2 deletions dist/web/pubnub.min.js

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion src/transport/web-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ export class WebTransport implements Transport {
) {
logger.debug('WebTransport', `Create with configuration:\n - transport: ${transport}`);

if (transport === 'fetch' && (!window || !window.fetch)) {
// Check for `fetch` availability directly (works in DOM and DOM-less contexts such as service workers).
// because a bare `window` reference would throw in a service worker, and a partial `window = self` shim can
// define `window` without `fetch`, so probe the actual `fetch` global via `typeof`.
if (transport === 'fetch' && typeof fetch === 'undefined') {
logger.warn('WebTransport', `'${transport}' not supported in this browser. Fallback to the 'xhr' transport.`);

this.transport = 'xhr';
Expand All @@ -106,6 +109,12 @@ export class WebTransport implements Transport {
if (this.transport !== 'fetch') return;

// Storing reference on original `fetch` function implementation as protection against APM lib monkey patching.
//
// This is done unconditionally (not gated on `isFetchMonkeyPatched()`): APM libraries can defeat that
// detection by overriding stringification to report `[native code]` while still patching `fetch`, so we
// cannot rely on it to decide whether the workaround is needed. `getOriginalFetch()` itself decides how to
// obtain the reference based on the environment (iframe when a DOM is available, context `fetch` otherwise),
// which keeps construction safe in DOM-less contexts such as MV3 service workers.
WebTransport.originalFetch = WebTransport.getOriginalFetch();

// Check whether `fetch` has been monkey patched or not.
Expand Down Expand Up @@ -431,6 +440,12 @@ export class WebTransport implements Transport {
* @returns Reference to the `fetch` function.
*/
private static getOriginalFetch(): typeof fetch {
// The iframe-based APM workaround requires a DOM. In DOM-less contexts (e.g. MV3 service workers) there is
// no `document`, and `fetch` cannot be reached through a fresh browsing context — return the context `fetch`
// directly. This is safe there: without a DOM there is no APM page script to monkey patch `fetch` in the
// first place. This environment check is what keeps the unconditional call in the constructor safe.
if (typeof document === 'undefined' || !document.body) return fetch;

let iframe = document.querySelector<HTMLIFrameElement>('iframe[name="pubnub-context-unpatched-fetch"]');

if (!iframe) {
Expand Down
14 changes: 12 additions & 2 deletions src/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ export default class PubNub extends PubNubCore<ArrayBuffer | string, PubNubFileP
userIdChangeHandler = (userId: string) => middleware.onUserIdChange(userId);
transport = middleware;

if (configurationCopy.subscriptionWorkerUnsubscribeOfflineClients) {
if (
configurationCopy.subscriptionWorkerUnsubscribeOfflineClients &&
typeof window !== 'undefined' &&
window.addEventListener
) {
window.addEventListener(
'pagehide',
(event) => {
Expand Down Expand Up @@ -211,7 +215,13 @@ export default class PubNub extends PubNubCore<ArrayBuffer | string, PubNubFileP
}
}

if (configuration.listenToBrowserNetworkEvents ?? true) {
// `window` is unavailable in DOM-less contexts (e.g. MV3 service workers); only attach network
// listeners when a `window` with event support actually exists.
if (
(configuration.listenToBrowserNetworkEvents ?? true) &&
typeof window !== 'undefined' &&
window.addEventListener
) {
window.addEventListener('offline', () => {
this.networkDownDetected();
});
Expand Down
Loading