UNPKG

@domoinc/ryuu-proxy

Version:

a middleware that provides a proxy for local domo app development

250 lines 10.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Domo = require("ryuu-client"); const axios_1 = __importDefault(require("axios")); const axios_cookiejar_support_1 = require("axios-cookiejar-support"); const tough_cookie_1 = require("tough-cookie"); const dotenv = __importStar(require("dotenv")); const utils_1 = require("../utils"); const constants_1 = require("../constants"); class Transport { constructor({ manifest }) { this.request = (options) => this.clientPromise.then((client) => client.processRequestRaw(options)); this.manifest = manifest; this.clientPromise = this.getLastLogin(); this.proxyId = (0, utils_1.getProxyId)(manifest); this.domainPromise = this.clientPromise.then(async (client) => client.getDomoappsData({ ...this.manifest }, this.proxyId)); this.oauthTokenPromise = this.getScopedOauthTokens(); // Add error handlers to prevent unhandled promise rejections // These will be caught by the actual consumers when they use the promises this.clientPromise.catch(() => { // Silently catch - error will be handled when promise is actually used }); this.domainPromise.catch(() => { // Silently catch - error will be handled when promise is actually used }); this.oauthTokenPromise.catch(() => { // Silently catch - error will be handled when promise is actually used }); // Initialize cookie jar once and reuse it for all requests to persist auth cookies (0, axios_cookiejar_support_1.wrapper)(axios_1.default); this.cookieJar = new tough_cookie_1.CookieJar(); } getEnv(instance) { const regexp = /([-_\w]+)\.(.*)/; const int = 2; const match = instance.match(regexp); if (!match) { throw new Error(`Invalid instance format: ${instance}`); } return match[int]; } isDomoRequest(url) { if (!url) { return false; } const domoPattern = /^\/domo\/.+\/v\d/; const dataPattern = /^\/data\/v\d\/.+/; const sqlQueryPattern = /^\/sql\/v\d\/.+/; const dqlPattern = /^\/dql\/v\d\/.+/; const apiPattern = /^\/api\/.+/; return (domoPattern.test(url) || dataPattern.test(url) || sqlQueryPattern.test(url) || dqlPattern.test(url) || apiPattern.test(url)); } isMultiPartRequest(headers) { return Object.entries(headers).some(([header, value]) => header.toLowerCase() === 'content-type' && value !== undefined && value.toString().toLowerCase().includes('multipart')); } getManifest() { return this.manifest; } getDomainPromise() { return this.domainPromise; } getLastLogin() { return (0, utils_1.getMostRecentLogin)() .then(this.verifyLogin) .then((recentLogin) => { dotenv.config({ path: `${process.cwd()}/.env` }); const proxyHost = process.env.REACT_APP_PROXY_HOST ?? process.env.PROXY_HOST; const proxyPort = process.env.REACT_APP_PROXY_PORT ?? process.env.PROXY_PORT; const proxyUsername = process.env.REACT_APP_PROXY_USERNAME ?? process.env.PROXY_USERNAME; const proxyPassword = process.env.REACT_APP_PROXY_PASSWORD ?? process.env.PROXY_PASSWORD; if (proxyHost !== undefined && proxyPort !== undefined) { if (proxyUsername !== undefined && proxyPassword !== undefined) { return new Domo(recentLogin.instance, recentLogin.refreshToken, constants_1.CLIENT_ID, { host: proxyHost, port: proxyPort, auth: `${proxyUsername}:${proxyPassword}`, }, recentLogin.devToken); } return new Domo(recentLogin.instance, recentLogin.refreshToken, constants_1.CLIENT_ID, { host: proxyHost, port: proxyPort, }, recentLogin.devToken); } return new Domo(recentLogin.instance, recentLogin.refreshToken, constants_1.CLIENT_ID, {}, recentLogin.devToken); }); } getScopedOauthTokens() { if ((0, utils_1.isOauthEnabled)(this.manifest)) { return (0, utils_1.getOauthTokens)(this.proxyId, this.manifest.scopes); } return Promise.resolve(undefined); } build(req) { let options; return this.buildBasic(req) .then((basicOptions) => { options = basicOptions; options.transformResponse = []; options.responseType = 'stream'; return this.parseBody(req); }) .then((data) => ({ ...options, data, })); } buildBasic(req) { let api; let hostname; return this.domainPromise .then((domain) => { api = `${domain.url}${req.url ?? ''}`; hostname = domain.url; return this.prepareHeaders(req.headers, this.proxyId, hostname); }) .then((headers) => ({ jar: this.cookieJar, headers, url: api, responseType: 'stream', method: req.method, })); } prepareHeaders(headers, _context, host) { const hostname = host.replace('https://', ''); return this.oauthTokenPromise.then((tokens) => { const refererValue = headers.referer ?? 'https://0.0.0.0:3000'; const referer = refererValue.indexOf('?') >= 0 ? `${refererValue}` : `${refererValue}?userId=27&customer=dev&locale=en-US&platform=desktop`; const cookieHeader = this.prepareCookies(headers, tokens); const filters = this.isMultiPartRequest(headers) ? ['content-type', 'content-length', 'cookie'] : ['cookie']; const filteredHeaders = Object.keys(headers).reduce((newHeaders, key) => { if (!filters.includes(key.toLowerCase())) { return { ...newHeaders, [key]: headers[key] }; } return newHeaders; }, {}); return { ...filteredHeaders, ...cookieHeader, referer, host: hostname, }; }); } prepareCookies(headers, tokens) { const existingCookie = Object.keys(headers).reduce((newHeaders, key) => { if (key.toLowerCase() === 'cookie') { const cookieValue = headers[key]; // handle if cookie is an array if (Array.isArray(cookieValue)) { return { ...newHeaders, cookie: cookieValue.join('; ') }; } if (cookieValue !== undefined) { return { ...newHeaders, cookie: cookieValue }; } } return newHeaders; }, {}); const tokenCookie = tokens !== undefined ? { cookie: `_daatv1=${tokens.access}; _dartv1=${tokens.refresh}` } : {}; if (existingCookie.cookie !== undefined && tokenCookie.cookie !== undefined) { return { cookie: `${existingCookie.cookie}; ${tokenCookie.cookie}`, }; } if (existingCookie.cookie === undefined) { return tokenCookie; } return existingCookie; } parseBody(req) { // if body-parser was used before this middleware the "body" attribute will be set const exprReq = req; if (typeof exprReq.body !== 'undefined') { if (typeof exprReq.body === 'string') { return Promise.resolve(exprReq.body); } return Promise.resolve(JSON.stringify(exprReq.body)); } return new Promise((resolve) => { const body = []; try { req.on('data', (chunk) => body.push(chunk)); req.on('end', () => { const raw = Buffer.concat(body).toString(); resolve(raw); }); req.on('error', () => { resolve(undefined); }); } catch (e) { resolve(undefined); } }); } verifyLogin(login) { if (!login.refreshToken) { throw new Error('Not authenticated. Please login using "domo login"'); } return login; } } exports.default = Transport; //# sourceMappingURL=index.js.map