box-node-sdk
Version:
Official SDK for Box Platform APIs
280 lines • 15.9 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BoxNetworkClient = exports.shouldIncludeBoxUaHeader = exports.xBoxUaHeader = exports.userAgentHeader = void 0;
exports.getRetryTimeout = getRetryTimeout;
const node_fetch_1 = __importDefault(require("node-fetch"));
const errors_1 = require("../box/errors");
const utils_1 = require("../internal/utils");
const version_1 = require("./version");
const json_1 = require("../serialization/json");
const retries_1 = require("./retries");
const logging_1 = require("../internal/logging");
exports.userAgentHeader = `Box JavaScript generated SDK v${version_1.sdkVersion} (${(0, utils_1.isBrowser)() ? navigator.userAgent : `Node ${process.version}`})`;
exports.xBoxUaHeader = constructBoxUAHeader();
const shouldIncludeBoxUaHeader = (options) => {
return !((0, utils_1.isBrowser)() &&
(options.responseFormat === 'binary' ||
options.responseFormat === 'no_content'));
};
exports.shouldIncludeBoxUaHeader = shouldIncludeBoxUaHeader;
function createRequestInit(options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const { method = 'GET', headers = {}, contentType: contentTypeInput = 'application/json', data, fileStream, } = options;
const { contentHeaders = {}, body } = yield (() => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const contentHeaders = {};
if (options.multipartData) {
const FormDataClass = (0, utils_1.isBrowser)() ? window.FormData : utils_1.FormData;
const formData = new FormDataClass();
for (const item of options.multipartData) {
if (item.fileStream) {
const buffer = yield (0, utils_1.readByteStream)(item.fileStream);
const blob = (0, utils_1.isBrowser)()
? new Blob([new Uint8Array(buffer)])
: buffer;
contentHeaders['content-md5'] = yield (0, utils_1.calculateMD5Hash)(buffer);
formData.append(item.partName, blob, {
filename: (_a = item.fileName) !== null && _a !== void 0 ? _a : 'file',
contentType: (_b = item.contentType) !== null && _b !== void 0 ? _b : 'application/octet-stream',
});
}
else if (item.data) {
formData.append(item.partName, (0, json_1.sdToJson)(item.data));
}
else {
throw new errors_1.BoxSdkError({
message: 'Multipart item must have either body or fileStream',
});
}
}
return {
contentHeaders: Object.assign(Object.assign({}, (!(0, utils_1.isBrowser)() && {
'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
})), contentHeaders),
body: formData,
};
}
contentHeaders['Content-Type'] = contentTypeInput;
switch (contentTypeInput) {
case 'application/json':
case 'application/json-patch+json':
return { contentHeaders, body: (0, json_1.sdToJson)(data) };
case 'application/x-www-form-urlencoded':
return { contentHeaders, body: (0, json_1.sdToUrlParams)(data) };
case 'application/octet-stream':
if (!fileStream) {
throw new errors_1.BoxSdkError({
message: 'fileStream required for application/octet-stream content type',
});
}
return {
contentHeaders,
body: (0, utils_1.isBrowser)()
? yield (0, utils_1.readByteStream)(fileStream)
: fileStream,
};
default:
throw new errors_1.BoxSdkError({
message: `Unsupported content type : ${contentTypeInput}`,
});
}
}))();
return Object.assign({ method, headers: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (method != 'GET' && contentHeaders)), headers), (options.auth && {
Authorization: yield options.auth.retrieveAuthorizationHeader(options.networkSession),
})), ((0, exports.shouldIncludeBoxUaHeader)(options) && {
'User-Agent': exports.userAgentHeader,
'X-Box-UA': exports.xBoxUaHeader,
})), (_a = options.networkSession) === null || _a === void 0 ? void 0 : _a.additionalHeaders), body: body, signal: options.cancellationToken, agent: (_b = options.networkSession) === null || _b === void 0 ? void 0 : _b.agent }, (fileStream && (0, utils_1.isBrowser)() && { duplex: 'half' }));
});
}
class BoxNetworkClient {
constructor(fields) {
Object.assign(this, fields);
}
fetch(options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const attemptNumber = (_a = options.attemptNumber) !== null && _a !== void 0 ? _a : 1;
let numberOfRetriesOnException = (_b = options.numberOfRetriesOnException) !== null && _b !== void 0 ? _b : 0;
const interceptors = (_d = (_c = options.networkSession) === null || _c === void 0 ? void 0 : _c.interceptors) !== null && _d !== void 0 ? _d : [];
const retryStrategy = (_f = (_e = options.networkSession) === null || _e === void 0 ? void 0 : _e.retryStrategy) !== null && _f !== void 0 ? _f : new retries_1.BoxRetryStrategy({});
const dataSanitizer = (_h = (_g = options.networkSession) === null || _g === void 0 ? void 0 : _g.dataSanitizer) !== null && _h !== void 0 ? _h : new logging_1.DataSanitizer({});
const fetchOptions = interceptors.length
? interceptors.reduce((modifiedOptions, interceptor) => interceptor.beforeRequest(modifiedOptions), options)
: options;
const fileStreamBuffer = fetchOptions.fileStream
? yield (0, utils_1.readByteStream)(fetchOptions.fileStream)
: void 0;
const multipartBuffer = fetchOptions.multipartData
? yield (0, utils_1.multipartStreamToBuffer)(fetchOptions.multipartData)
: void 0;
let isExceptionCase = false;
let fetchResponse;
let responseBytesBuffer;
let caughtError;
const { params = {} } = fetchOptions;
const requestInit = yield createRequestInit(Object.assign(Object.assign({}, fetchOptions), { fileStream: fileStreamBuffer
? (0, utils_1.generateByteStreamFromBuffer)(fileStreamBuffer)
: void 0, multipartData: multipartBuffer
? (0, utils_1.multipartBufferToStream)(multipartBuffer)
: void 0 }));
try {
const response = yield (0, node_fetch_1.default)(''.concat(fetchOptions.url, Object.keys(params).length === 0 || fetchOptions.url.endsWith('?')
? ''
: '?', new URLSearchParams(params).toString()), Object.assign(Object.assign({}, requestInit), { redirect: (0, utils_1.isBrowser)() ? 'follow' : 'manual' }));
const contentType = (_j = response.headers.get('content-type')) !== null && _j !== void 0 ? _j : '';
const ignoreResponseBody = fetchOptions.followRedirects === false;
let data;
let content = (0, utils_1.generateByteStreamFromBuffer)(new Uint8Array().buffer);
if (!ignoreResponseBody) {
if (options.responseFormat === 'binary') {
content = response.body;
responseBytesBuffer = new Uint8Array();
}
else if (options.responseFormat === 'json') {
responseBytesBuffer = yield response.arrayBuffer();
const text = new TextDecoder().decode(responseBytesBuffer);
if (text) {
data = (0, json_1.jsonToSerializedData)(text);
}
content = (0, utils_1.generateByteStreamFromBuffer)(responseBytesBuffer);
}
}
fetchResponse = {
url: response.url,
status: response.status,
data,
content,
headers: Object.fromEntries(Array.from(response.headers.entries())),
};
if (interceptors.length) {
fetchResponse = interceptors.reduce((modifiedResponse, interceptor) => interceptor.afterRequest(modifiedResponse), fetchResponse);
}
}
catch (error) {
isExceptionCase = true;
numberOfRetriesOnException++;
caughtError = error instanceof Error ? error : new Error(String(error));
fetchResponse = fetchResponse !== null && fetchResponse !== void 0 ? fetchResponse : { status: 0, headers: {} };
}
const attemptForRetry = isExceptionCase
? numberOfRetriesOnException
: attemptNumber;
const shouldRetry = yield retryStrategy.shouldRetry(fetchOptions, fetchResponse, attemptForRetry);
if (shouldRetry) {
const retryTimeout = retryStrategy.retryAfter(fetchOptions, fetchResponse, attemptForRetry);
yield new Promise((resolve) => setTimeout(resolve, retryTimeout));
return this.fetch(Object.assign(Object.assign({}, options), { attemptNumber: attemptNumber + 1, numberOfRetriesOnException: numberOfRetriesOnException, fileStream: fileStreamBuffer
? (0, utils_1.generateByteStreamFromBuffer)(fileStreamBuffer)
: void 0, multipartData: multipartBuffer
? (0, utils_1.multipartBufferToStream)(multipartBuffer)
: void 0 }));
}
if (fetchResponse.status >= 300 &&
fetchResponse.status < 400 &&
fetchOptions.followRedirects !== false) {
if (!fetchResponse.headers['location']) {
throw new errors_1.BoxSdkError({
message: `Unable to follow redirect for ${fetchOptions.url}`,
});
}
const sameOrigin = new URL(fetchResponse.headers['location']).origin ===
new URL(fetchOptions.url).origin;
return this.fetch(Object.assign(Object.assign({}, options), { params: undefined, auth: sameOrigin ? fetchOptions.auth : undefined, url: fetchResponse.headers['location'] }));
}
if (fetchResponse.status >= 200 && fetchResponse.status < 400) {
return fetchResponse;
}
const [code, contextInfo, requestId, helpUrl, message, error, errorDescription,] = (0, json_1.sdIsMap)(fetchResponse.data)
? [
(0, json_1.sdToJson)(fetchResponse.data['code']),
(0, json_1.sdIsMap)(fetchResponse.data['context_info'])
? fetchResponse.data['context_info']
: undefined,
(0, json_1.sdToJson)(fetchResponse.data['request_id']),
(0, json_1.sdToJson)(fetchResponse.data['help_url']),
(0, json_1.sdToJson)(fetchResponse.data['message']),
(0, json_1.sdToJson)(fetchResponse.data['error']),
(0, json_1.sdToJson)(fetchResponse.data['error_description']),
]
: [];
if (fetchResponse.status === 0) {
throw new errors_1.BoxSdkError({
message: `Unexpected Error occurred`,
timestamp: `${Date.now()}`,
error: caughtError,
});
}
const errorMessage = [
[fetchResponse.status, code, message].filter(Boolean).join(' '),
requestId && `Request ID: ${requestId}`,
error && `${error} - ${errorDescription}`,
]
.filter(Boolean)
.join('; ');
throw new errors_1.BoxApiError({
message: errorMessage,
timestamp: `${Date.now()}`,
requestInfo: {
method: requestInit.method,
url: fetchOptions.url,
queryParams: params,
headers: (_k = requestInit.headers) !== null && _k !== void 0 ? _k : {},
body: typeof requestInit.body === 'string' ? requestInit.body : undefined,
},
responseInfo: {
statusCode: fetchResponse.status,
headers: fetchResponse.headers,
body: fetchResponse.data,
rawBody: new TextDecoder().decode(responseBytesBuffer),
code: code,
contextInfo: contextInfo,
requestId: requestId,
helpUrl: helpUrl,
},
dataSanitizer: dataSanitizer,
});
});
}
}
exports.BoxNetworkClient = BoxNetworkClient;
function constructBoxUAHeader() {
const analyticsIdentifiers = {
agent: `box-javascript-generated-sdk/${version_1.sdkVersion}`,
env: (0, utils_1.isBrowser)()
? navigator.userAgent
: `Node/${process.version.replace('v', '')}`,
};
return Object.keys(analyticsIdentifiers)
.map((k) => `${k}=${analyticsIdentifiers[k]}`)
.join('; ');
}
// Retry intervals are between 50% and 150% of the exponentially increasing base amount
const RETRY_RANDOMIZATION_FACTOR = 0.5;
/**
* Calculate the exponential backoff time with randomized jitter
* @param {int} numRetries Which retry number this one will be
* @param {int} baseInterval The base retry interval set in config
* @returns {int} The number of milliseconds after which to retry
*/
function getRetryTimeout(numRetries, baseInterval) {
var minRandomization = 1 - RETRY_RANDOMIZATION_FACTOR;
var maxRandomization = 1 + RETRY_RANDOMIZATION_FACTOR;
var randomization = Math.random() * (maxRandomization - minRandomization) + minRandomization;
var exponential = Math.pow(2, numRetries - 1);
return Math.ceil(exponential * baseInterval * randomization);
}
//# sourceMappingURL=boxNetworkClient.js.map