Skip to content

Commit a2bd6ea

Browse files
committed
feat(transport): add the ability to switch transport API
For the browser version of PubNub SDK, add the ability to switch between `fetch` and `xhr` APIs (`transport` configuration option). fix(event-engine): handshake/receive requests timeout Fix issue because of which, in Event Engine mode, wrong timeout values have been set for requests which create long-poll request. refactor(request): make sure request cancels on timeout Refactor `timeout` implementation for `fetch` transport to properly cancel request when the timeout timer will fire.
1 parent a204205 commit a2bd6ea

20 files changed

Lines changed: 924 additions & 443 deletions

File tree

dist/web/pubnub.js

Lines changed: 393 additions & 296 deletions
Large diffs are not rendered by default.

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/request.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class AbstractRequest {
103103
path: this.path,
104104
queryParameters: this.queryParameters,
105105
cancellable: (_d = (_c = this.params) === null || _c === void 0 ? void 0 : _c.cancellable) !== null && _d !== void 0 ? _d : false,
106-
timeout: 10000,
106+
timeout: 10,
107107
identifier: this.requestIdentifier,
108108
};
109109
// Attach headers (if required).

lib/core/pubnub-common.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -550,21 +550,23 @@ class PubNubCore {
550550
}
551551
// Complete request configuration.
552552
const transportRequest = request.request();
553+
const operation = request.operation();
553554
if ((transportRequest.formData && transportRequest.formData.length > 0) ||
554-
request.operation() === operations_1.default.PNDownloadFileOperation) {
555+
operation === operations_1.default.PNDownloadFileOperation) {
555556
// Set file upload / download request delay.
556557
transportRequest.timeout = this._configuration.getFileTimeout();
557558
}
558559
else {
559-
if (request.operation() === operations_1.default.PNSubscribeOperation)
560+
if (operation === operations_1.default.PNSubscribeOperation ||
561+
operation === operations_1.default.PNReceiveMessagesOperation)
560562
transportRequest.timeout = this._configuration.getSubscribeTimeout();
561563
else
562564
transportRequest.timeout = this._configuration.getTransactionTimeout();
563565
}
564566
// API request processing status.
565567
const status = {
566568
error: false,
567-
operation: request.operation(),
569+
operation,
568570
category: categories_1.default.PNAcknowledgmentCategory,
569571
statusCode: 0,
570572
};
@@ -600,8 +602,8 @@ class PubNubCore {
600602
const apiError = !(error instanceof pubnub_api_error_1.PubNubAPIError) ? pubnub_api_error_1.PubNubAPIError.create(error) : error;
601603
// Notify callback (if possible).
602604
if (callback)
603-
return callback(apiError.toStatus(request.operation()), null);
604-
throw apiError.toPubNubError(request.operation(), 'REST API request processing error, check status for details');
605+
return callback(apiError.toStatus(operation), null);
606+
throw apiError.toPubNubError(operation, 'REST API request processing error, check status for details');
605607
});
606608
});
607609
}

