@domoinc/ryuu-proxy
Version:
a middleware that provides a proxy for local domo app development
250 lines • 10.2 kB
JavaScript
;
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