@pnp/cli-microsoft365
Version:
Manage Microsoft 365 and SharePoint Framework projects on any platform
209 lines • 8.56 kB
JavaScript
import Axios from 'axios';
import { Stream } from 'stream';
import auth, { Auth } from './Auth.js';
import { app } from './utils/app.js';
import { formatting } from './utils/formatting.js';
import { timings } from './cli/timings.js';
import { timersUtil } from './utils/timersUtil.js';
class Request {
set debug(debug) {
// if the value to set is the same as current value return early to avoid
// instantiating interceptors multiple times. This can happen when calling
// one command from another
if (this._debug === debug) {
return;
}
this._debug = debug;
if (this._debug) {
this.req.interceptors.request.use(async (config) => {
if (this._logger) {
await this._logger.logToStderr('Request:');
const properties = ['url', 'method', 'headers', 'responseType', 'decompress'];
if (config.responseType !== 'stream') {
properties.push('data');
}
await this._logger.logToStderr(JSON.stringify(formatting.filterObject(config, properties), null, 2));
}
return config;
});
// since we're stubbing requests, response interceptor is never called in
// tests, so let's exclude it from coverage
/* c8 ignore next 26 */
this.req.interceptors.response.use(async (response) => {
if (this._logger) {
await this._logger.logToStderr('Response:');
const properties = ['status', 'statusText', 'headers'];
if (response.headers['content-type'] &&
response.headers['content-type'].indexOf('json') > -1) {
properties.push('data');
}
await this._logger.logToStderr(JSON.stringify({
url: response.config.url,
...formatting.filterObject(response, properties)
}, null, 2));
}
return response;
}, async (error) => {
if (this._logger) {
const properties = ['status', 'statusText', 'headers'];
await this._logger.logToStderr('Request error:');
await this._logger.logToStderr(JSON.stringify({
url: error.config?.url,
...formatting.filterObject(error.response, properties),
error: error.error
}, null, 2));
}
throw error;
});
}
}
get logger() {
return this._logger;
}
set logger(logger) {
this._logger = logger;
}
constructor() {
this._debug = false;
this.req = Axios.create({
headers: {
'user-agent': `NONISV|SharePointPnP|CLIMicrosoft365/${app.packageJson().version}`,
'accept-encoding': 'gzip, deflate',
'X-ClientService-ClientTag': `M365CLI:${app.packageJson().version}`
},
decompress: true,
responseType: 'text',
/* c8 ignore next */
transformResponse: [data => data],
maxBodyLength: Infinity,
maxContentLength: Infinity
});
// since we're stubbing requests, request interceptor is never called in
// tests, so let's exclude it from coverage
/* c8 ignore next 7 */
this.req.interceptors.request.use(config => {
if (config.responseType === 'json') {
config.transformResponse = Axios.defaults.transformResponse;
}
return config;
});
// since we're stubbing requests, response interceptor is never called in
// tests, so let's exclude it from coverage
/* c8 ignore next 15 */
this.req.interceptors.response.use((response) => response, (error) => {
if (error &&
error.response &&
error.response.data &&
!(error.response.data instanceof Stream)) {
// move error details from response.data to error property to make
// it compatible with our code
error.error = JSON.parse(JSON.stringify(error.response.data));
}
throw error;
});
}
post(options) {
options.method = 'POST';
return this.execute(options);
}
get(options) {
options.method = 'GET';
return this.execute(options);
}
patch(options) {
options.method = 'PATCH';
return this.execute(options);
}
put(options) {
options.method = 'PUT';
return this.execute(options);
}
delete(options) {
options.method = 'DELETE';
return this.execute(options);
}
head(options) {
options.method = 'HEAD';
return this.execute(options);
}
async execute(options) {
const start = process.hrtime.bigint();
if (!this._logger) {
throw 'Logger not set on the request object';
}
this.updateRequestForCloudType(options, auth.connection.cloudType);
this.removeDoubleSlashes(options);
try {
let accessToken = '';
if (options.headers && options.headers['x-anonymous']) {
accessToken = '';
}
else {
const url = options.headers && options.headers['x-resource'] ? options.headers['x-resource'] : options.url;
const resource = Auth.getResourceFromUrl(url);
accessToken = await auth.ensureAccessToken(resource, this._logger, this._debug);
}
if (options.headers) {
if (options.headers['x-anonymous']) {
delete options.headers['x-anonymous'];
}
if (options.headers['x-resource']) {
delete options.headers['x-resource'];
}
if (accessToken !== '') {
options.headers.authorization = `Bearer ${accessToken}`;
}
}
const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
if (proxyUrl) {
options.proxy = this.createProxyConfigFromUrl(proxyUrl);
}
const res = await this.req(options);
const end = process.hrtime.bigint();
timings.api.push(Number(end - start));
return options.responseType === 'stream' || options.fullResponse ?
res :
res.data;
}
catch (error) {
const end = process.hrtime.bigint();
timings.api.push(Number(end - start));
if (error && error.response && (error.response.status === 429 || error.response.status === 503)) {
let retryAfter = parseInt(error.response.headers['retry-after'] || '10');
if (isNaN(retryAfter)) {
retryAfter = 10;
}
if (this._debug) {
await this._logger.log(`Request throttled. Waiting ${retryAfter} sec before retrying...`);
}
await timersUtil.setTimeout(retryAfter * 1000);
return this.execute(options);
}
throw error;
}
}
updateRequestForCloudType(options, cloudType) {
const url = new URL(options.url);
const hostname = `${url.protocol}//${url.hostname}`;
const cloudUrl = Auth.getEndpointForResource(hostname, cloudType);
options.url = options.url.replace(hostname, cloudUrl);
}
removeDoubleSlashes(options) {
options.url = options.url.substring(0, 8) +
options.url.substring(8).replace('//', '/');
}
createProxyConfigFromUrl(url) {
const parsedUrl = new URL(url);
const port = parsedUrl.port || (url.toLowerCase().startsWith('https') ? 443 : 80);
let authObject = null;
if (parsedUrl.username && parsedUrl.password) {
authObject = {
username: parsedUrl.username,
password: parsedUrl.password
};
}
return { host: parsedUrl.hostname, port: Number(port), protocol: 'http', ...(authObject && { auth: authObject }) };
}
}
export default new Request();
//# sourceMappingURL=request.js.map