@splitsoftware/splitio-react-native
Version:
Split SDK for React Native
304 lines • 11.3 kB
JavaScript
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/**
* EventSource polyfill based on https://www.npmjs.com/package/react-native-sse
*
* The MIT License
*
* Copyright (c) 2021 Binary Minds
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
const XMLReadyStateMap = ['UNSENT', 'OPENED', 'HEADERS_RECEIVED', 'LOADING', 'DONE'];
export class EventSourceXHR {
constructor(url, options = {}) {
var _options$timeout, _options$timeoutBefor, _options$pollingInter;
_defineProperty(this, "ERROR", -1);
_defineProperty(this, "CONNECTING", 0);
_defineProperty(this, "OPEN", 1);
_defineProperty(this, "CLOSED", 2);
_defineProperty(this, "CRLF", '\r\n');
_defineProperty(this, "LF", '\n');
_defineProperty(this, "CR", '\r');
this.lastEventId = null;
this.status = this.CONNECTING;
this.eventHandlers = {
open: [],
message: [],
error: [],
done: [],
close: []
};
this.method = options.method || 'GET';
this.timeout = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : 0;
this.timeoutBeforeConnection = (_options$timeoutBefor = options.timeoutBeforeConnection) !== null && _options$timeoutBefor !== void 0 ? _options$timeoutBefor : 500;
this.withCredentials = options.withCredentials || false;
this.body = options.body || undefined;
this.debug = options.debug || false;
this.interval = (_options$pollingInter = options.pollingInterval) !== null && _options$pollingInter !== void 0 ? _options$pollingInter : 5000;
this.lineEndingCharacter = options.lineEndingCharacter || null;
const defaultHeaders = {
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
'X-Requested-With': 'XMLHttpRequest'
};
this.headers = {
...defaultHeaders,
...options.headers
};
this._xhr = null;
this._pollTimer = null;
this._lastIndexProcessed = 0;
if (!url || typeof url !== 'string' && typeof url.toString !== 'function') {
throw new SyntaxError('[EventSource] Invalid URL argument.');
}
if (typeof url.toString === 'function') {
this.url = url.toString();
} else {
this.url = url;
}
this._pollAgain(this.timeoutBeforeConnection, true);
}
_pollAgain(time, allowZero) {
if (time > 0 || allowZero) {
this._logDebug(`[EventSource] Will open new connection in ${time} ms.`);
this._pollTimer = setTimeout(() => {
this.open();
}, time);
}
}
open() {
try {
this.status = this.CONNECTING;
this._lastIndexProcessed = 0;
this._xhr = new XMLHttpRequest();
this._xhr.open(this.method, this.url, true);
if (this.withCredentials) {
this._xhr.withCredentials = true;
}
for (const [key, value] of Object.entries(this.headers)) {
if (value !== undefined && value !== null) {
this._xhr.setRequestHeader(key, value);
}
}
if (this.lastEventId !== null) {
this._xhr.setRequestHeader('Last-Event-ID', this.lastEventId);
}
this._xhr.timeout = this.timeout;
this._xhr.onreadystatechange = () => {
if (this.status === this.CLOSED) {
return;
}
const xhr = this._xhr;
this._logDebug(`[EventSource][onreadystatechange] ReadyState: ${XMLReadyStateMap[xhr.readyState] || 'Unknown'}(${xhr.readyState}), status: ${xhr.status}`);
if (![XMLHttpRequest.DONE, XMLHttpRequest.LOADING].includes(xhr.readyState)) {
return;
}
if (xhr.status >= 200 && xhr.status < 400) {
if (this.status === this.CONNECTING) {
this.status = this.OPEN;
this.dispatch('open', {
type: 'open'
});
this._logDebug('[EventSource][onreadystatechange][OPEN] Connection opened.');
}
this._handleEvent(xhr.responseText || '');
if (xhr.readyState === XMLHttpRequest.DONE) {
this._logDebug('[EventSource][onreadystatechange][DONE] Operation done.');
this._pollAgain(this.interval, false);
this.dispatch('done', {
type: 'done'
});
}
} else if (xhr.status !== 0) {
this.status = this.ERROR;
this.dispatch('error', {
type: 'error',
message: xhr.responseText,
xhrStatus: xhr.status,
xhrState: xhr.readyState
});
if (xhr.readyState === XMLHttpRequest.DONE) {
this._logDebug('[EventSource][onreadystatechange][ERROR] Response status error.');
this._pollAgain(this.interval, false);
}
}
};
this._xhr.onerror = () => {
if (this.status === this.CLOSED) {
return;
}
this.status = this.ERROR;
this.dispatch('error', {
type: 'error',
message: this._xhr.responseText,
xhrStatus: this._xhr.status,
xhrState: this._xhr.readyState
});
};
if (this.body) {
this._xhr.send(this.body);
} else {
this._xhr.send();
}
if (this.timeout > 0) {
setTimeout(() => {
if (this._xhr.readyState === XMLHttpRequest.LOADING) {
this.dispatch('error', {
type: 'timeout'
});
this.close();
}
}, this.timeout);
}
} catch (e) {
this.status = this.ERROR;
this.dispatch('error', {
type: 'exception',
message: e.message,
error: e
});
}
}
_logDebug(...msg) {
if (this.debug) {
console.debug(...msg);
}
}
_handleEvent(response) {
if (this.lineEndingCharacter === null) {
const detectedNewlineChar = this._detectNewlineChar(response);
if (detectedNewlineChar !== null) {
this._logDebug(`[EventSource] Automatically detected lineEndingCharacter: ${JSON.stringify(detectedNewlineChar).slice(1, -1)}`);
this.lineEndingCharacter = detectedNewlineChar;
} else {
console.warn("[EventSource] Unable to identify the line ending character. Ensure your server delivers a standard line ending character: \\r\\n, \\n, \\r, or specify your custom character using the 'lineEndingCharacter' option.");
return;
}
}
const indexOfDoubleNewline = this._getLastDoubleNewlineIndex(response);
if (indexOfDoubleNewline <= this._lastIndexProcessed) {
return;
}
const parts = response.substring(this._lastIndexProcessed, indexOfDoubleNewline).split(this.lineEndingCharacter);
this._lastIndexProcessed = indexOfDoubleNewline;
let type;
let id = null;
let data = [];
let retry = 0;
let line = '';
for (let i = 0; i < parts.length; i++) {
line = parts[i].trim();
if (line.startsWith('event')) {
type = line.replace(/event:?\s*/, '');
} else if (line.startsWith('retry')) {
retry = parseInt(line.replace(/retry:?\s*/, ''), 10);
if (!isNaN(retry)) {
this.interval = retry;
}
} else if (line.startsWith('data')) {
data.push(line.replace(/data:?\s*/, ''));
} else if (line.startsWith('id')) {
id = line.replace(/id:?\s*/, '');
if (id !== '') {
this.lastEventId = id;
} else {
this.lastEventId = null;
}
} else if (line === '') {
if (data.length > 0) {
const eventType = type || 'message';
const event = {
type: eventType,
data: data.join('\n'),
url: this.url,
lastEventId: this.lastEventId
};
this.dispatch(eventType, event);
data = [];
type = undefined;
}
}
}
}
_detectNewlineChar(response) {
const supportedLineEndings = [this.CRLF, this.LF, this.CR];
for (const char of supportedLineEndings) {
if (response.includes(char)) {
return char;
}
}
return null;
}
_getLastDoubleNewlineIndex(response) {
const doubleLineEndingCharacter = this.lineEndingCharacter + this.lineEndingCharacter;
const lastIndex = response.lastIndexOf(doubleLineEndingCharacter);
if (lastIndex === -1) {
return -1;
}
return lastIndex + doubleLineEndingCharacter.length;
}
addEventListener(type, listener) {
if (this.eventHandlers[type] === undefined) {
this.eventHandlers[type] = [];
}
this.eventHandlers[type].push(listener);
}
removeEventListener(type, listener) {
if (this.eventHandlers[type] !== undefined) {
this.eventHandlers[type] = this.eventHandlers[type].filter(handler => handler !== listener);
}
}
removeAllEventListeners(type) {
const availableTypes = Object.keys(this.eventHandlers);
if (type === undefined) {
for (const eventType of availableTypes) {
this.eventHandlers[eventType] = [];
}
} else {
if (!availableTypes.includes(type)) {
throw Error(`[EventSource] '${type}' type is not supported event type.`);
}
this.eventHandlers[type] = [];
}
}
dispatch(type, data) {
const availableTypes = Object.keys(this.eventHandlers);
if (!availableTypes.includes(type)) {
return;
}
for (const handler of Object.values(this.eventHandlers[type])) {
handler(data);
}
}
close() {
if (this.status !== this.CLOSED) {
this.status = this.CLOSED;
this.dispatch('close', {
type: 'close'
});
}
clearTimeout(this._pollTimer);
if (this._xhr) {
this._xhr.abort();
}
}
}
//# sourceMappingURL=EventSourceXHR.js.map