Skip to content

Commit

Permalink
Preparations to remove "request" module
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanBluefox committed Aug 3, 2024
1 parent a1660f7 commit f7bbd67
Show file tree
Hide file tree
Showing 5 changed files with 354 additions and 52 deletions.
186 changes: 152 additions & 34 deletions lib/request.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,157 @@
// This module monkey-patches request for the sandbox
// so unhandled errors in the callback or forgetting to
// attach the error event handler does not bring down the adapter

const _request = require('request');
// This module monkey-patches "request" with axios. It is a part of the migration to axios.
const axios = require('axios');
const URL = require('node:url').URL;

let logger = {
error: error => console.error(error),
};

function requestError(error) {
logger.error(`Request error: ${error}`);
function migrateAxiosResponse(response) {
const res = {
statusCode: response.status,
headers: response.headers,
body: response.data,
};

return res;
}

/**
* Calls a request method which accepts a callback and handles errors in the request and the callback itself
* @param {(...args: any[]) => any} method
* @param {...any} args
*/
function requestSafe(method, ...args) {
const lastArg = args[args.length - 1];
if (typeof lastArg === 'function') {
// If a callback was provided, handle errors in the callback
const otherArgs = args.slice(0, args.length - 1);
return method(...otherArgs, (...cbArgs) => {
try {
lastArg(...cbArgs);
} catch (e) {
logger.error(`Error in request callback: ${e}`);
}
}).on('error', requestError);
function migrateAxiosError(error) {
const err = {
code: error.code,
message: error.message,
stack: error.stack,
};
if (error.config?.url) {
const url = new URL(error.config.url);
err.address = url.hostname;
err.port = parseInt(url.port, 10) || (url.protocol === 'https:' ? 443 : 80);
}

if (error.response) {
err.statusCode = error.response.status;
err.headers = error.response.headers;
err.body = error.response.data;
}

return err;
}

function migrateRequestParams(url, requestParams) {
const axiosParams = {};

if (!requestParams.method) {
axiosParams.method = 'GET';
} else {
// otherwise, just pass the call through
return method(...args).on('error', requestError);
axiosParams.method = requestParams.method.toUpperCase();
}
if (typeof requestParams.url === 'string') {
axiosParams.url = requestParams.url;
}
if (typeof url === 'string') {
axiosParams.url = url;
}

if (requestParams.Headers || requestParams.headers) {
axiosParams.headers = requestParams.Headers || requestParams.headers;
}
if (requestParams.auth) {
axiosParams.headers = axiosParams.headers || {};
if (requestParams.auth.user || requestParams.auth.username) {
axiosParams.headers.Authorization = `Basic ${Buffer.from(`${requestParams.auth.user || requestParams.auth.username}:${requestParams.auth.pass || requestParams.auth.password}`).toString('base64')}`;
} else if (requestParams.auth.bearer) {
axiosParams.headers.Authorization = `Bearer ${requestParams.auth.bearer}`;
}
}
if (requestParams.method !== 'GET' && requestParams.method !== 'HEAD' && requestParams.method !== 'OPTIONS') {
if (requestParams.form) {
axiosParams.headers = axiosParams.headers || {};
axiosParams.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axiosParams.data = requestParams.form;
} else if (requestParams.json) {
axiosParams.headers = axiosParams.headers || {};
axiosParams.headers['Content-Type'] = 'application/json';
axiosParams.data = requestParams.json;
} else if (requestParams.dataType === 'json') {
axiosParams.headers = axiosParams.headers || {};
axiosParams.headers['Content-Type'] = 'application/json';
} else if (requestParams.dataType === 'form') {
axiosParams.headers = axiosParams.headers || {};
axiosParams.headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestParams.dataType === 'text') {
axiosParams.headers = axiosParams.headers || {};
axiosParams.headers['Content-Type'] = 'text/plain';
} else if (requestParams.dataType === 'xml') {
axiosParams.headers = axiosParams.headers || {};
axiosParams.headers['Content-Type'] = 'application/xml';
} else if (requestParams.formData) {
axiosParams.headers = axiosParams.headers || {};
axiosParams.headers['Content-Type'] = 'multipart/form-data';
const form = new FormData();
for (const attr in requestParams.formData) {
if (Object.prototype.hasOwnProperty.call(requestParams.formData, attr)) {
form.append(attr, requestParams.formData[attr]);
}
}
axiosParams.data = form;
// axiosParams.form = form;
} else {
axiosParams.data = requestParams.data;
}
}

if (!axiosParams.headers || axiosParams.headers['Content-Type'] !== 'multipart/form-data') {
axiosParams.transformResponse = x => x;
}

return axiosParams;
}

function migrateMethod(method) {
return function (url, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (typeof url === 'object') {
options = url;
}

options = options || {};
if (typeof url === 'string') {
options.url = url;
}
options.method = (method || 'GET').toUpperCase();

const axiosParams = migrateRequestParams(url, options);

if (typeof callback === 'function') {
axios(axiosParams)
.then(response => {
if (options.json) {
try {
response.data = JSON.parse(response.data);
} catch (e) {
logger.error(`Cannot parse answer: ${response.data}`);
}
callback(null, migrateAxiosResponse(response), response.data);
} else {
callback(null, migrateAxiosResponse(response), response.data);
}
})
.catch(error => {
logger.error(`Request error: ${error}`);
callback(migrateAxiosError(error));
});
} else {
axios(axiosParams)
.catch(error => logger.error(`Request error: ${error}`));
}
};
}

const request = migrateMethod();

// Wrap all methods that accept a callback
const methodsWithCallback = [
'get',
Expand All @@ -46,11 +163,6 @@ const methodsWithCallback = [
'delete',
'initParams',
];
// and request itself
const request = (...args) => requestSafe(_request, ...args);
for (const methodName of methodsWithCallback) {
request[methodName] = (...args) => requestSafe(_request[methodName], ...args);
}

// And copy all other properties and methods
const otherPropsAndMethods = [
Expand All @@ -60,13 +172,19 @@ const otherPropsAndMethods = [
'cookie',
'debug',
];
for (const propName of otherPropsAndMethods) {
request[propName] = _request[propName];
for (const method in otherPropsAndMethods) {
request[method] = function () {
logger.error(`Request error: method "${method}" is not implemented. Please migrate to axios`);
};
}

for (const method of methodsWithCallback) {
request[method] = migrateMethod(method);
}

request.setLogger = function (_logger) {
logger = _logger;
};

// end of monkeypatching
module.exports = request;
module.exports = request;
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,12 @@
"@iobroker/adapter-core": "^3.1.6",
"@types/node": "^20.14.12",
"@types/request": "^2.48.12",
"axios": "^1.7.2",
"axios": "^1.7.3",
"jsonata": "^2.0.5",
"jszip": "^3.10.1",
"node-inspect": "^2.0.0",
"node-schedule": "2.1.1",
"promisify-child-process": "^4.1.2",
"request": "^2.88.2",
"semver": "^7.6.3",
"suncalc2": "^1.8.1",
"typescript": "~5.5.4",
Expand All @@ -64,9 +63,10 @@
"@alcalzone/release-script-plugin-manual-review": "^3.7.0",
"@iobroker/adapter-dev": "^1.3.0",
"@iobroker/types": "^6.0.9",
"@iobroker/vis-2-widgets-react-dev": "^2.0.2",
"@iobroker/vis-2-widgets-react-dev": "^3.0.7",
"alcalzone-shared": "^4.0.8",
"chai": "^4.4.1",
"request": "^2.88.2",
"chai": "^4.5.0",
"eslint": "^8.57.0",
"gulp": "^4.0.2",
"gulp-rename": "^2.0.0",
Expand All @@ -92,6 +92,7 @@
"scripts": {
"test:declarations": "tsc -p test/lib/TS/tsconfig.json && tsc -p test/lib/JS/tsconfig.json",
"test:javascript": "node node_modules/mocha/bin/mocha --exit",
"test:request": "node node_modules/mocha/bin/mocha test/testRequest.js --exit",
"test": "npm run test:declarations && npm run test:javascript",
"translate": "translate-adapter",
"//postinstall": "node ./install/installTypings.js",
Expand Down
10 changes: 5 additions & 5 deletions src-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"@craco/craco": "^7.1.0",
"@iobroker/adapter-react-v5": "^6.1.6",
"@iobroker/json-config": "^7.0.22",
"@mui/icons-material": "^5.16.5",
"@mui/material": "^5.16.5",
"@mui/x-date-pickers": "^7.11.1",
"@mui/icons-material": "^5.16.6",
"@mui/material": "^5.16.6",
"@mui/x-date-pickers": "^7.12.0",
"@originjs/vite-plugin-federation": "^1.3.5",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^26.0.1",
Expand All @@ -30,7 +30,7 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-react": "^7.34.4",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"leaflet": "^1.9.4",
"prop-types": "^15.8.1",
Expand All @@ -53,4 +53,4 @@
"last 1 safari version"
]
}
}
}
18 changes: 9 additions & 9 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@icons/material": "^0.4.1",
"@iobroker/adapter-react-v5": "^6.1.6",
"@iobroker/type-detector": "^3.0.5",
"@mui/icons-material": "^5.16.5",
"@iobroker/type-detector": "^4.0.1",
"@mui/icons-material": "^5.16.6",
"@devbookhq/splitter": "^1.4.2",
"@mui/material": "^5.16.6",
"@mui/x-date-pickers": "^7.11.1",
"@sentry/browser": "^8.20.0",
"@mui/x-date-pickers": "^7.12.0",
"@sentry/browser": "^8.22.0",
"craco-module-federation": "^1.1.0",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
Expand All @@ -24,12 +24,12 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"lodash": "^4.17.21",
"monaco-editor": "~0.50.0",
"openai": "^4.53.2",
"openai": "^4.54.0",
"react": "^18.3.1",
"react-ace": "^12.0.0",
"react-bem-helper": "^1.4.1",
Expand All @@ -39,7 +39,7 @@
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
"react-fullscreen": "^0.1.0",
"react-i18next": "^14.1.2",
"react-i18next": "^15.0.0",
"react-icons": "^5.2.1",
"react-inlinesvg": "^4.1.3",
"react-json-view": "^1.21.3",
Expand Down Expand Up @@ -70,4 +70,4 @@
"not ie <= 11",
"not op_mini all"
]
}
}
Loading

0 comments on commit f7bbd67

Please sign in to comment.