lib/errors/pubnub-api-error.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ class PubNubAPIError extends Error {
5757
category = categories_1.default.PNCancelledCategory;
5858
message = 'Request cancelled';
5959
}
60-
else if (message.indexOf('timeout') !== -1) {
60+
else if (message.toLowerCase().indexOf('timeout') !== -1) {
6161
category = categories_1.default.PNTimeoutCategory;
6262
message = 'Request timeout';
6363
}
64-
else if (message.indexOf('network') !== -1) {
64+
else if (message.toLowerCase().indexOf('network') !== -1) {
6565
category = categories_1.default.PNNetworkIssuesCategory;
6666
message = 'Network issues';
6767
}

lib/react_native/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ const text_encoding_1 = require("text-encoding");
77
require("react-native-url-polyfill/auto");
88
const cbor_js_1 = __importDefault(require("cbor-js"));
99
const buffer_1 = require("buffer");
10-
const web_react_native_transport_1 = require("../transport/web-react-native-transport");
1110
const stringify_buffer_keys_1 = require("../core/components/stringify_buffer_keys");
11+
const react_native_transport_1 = require("../transport/react-native-transport");
1212
const configuration_1 = require("../core/components/configuration");
1313
const token_manager_1 = require("../core/components/token_manager");
1414
const middleware_1 = require("../transport/middleware");
@@ -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(fetch, clientConfiguration.keepAlive, clientConfiguration.logVerbosity),
65+
transport: new react_native_transport_1.ReactNativeTransport(clientConfiguration.keepAlive, clientConfiguration.logVerbosity),
6666
});
6767
super({
6868
configuration: clientConfiguration,

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

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,34 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
1414
});
1515
};
1616
Object.defineProperty(exports, "__esModule", { value: true });
17-
exports.WebReactNativeTransport = void 0;
17+
exports.ReactNativeTransport = void 0;
1818
const pubnub_api_error_1 = require("../errors/pubnub-api-error");
1919
const utils_1 = require("../core/utils");
2020
/**
21-
* Class representing a `fetch`-based browser and React Native transport provider.
21+
* Class representing a React Native transport provider.
2222
*
2323
* @internal
2424
*/
25-
class WebReactNativeTransport {
25+
class ReactNativeTransport {
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.
3029
* @param [keepAlive] - Whether client should try to keep connections open for reuse or not.
3130
* @param logVerbosity - Whether verbose logs should be printed or not.
3231
*
3332
* @internal
3433
*/
35-
constructor(originalFetch, keepAlive = false, logVerbosity = false) {
34+
constructor(keepAlive = false, logVerbosity = false) {
3635
this.keepAlive = keepAlive;
3736
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-
}
4737
}
4838
makeSendable(req) {
49-
let controller;
50-
let abortController;
51-
if (req.cancellable) {
52-
abortController = new AbortController();
53-
controller = {
54-
// Storing controller inside to prolong object lifetime.
55-
abortController,
56-
abort: () => abortController === null || abortController === void 0 ? void 0 : abortController.abort(),
57-
};
58-
}
39+
const abortController = new AbortController();
40+
const controller = {
41+
// Storing controller inside to prolong object lifetime.
42+
abortController,
43+
abort: () => !abortController.signal.aborted && abortController.abort(),
44+
};
5945
return [
6046
this.requestFromTransportRequest(req).then((request) => {
6147
const start = new Date().getTime();
@@ -65,21 +51,27 @@ class WebReactNativeTransport {
6551
*
6652
* **Note:** Native Fetch API doesn't support `timeout` out-of-box.
6753
*/
54+
let timeoutId;
6855
const requestTimeout = new Promise((_, reject) => {
69-
const timeoutId = setTimeout(() => {
70-
// Clean up.
56+
timeoutId = setTimeout(() => {
7157
clearTimeout(timeoutId);
7258
reject(new Error('Request timeout'));
59+
controller.abort();
7360
}, req.timeout * 1000);
7461
});
7562
return Promise.race([
76-
WebReactNativeTransport.originalFetch(request, {
77-
signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal,
63+
fetch(request, {
64+
signal: abortController.signal,
7865
credentials: 'omit',
7966
cache: 'no-cache',
8067
}),
8168
requestTimeout,
8269
])
70+
.then((response) => {
71+
if (timeoutId)
72+
clearTimeout(timeoutId);
73+
return response;
74+
})
8375
.then((response) => response.arrayBuffer().then((arrayBuffer) => [response, arrayBuffer]))
8476
.then((response) => {
8577
const responseBody = response[1].byteLength > 0 ? response[1] : undefined;
@@ -177,7 +169,7 @@ class WebReactNativeTransport {
177169
if (typeof requestBody === 'string')
178170
outgoing += `\n\n${requestBody}`;
179171
else
180-
outgoing += `\n\n${WebReactNativeTransport.decoder.decode(requestBody)}`;
172+
outgoing += `\n\n${ReactNativeTransport.decoder.decode(requestBody)}`;
181173
}
182174
console.log(`<<<<<`);
183175
console.log(outgoing);
@@ -186,28 +178,17 @@ class WebReactNativeTransport {
186178
else {
187179
let outgoing = `[${timestamp} / ${elapsed}]\n${protocol}//${host}${pathname}\n${search}`;
188180
if (body)
189-
outgoing += `\n\n${WebReactNativeTransport.decoder.decode(body)}`;
181+
outgoing += `\n\n${ReactNativeTransport.decoder.decode(body)}`;
190182
console.log('>>>>>>');
191183
console.log(outgoing);
192184
console.log('-----');
193185
}
194186
}
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-
}
206187
}
207-
exports.WebReactNativeTransport = WebReactNativeTransport;
188+
exports.ReactNativeTransport = ReactNativeTransport;
208189
/**
209190
* Service {@link ArrayBuffer} response decoder.
210191
*
211192
* @internal
212193
*/
213-
WebReactNativeTransport.decoder = new TextDecoder();
194+
ReactNativeTransport.decoder = new TextDecoder();

lib/types/index.d.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2753,14 +2753,35 @@ declare namespace PubNub {
27532753
validate(): void;
27542754
};
27552755

2756+
/**
2757+
* Policy, which uses linear formula to calculate next request retry attempt time.
2758+
*/
27562759
export type LinearRetryPolicyConfiguration = {
2760+
/**
2761+
* Delay between retry attempt (in seconds).
2762+
*/
27572763
delay: number;
2764+
/**
2765+
* Maximum number of retry attempts.
2766+
*/
27582767
maximumRetry: number;
27592768
};
27602769

2770+
/**
2771+
* Policy, which uses exponential formula to calculate next request retry attempt time.
2772+
*/
27612773
export type ExponentialRetryPolicyConfiguration = {
2774+
/**
2775+
* Minimum delay between retry attempts (in seconds).
2776+
*/
27622777
minimumDelay: number;
2778+
/**
2779+
* Maximum delay between retry attempts (in seconds).
2780+
*/
27632781
maximumDelay: number;
2782+
/**
2783+
* Maximum number of retry attempts.
2784+
*/
27642785
maximumRetry: number;
27652786
};
27662787

@@ -2915,7 +2936,7 @@ declare namespace PubNub {
29152936
*/
29162937
body?: ArrayBuffer | PubNubFileInterface | string;
29172938
/**
2918-
* For how long request should wait response from the server.
2939+
* For how long (in seconds) request should wait response from the server.
29192940
*
29202941
* @default `10` seconds.
29212942
*/

src/core/components/request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export abstract class AbstractRequest<ResponseType> implements Request<ResponseT
107107
path: this.path,
108108
queryParameters: this.queryParameters,
109109
cancellable: this.params?.cancellable ?? false,
110-
timeout: 10000,
110+
timeout: 10,
111111
identifier: this.requestIdentifier,
112112
};
113113

src/core/pubnub-common.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -806,22 +806,26 @@ export class PubNubCore<
806806

807807
// Complete request configuration.
808808
const transportRequest = request.request();
809+
const operation = request.operation();
809810
if (
810811
(transportRequest.formData && transportRequest.formData.length > 0) ||
811-
request.operation() === RequestOperation.PNDownloadFileOperation
812+
operation === RequestOperation.PNDownloadFileOperation
812813
) {
813814
// Set file upload / download request delay.
814815
transportRequest.timeout = this._configuration.getFileTimeout();
815816
} else {
816-
if (request.operation() === RequestOperation.PNSubscribeOperation)
817+
if (
818+
operation === RequestOperation.PNSubscribeOperation ||
819+
operation === RequestOperation.PNReceiveMessagesOperation
820+
)
817821
transportRequest.timeout = this._configuration.getSubscribeTimeout();
818822
else transportRequest.timeout = this._configuration.getTransactionTimeout();
819823
}
820824

821825
// API request processing status.
822826
const status: Status = {
823827
error: false,
824-
operation: request.operation(),
828+
operation,
825829
category: StatusCategory.PNAcknowledgmentCategory,
826830
statusCode: 0,
827831
};
@@ -862,12 +866,9 @@ export class PubNubCore<
862866
const apiError = !(error instanceof PubNubAPIError) ? PubNubAPIError.create(error) : error;
863867

864868
// Notify callback (if possible).
865-
if (callback) return callback(apiError.toStatus(request.operation()), null);
869+
if (callback) return callback(apiError.toStatus(operation), null);
866870

867-
throw apiError.toPubNubError(
868-
request.operation(),
869-
'REST API request processing error, check status for details',
870-
);
871+
throw apiError.toPubNubError(operation, 'REST API request processing error, check status for details');
871872
});
872873
}
873874

0 commit comments

Comments
 (0)