{"version":3,"file":"api.cjs","sources":["../../src/api.ts"],"sourcesContent":["/**\n * @module api\n *\n * @description This is not a robust, secure web API. It is just a quick starting point.\n * This repo is not used in production as a web API (only as a library).\n *\n * See inventory of work to be done here: https://github.com/enketo/enketo-transformer/labels/web-api-only.\n *\n * PRs are very welcome!\n */\n\nimport express from 'express';\nimport { readFileSync } from 'fs';\nimport { dirname, resolve } from 'path';\nimport { request } from 'undici';\nimport { fileURLToPath } from 'url';\nimport { transform } from './transformer';\n\nconst router = express.Router();\n\nclass FetchError extends Error {\n    readonly status: number;\n\n    constructor(status: number, message: string) {\n        super(message);\n        this.status = status;\n    }\n}\n\nconst getXForm = async (req: express.Request) => {\n    const { xform } = req.query;\n\n    try {\n        if (typeof xform !== 'string') {\n            throw new FetchError(\n                400,\n                'An `xform` query parameter is required.'\n            );\n        }\n\n        if (!xform.includes('/')) {\n            const basePath = resolve(\n                dirname(fileURLToPath(import.meta.url)),\n                '../'\n            );\n            const paths = [\n                resolve(basePath, './test/forms', xform),\n                resolve(\n                    basePath,\n                    './test/external-fixtures/enketo-core',\n                    xform\n                ),\n            ];\n\n            for (const path of paths) {\n                try {\n                    return readFileSync(path, 'utf-8');\n                } catch {\n                    // Try next path...\n                }\n            }\n\n            throw new FetchError(400, `Could not find XForm: ${xform}`);\n        }\n\n        const response = await request(xform, {\n            headers: {\n                'X-OpenRosa-Version': '1.0',\n            },\n        });\n        const { statusCode } = response;\n\n        if (statusCode === 401) {\n            throw new FetchError(\n                statusCode,\n                'Forbidden. Authorization Required.'\n            );\n        }\n\n        if (statusCode < 200 || statusCode >= 300) {\n            throw new FetchError(statusCode, `Request to ${xform} failed.`);\n        }\n\n        return response.body.text();\n    } catch (error) {\n        console.error(`Error occurred when requesting ${xform}`, error);\n\n        if (error instanceof Error && !(error instanceof FetchError)) {\n            throw new FetchError(500, error.message ?? 'Unknown error.');\n        }\n\n        throw error;\n    }\n};\n\nconst getTransformedSurvey = async (req: express.Request) => {\n    const isPost = req.method === 'POST';\n    const payload = isPost ? req.body : req.query;\n    const { markdown, openclinica, theme } = payload;\n    const media = isPost ? payload.media : {};\n    const xform = req.method === 'POST' ? payload.xform : await getXForm(req);\n\n    return transform({\n        xform,\n        markdown: markdown !== 'false',\n        openclinica: openclinica === 'true',\n        media,\n        theme,\n    });\n};\n\nrouter\n    .all('/', (req, res, next) => {\n        try {\n            if (req.app.get('secure')) {\n                throw new FetchError(405, 'Not Allowed.');\n            }\n\n            const payload = req.method === 'POST' ? req.body : req.query;\n\n            if (payload.xform == null) {\n                throw new FetchError(400, 'Bad Request.');\n            }\n\n            // Allow requests from anywhere\n            res.set('Access-Control-Allow-Origin', '*');\n            next();\n        } catch (error) {\n            next(error);\n        }\n    })\n    .get('/', async (req, res, next) => {\n        try {\n            const survey = await getTransformedSurvey(req);\n\n            res.json(survey);\n        } catch (error) {\n            next(error);\n        }\n    })\n    .post('/', async (req, res, next) => {\n        try {\n            const survey = await getTransformedSurvey(req);\n\n            res.json(survey);\n        } catch (error) {\n            next(error);\n        }\n    })\n    // for development purposes, to return HTML that can be easily inspected in the developer console\n    .get('/htmlform', async (req, res, next) => {\n        try {\n            const { form } = await getTransformedSurvey(req);\n\n            res.set('Content-Type', 'text/html');\n            res.end(form);\n        } catch (error) {\n            next(error);\n        }\n    });\n\nconst errorHandler: express.ErrorRequestHandler = (\n    error: unknown,\n    _req,\n    res\n) => {\n    if (error instanceof FetchError) {\n        res.status(error.status).send(\n            `${error.message} (stack: ${error.stack})`\n        );\n    } else {\n        res.status(500).send(`Unknown error: ${error}`);\n    }\n};\n\n/** @package */\nexport const api = (app: express.Application) => {\n    app.use('/transform', router, errorHandler);\n};\n\n/**\n * Exported for backwards compatibility, prefer named imports.\n */\nexport default api;\n"],"names":["resolve","dirname","fileURLToPath","path","readFileSync","request","transform"],"mappings":"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"}