nativescript-connectycube
Version:
ConnectyCube chat and video chat SDK for NativeScript
296 lines (240 loc) • 8.81 kB
JavaScript
const config = require('./cubeConfig');
const Utils = require('./cubeInternalUtils');
const fetchImpl = require('./cubeDependencies').fetchImpl;
const formDataImpl = require('./cubeDependencies').formDataImpl;
class HTTPProxy {
constructor() {
this.sdkInstance = {
config: config,
session: null,
};
this.currentUserId = null;
this.requestsNumber = 0;
this.fetchImpl = fetchImpl;
this.abortControllersMap = {};
}
setSession(session) {
this.sdkInstance.session = session;
if (session && session.user_id) {
this.setCurrentUserId(session.user_id);
}
}
getSession() {
return this.sdkInstance.session;
}
setCurrentUserId(userId) {
this.currentUserId = userId;
}
getCurrentUserId() {
return this.currentUserId;
}
logRequest(params, requestId) {
Utils.DLog(`[Request][${requestId}]`, `${params.type || 'GET'} ${params.url}`, params);
}
logResponse(response, requestId) {
Utils.DLog(`[Response][${requestId}]`, response);
}
buildRequestAndURL(params) {
const isGetOrHeadType = !params.type || params.type === 'GET' || params.type === 'HEAD';
const isPostOrPutType = params.type && (params.type === 'POST' || params.type === 'PUT');
const token = this.sdkInstance && this.sdkInstance.session && this.sdkInstance.session.token;
const isInternalRequest = params.url.indexOf('s3.amazonaws.com') === -1;
const isMultipartFormData = params.contentType === false;
let requestBody;
let requestURL = params.url;
const requestObject = {};
requestObject.method = params.type || 'GET';
if (params.data) {
requestBody = this.buildRequestBody(params, isMultipartFormData, isPostOrPutType);
if (isGetOrHeadType) {
requestURL += '?' + requestBody;
} else {
requestObject.body = requestBody;
}
}
if (!isMultipartFormData) {
requestObject.headers = {
'Content-Type': isPostOrPutType
? 'application/json;charset=utf-8'
: 'application/x-www-form-urlencoded; charset=UTF-8',
};
}
if (isInternalRequest) {
if (!requestObject.headers) {
requestObject.headers = {};
}
requestObject.headers['CB-SDK'] = 'JS ' + config.version + ' - Client';
if (token) {
requestObject.headers['CB-Token'] = token;
}
}
if (config.timeout) {
requestObject.timeout = config.timeout;
}
return [requestObject, requestURL];
}
buildRequestBody(params, isMultipartFormData, isPostOrPutType) {
const data = params.data;
const useArrayQuery = params.useArrayQuery;
let dataObject;
if (isMultipartFormData) {
dataObject = new formDataImpl();
Object.keys(data).forEach(function (item) {
if (params.fileToCustomObject && item === 'file') {
dataObject.append(item, data[item].data, data[item].name);
} else {
dataObject.append(item, params.data[item]);
}
});
} else if (isPostOrPutType) {
dataObject = JSON.stringify(data);
} else {
dataObject = this.serializeQueryParams(data, null, useArrayQuery, 0);
}
return dataObject;
}
serializeQueryParams(obj, prefix, useArrayQuery, level) {
level = level || 0;
const parts = [];
let propName = void 0;
for (propName in obj) {
let propQueryName = this.encodeURIComponent(propName);
if (Utils.isArray(obj)) {
propQueryName = '';
}
const key = prefix ? prefix + `[${propQueryName}]` : propQueryName;
let value = obj[propName];
const isArrayVal = Utils.isArray(value);
if ((isArrayVal && (useArrayQuery || level === 0)) || Utils.isObject(value)) {
parts.push(this.serializeQueryParams(value, key, useArrayQuery, ++level));
} else {
value = isArrayVal ? value.sort().join(',') : value;
parts.push(`${key}=${this.encodeURIComponent(value)}`);
}
}
return parts.sort().join('&');
}
encodeURIComponent(str) {
return encodeURIComponent(str).replace(/[#$&+,/:;=?@\[\]]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16);
});
}
abortRequest(abortId) {
if (this.abortControllersMap[abortId]) {
const controllers = this.abortControllersMap[abortId].controllers || {};
const controllersArray = Object.values(controllers);
controllersArray.forEach((controller) => {
controller.abort();
});
}
}
processSuccessfulOrFailedRequest(abort_id) {
if (!this.abortControllersMap[abort_id]) {
return;
}
const controllers = this.abortControllersMap[abort_id].controllers || {};
const createdControllersCount = Object.keys(controllers).length;
if (!this.abortControllersMap[abort_id].doneRequestsCount) {
this.abortControllersMap[abort_id].doneRequestsCount = 1;
} else {
this.abortControllersMap[abort_id].doneRequestsCount += 1;
}
const doneRequestsCount = this.abortControllersMap[abort_id].doneRequestsCount;
if (doneRequestsCount === createdControllersCount) {
delete this.abortControllersMap[abort_id];
}
}
ajax(params) {
const requestId = ++this.requestsNumber;
return new Promise((resolve, reject) => {
this.logRequest(params, requestId);
const requestAndURL = this.buildRequestAndURL(params);
let requestObject = requestAndURL[0];
const requestURL = requestAndURL[1];
const abort_id = params.abort_id;
if (abort_id) {
let i;
if (this.abortControllersMap[abort_id]) {
const controllers = this.abortControllersMap[abort_id].controllers || [];
const index = Object.keys(controllers).length;
this.abortControllersMap[abort_id].controllers.push(new AbortController());
i = index;
} else {
this.abortControllersMap[abort_id] = {
controllers: [new AbortController()],
};
i = 0;
}
const signal = this.abortControllersMap[abort_id].controllers[i].signal;
requestObject = { ...requestObject, signal };
}
let response;
// The Promise returned from fetch() won’t reject on HTTP error
// status even if the response is an HTTP 404 or 500.
// Instead, it will resolve normally (with ok status set to false),
// and it will only reject on network failure or if anything prevented the request from completing.
fetchImpl(requestURL, requestObject)
.then((resp) => {
response = resp;
const dataType = params.dataType || 'json';
return dataType === 'text' ? response.text() : response.json();
})
.then((body) => {
this.processSuccessfulOrFailedRequest(abort_id);
if (!response.ok) {
this.processAjaxError(response, body, null, reject, resolve, params, requestId);
} else {
this.processAjaxResponse(body, resolve, requestId);
}
})
.catch((error) => {
this.processSuccessfulOrFailedRequest(abort_id);
this.processAjaxError(response, ' ', error, reject, resolve, params, requestId);
});
});
}
processAjaxResponse(body, resolve, requestId) {
const responseBody = body && body !== ' ' ? body : 'empty body';
this.logResponse(responseBody, requestId);
resolve(body);
}
processAjaxError(response, body, error, reject, resolve, params, requestId) {
if (!response && error && !error.code) {
reject(error);
return;
}
const statusCode = response && (response.status || response.statusCode);
const errorObject = {
code: (response && statusCode) || (error && error.code),
info: (body && typeof body === 'string' && body !== ' ' ? JSON.parse(body) : body) || (error && error.errno),
};
const responseBody = body || error || body.errors;
this.logResponse(responseBody, requestId);
if (response.url.indexOf(config.urls.session) === -1) {
if (Utils.isExpiredSessionError(errorObject) && typeof config.on.sessionExpired === 'function') {
this.handleExpiredSessionResponse(errorObject, null, reject, resolve, params);
} else {
reject(errorObject);
}
} else {
reject(errorObject);
}
}
handleExpiredSessionResponse(error, response, reject, resolve, params) {
const handleResponse = () => {
if (error) {
reject(error);
} else {
resolve(response);
}
};
const retryCallback = (session) => {
if (session) {
this.setSession(session);
this.ajax(params).then(resolve).catch(reject);
}
};
config.on.sessionExpired(handleResponse, retryCallback);
}
}
module.exports = HTTPProxy;