diff --git a/CHANGELOG.md b/CHANGELOG.md
index af3055f..fbe0905 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## [2.1.7] - 2022-12-14
+
+##### Changed
+
+- Replace request with undici (Node's official fetch package), update dependencies, patch vulnerabilities (#155)
+
## [2.1.6] - 2022-10-27
##### Changed
diff --git a/README.md b/README.md
index 6429d5e..247d725 100644
--- a/README.md
+++ b/README.md
@@ -119,17 +119,17 @@ DEBUG=api,transformer,markdown,language node app.js
Releases are done each time a dependent tool needs an `enketo-transformer` change.
1. Create release PR
+1. Update `CHANGELOG.md`
+1. Update version in `package.json`
+ - Bump to major version if downstream has to make changes.
1. Check [Dependabot](https://github.com/enketo/enketo-transformer/security/dependabot) for alerts
1. Run `npm update`
- Check if `node-libxslt` has been updated because it has caused problems in the past
1. Run `npm audit`
- Run `npm audit fix --production` to apply most important fixes
-1. Run `npm ci`
+1. Run `npm i`
1. Run `npm test`
1. Run `npm run build-docs`
-1. Update `CHANGELOG.md`
-1. Update version in `package.json`
- - Bump to major version if downstream has to make changes.
1. Merge PR with all changes
1. Create GitHub release
1. Tag and publish the release
diff --git a/docs/api.js.html b/docs/api.js.html
index 1ffc334..ce2e083 100644
--- a/docs/api.js.html
+++ b/docs/api.js.html
@@ -32,7 +32,7 @@
-
@@ -51,140 +51,143 @@
api.js
* @module api
*
* @description This is not a robust, secure web API. It is just a quick starting point.
- * This is repo is not used in production as a web API (only as a library).
+ * This repo is not used in production as a web API (only as a library).
*
* See inventory of work to be done here: https://github.com/enketo/enketo-transformer/labels/web-api-only.
*
* PRs are very welcome!
*/
-const request = require('request');
const express = require('express');
+const { request } = require('undici');
+const transformer = require('./transformer');
const router = express.Router();
-const transformer = require('./transformer');
+
+class FetchError extends Error {
+ /**
+ * @param {number} status
+ * @param {string} message
+ */
+ constructor(status, message) {
+ super(message);
+ this.status = status;
+ }
+}
+
+/**
+ * @param {express.Request} req
+ * @returns {Promise<string>}
+ */
+const getXForm = async (req) => {
+ const url = req.query.xform;
+
+ try {
+ const response = await request(url, {
+ headers: {
+ 'X-OpenRosa-Version': '1.0',
+ },
+ });
+ const { statusCode } = response;
+
+ if (statusCode === 401) {
+ throw new FetchError(
+ statusCode,
+ 'Forbidden. Authorization Required.'
+ );
+ }
+
+ if (statusCode < 200 || statusCode >= 300) {
+ throw new FetchError(statusCode, `Request to ${url} failed.`);
+ }
+
+ return response.body.text();
+ } catch (error) {
+ console.error(`Error occurred when requesting ${url}`, error);
+
+ if (error.status == null) {
+ throw new FetchError(500, error.message ?? 'Unknown error.');
+ }
+
+ throw error;
+ }
+};
+
+/**
+ * @param {import('express').Request} req
+ * @returns {Promise<import('./transformer').TransformedSurvey>}
+ */
+const getTransformedSurvey = async (req) => {
+ const isPost = req.method === 'POST';
+ const payload = isPost ? req.body : req.query;
+ const { markdown, openclinica, theme } = payload;
+ const media = isPost ? payload.media : {};
+ const xform = req.method === 'POST' ? payload.xform : await getXForm(req);
+
+ return transformer.transform({
+ xform,
+ markdown: markdown !== 'false',
+ openclinica: openclinica === 'true',
+ media,
+ theme,
+ });
+};
router
.all('/', (req, res, next) => {
- if (req.app.get('secure')) {
- res.status(405).send('Not Allowed');
- } else if (!req.query.xform) {
- res.status(400).send('Bad Request.');
- } else {
- // allow requests from anywhere
+ try {
+ if (req.app.get('secure')) {
+ throw new FetchError(405, 'Not Allowed.');
+ }
+
+ const payload = req.method === 'POST' ? req.body : req.query;
+
+ if (payload.xform == null) {
+ throw new FetchError(400, 'Bad Request.');
+ }
+
+ // Allow requests from anywhere
res.set('Access-Control-Allow-Origin', '*');
next();
+ } catch (error) {
+ next(error);
}
})
- .get('/', (req, res) => {
- _request({
- method: 'get',
- url: req.query.xform,
- })
- .then((xform) =>
- transformer.transform({
- xform,
- theme: req.query.theme,
- openclinica: req.query.openclinica === 'true',
- markdown: req.query.markdown !== 'false',
- })
- )
- .then((result) => {
- res.json(result);
- })
- .catch((error) => {
- error.status = error.status || 500;
- error.message = error.message || 'Unknown error.';
- res.status(error.status).send(
- `${error.message} (stack: ${error.stack})`
- );
- });
+ .get('/', async (req, res) => {
+ try {
+ const survey = await getTransformedSurvey(req);
+
+ res.json(survey);
+ } catch (error) {
+ res.status(error.status).send(
+ `${error.message} (stack: ${error.stack})`
+ );
+ }
})
- .post('/', (req, res) => {
- transformer
- .transform({
- xform: req.body.xform,
- theme: req.body.theme,
- media: req.body.media,
- openclinica: req.body.openclinica === 'true',
- markdown: req.body.markdown !== 'false',
- })
- .then((result) => {
- res.json(result);
- })
- .catch((error) => {
- error.status = error.status || 500;
- error.message = error.message || 'Unknown error.';
- res.status(error.status).send(
- `${error.message} (stack: ${error.stack})`
- );
- });
+ .post('/', async (req, res) => {
+ try {
+ const survey = await getTransformedSurvey(req);
+
+ res.json(survey);
+ } catch (error) {
+ res.status(error.status).send(
+ `${error.message} (stack: ${error.stack})`
+ );
+ }
})
// for development purposes, to return HTML that can be easily inspected in the developer console
- .get('/htmlform', (req, res) => {
- _request({
- method: 'get',
- url: req.query.xform,
- })
- .then((xform) =>
- transformer.transform({
- xform,
- theme: req.query.theme,
- openclinica: req.query.openclinica === 'true',
- markdown: req.query.markdown !== 'false',
- })
- )
- .then((result) => {
- res.send(result.form);
- })
- .catch((error) => {
- error.status = error.status || 500;
- error.message = error.message || 'Unknown error.';
- res.status(error.status).send(
- `${error.message} (stack: ${error.stack})`
- );
- });
- });
-
-/**
- * Sends a request to an OpenRosa server. Only for basic retrieval of
- * public forms that do not require authentication.
- *
- * @param { {url: string, method: string} } options - Request options object.
- * @return { Promise<Error|object> } a promise that resolves in request body.
- */
-function _request(options) {
- options.headers = options.headers || {};
- options.headers['X-OpenRosa-Version'] = '1.0';
-
- const method = options.method || 'get';
-
- return new Promise((resolve, reject) => {
- request[method](options, (error, response, body) => {
- if (error) {
- console.error(
- `Error occurred when requesting ${options.url}`,
- error
- );
- reject(error);
- } else if (response.statusCode === 401) {
- const error = new Error('Forbidden. Authorization Required.');
- error.status = response.statusCode;
- reject(error);
- } else if (
- response.statusCode < 200 ||
- response.statusCode >= 300
- ) {
- const error = new Error(`Request to ${options.url} failed.`);
- error.status = response.statusCode;
- reject(error);
- } else {
- // console.log( `response of request to ${options.url} has status code: `, response.statusCode );
- resolve(body);
- }
- });
+ .get('/htmlform', async (req, res) => {
+ try {
+ const { form } = await getTransformedSurvey(req);
+
+ res.set('Content-Type', 'text/html');
+ res.end(form);
+ } catch (error) {
+ res.status(error.status).send.send(
+ `${error.message} (stack: ${error.stack})`
+ );
+ }
});
-}
module.exports = (app) => {
app.use('/transform', router);
@@ -203,7 +206,7 @@
api.js
diff --git a/docs/global.html b/docs/global.html
index 716917d..aa44f16 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -449,7 +449,7 @@
Parameters:
diff --git a/docs/index.html b/docs/index.html
index cf0ec76..7f41126 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -213,7 +213,7 @@
Change Log
diff --git a/docs/language.js.html b/docs/language.js.html
index bbaf98a..1695e7a 100644
--- a/docs/language.js.html
+++ b/docs/language.js.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -168,7 +168,7 @@
language.js
diff --git a/docs/markdown.js.html b/docs/markdown.js.html
index 9c28ae5..6853c4e 100644
--- a/docs/markdown.js.html
+++ b/docs/markdown.js.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -250,7 +250,7 @@
markdown.js
diff --git a/docs/module-api-FetchError.html b/docs/module-api-FetchError.html
new file mode 100644
index 0000000..2b1f43d
--- /dev/null
+++ b/docs/module-api-FetchError.html
@@ -0,0 +1,279 @@
+
+
+
+
+
+
FetchError - Enketo Transformer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes Modules Global
+
+
+
+
+
FetchError
+
+
+
+
+
+
+
+
+
+
+
+
+ api ~
+
+ FetchError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
new FetchError(status, message)
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ status
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ message
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Extends
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/module-api.html b/docs/module-api.html
index 068be08..5e20e88 100644
--- a/docs/module-api.html
+++ b/docs/module-api.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -61,7 +61,7 @@
api
This is not a robust, secure web API. It is just a quick starting point.
-This is repo is not used in production as a web API (only as a library).
+This repo is not used in production as a web API (only as a library).
See inventory of work to be done here: https://github.com/enketo/enketo-transformer/labels/web-api-only.
PRs are very welcome!
@@ -153,6 +153,13 @@
api
+
Classes
+
+
+ FetchError
+
+
+
@@ -170,7 +177,7 @@
Methods
-
(inner) _request(options) → {Promise.<(Error|object)>}
+
@@ -182,7 +189,7 @@
(inner) _req
Source:
@@ -221,11 +228,6 @@ (inner) _req
-
-
Sends a request to an OpenRosa server. Only for basic retrieval of
-public forms that do not require authentication.
-
-
@@ -261,13 +263,13 @@ Parameters:
- options
+ req
-Object
+module:express~Request
@@ -277,7 +279,7 @@ Parameters:
- Request options object.
+
@@ -302,11 +304,157 @@ Parameters:
Returns:
-
-
a promise that resolves in request body.
-
+
+
+ Type
+
+
+
+Promise.<module:transformer~TransformedSurvey >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ req
+
+
+
+
+
+express.Request
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
+
+
+
@@ -314,7 +462,7 @@ Returns:
-Promise.<(Error|object)>
+Promise.<string>
@@ -344,7 +492,7 @@ Returns:
diff --git a/docs/module-language.html b/docs/module-language.html
index 3b80d83..876ce49 100644
--- a/docs/module-language.html
+++ b/docs/module-language.html
@@ -32,7 +32,7 @@
- Modules Global
+ Classes Modules Global
@@ -1105,7 +1105,7 @@
Properties:
diff --git a/docs/module-markdown.html b/docs/module-markdown.html
index ff461ba..735e205 100644
--- a/docs/module-markdown.html
+++ b/docs/module-markdown.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -1906,7 +1906,7 @@
Returns:
diff --git a/docs/module-transformer.html b/docs/module-transformer.html
index 72daa22..ddd90b7 100644
--- a/docs/module-transformer.html
+++ b/docs/module-transformer.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -3379,7 +3379,7 @@
Type:
diff --git a/docs/transformer.js.html b/docs/transformer.js.html
index 308a705..04648a3 100644
--- a/docs/transformer.js.html
+++ b/docs/transformer.js.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -639,7 +639,7 @@
transformer.js
diff --git a/docs/url.js.html b/docs/url.js.html
index b6cb1dd..14f5e6c 100644
--- a/docs/url.js.html
+++ b/docs/url.js.html
@@ -32,7 +32,7 @@
-
Modules Global
+
Classes Modules Global
@@ -103,7 +103,7 @@
url.js
diff --git a/package-lock.json b/package-lock.json
index 7725981..ff2556d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "enketo-transformer",
- "version": "2.1.6",
+ "version": "2.1.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 2ae0497..d5f60ad 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "enketo-transformer",
- "version": "2.1.6",
+ "version": "2.1.7",
"description": "Library/app that transforms ODK-compliant XForms into a format that Enketo can consume",
"license": "Apache-2.0",
"main": "src/transformer.js",