@deep-foundation/deeplinks
Version:
[](https://www.npmjs.com/package/@deep-foundation/deeplinks) [](https://gitpod.io/#https://github.com/deep-fo
532 lines • 26.6 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import atob from 'atob';
import express from 'express';
import router from './imports/router/index.js';
import generateJwtServer from './imports/router/jwt.js';
import generateGuestServer from './imports/router/guest.js';
import generatePackagerServer from './imports/router/packager.js';
import axios from 'axios';
import http from 'http';
import { createProxyMiddleware, fixRequestBody, responseInterceptor } from 'http-proxy-middleware';
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const expressPlayground = require('graphql-playground-middleware-express').default;
import moesif from 'moesif-nodejs';
import Debug from 'debug';
import waitOn from 'wait-on';
import { generateApolloClient } from '@deep-foundation/hasura/client.js';
import { DeepClient } from './imports/client.js';
import gql from 'graphql-tag';
import { containerController, DOCKER, getJwt } from './imports/router/links.js';
import _ from 'lodash';
import Cors from 'cors';
import cookieParser from 'cookie-parser';
import { serializeError } from 'serialize-error';
const DEEPLINKS_HASURA_PATH = process.env.DEEPLINKS_HASURA_PATH || 'localhost:8080';
const DEEPLINKS_HASURA_STORAGE_URL = process.env.DEEPLINKS_HASURA_STORAGE_URL || 'http://localhost:8000';
const DEEPLINKS_HASURA_SSL = process.env.DEEPLINKS_HASURA_SSL || 0;
const DEEPLINKS_HASURA_SECRET = process.env.DEEPLINKS_HASURA_SECRET || 'myadminsecretkey';
const MOESIF_TOKEN = process.env.MOESIF_TOKEN || '';
const DEEPLINKS_PUBLIC_URL = process.env.DEEPLINKS_PUBLIC_URL || '';
const PORT = process.env.PORT || 3006;
const debug = Debug('deeplinks');
const log = debug.extend('log');
const error = debug.extend('error');
export const delay = (time) => new Promise(res => setTimeout(() => res(null), time));
const makeDeepClient = (token) => {
return new DeepClient({
apolloClient: generateApolloClient({
path: `${process.env.DEEPLINKS_HASURA_PATH}/v1/graphql`,
ssl: !!+process.env.DEEPLINKS_HASURA_SSL,
token
}),
});
};
const app = express();
app.use(cookieParser());
const httpServer = http.createServer(app);
const cors = Cors({ origin: '*' });
app.use(cors);
app.get('/gql', expressPlayground({
tabs: [{
endpoint: `${DEEPLINKS_PUBLIC_URL}/gql`,
query: `query MyQuery {
links(limit: 1) {
id
}
}`,
headers: {
Authorization: 'Bearer TOKEN',
},
}],
}));
app.use('/gql', createProxyMiddleware((pathname, req) => {
return !!pathname.match(`^/gql`);
}, {
target: `http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}://${DEEPLINKS_HASURA_PATH}`,
changeOrigin: true,
ws: true,
logLevel: 'debug',
pathRewrite: {
"/gql": "/v1/graphql",
},
}));
app.get(['/file'], createProxyMiddleware((pathname, req) => {
return !!pathname.match(`^/file`);
}, {
target: DEEPLINKS_HASURA_STORAGE_URL,
changeOrigin: true,
logLevel: 'debug',
pathRewrite: (path, req) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d;
console.log('/file get proxy', 'path', path);
console.log('/file get proxy', 'req.baseUrl', req.baseUrl);
console.log('/file get proxy', 'req.originalUrl', req.originalUrl);
console.log('/file get proxy', 'req.protocol', req.protocol);
console.log('/file get proxy', 'req.hostname', req.hostname);
console.log('/file get proxy', 'req.url', req.url);
console.log('/file get proxy', 'req.query.linkId', req.query.linkId);
console.log('/file get proxy', 'req.query.token', req.query.token);
req.params;
const headers = req.headers;
console.log('/file get proxy', 'headers', headers);
const cookies = req.cookies;
console.log('/file get proxy', 'cookies', JSON.stringify(cookies, null, 2));
let token = '';
let authorizationHeader = headers['authorization'];
if (authorizationHeader) {
token = authorizationHeader.split(' ')[1];
console.log('/file get proxy', 'header token', token);
}
else {
const tokenCookie = cookies === null || cookies === void 0 ? void 0 : cookies['dc-dg-token'];
if (tokenCookie) {
token = (_a = JSON.parse(tokenCookie)) === null || _a === void 0 ? void 0 : _a.value;
console.log('/file get proxy', 'cookie token', token);
console.log('/file get proxy', 'cookie token is set as header token');
}
else {
if (req.query.token)
token = req.query.token;
}
}
if (token) {
req.headers.authorization = `Bearer ${token}`;
}
console.log('/file get proxy', 'result token', token);
const deep = makeDeepClient(token);
const linkId = req.query.linkId;
const result = yield deep.apolloClient.query({
query: gql `{
files(where: {link_id: {_eq: ${linkId}}}) {
id
}
}`
});
const fileId = (_d = (_c = (_b = result === null || result === void 0 ? void 0 : result.data) === null || _b === void 0 ? void 0 : _b.files) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.id;
console.log('/file get proxy', 'fileId', fileId);
if (fileId) {
return `/v1/files/${fileId}`;
}
else {
return `/v1/files/00000000-0000-0000-0000-000000000000`;
}
})
}));
app.post('/file', (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
var _e;
console.log('/file post proxy', 'DEEPLINKS_HASURA_STORAGE_URL', DEEPLINKS_HASURA_STORAGE_URL);
const headers = req.headers;
console.log('/file post proxy', 'headers', JSON.stringify(headers, null, 2));
const cookies = req.cookies;
console.log('/file post proxy', 'cookies', JSON.stringify(cookies, null, 2));
let userId;
let linkId = +(headers['linkId'] || headers['linkid']) || +((_e = req.query) === null || _e === void 0 ? void 0 : _e.linkId);
console.log('/file post proxy', 'req.query.linkId', req.query.linkId);
console.log('/file post proxy', 'req.query.token', req.query.token);
if (headers.authorization) {
try {
const claims = atob(`${headers['authorization'] ? headers['authorization'] : headers['Authorization']}`.split(' ')[1].split('.')[1]);
userId = +(JSON.parse(claims)['https://hasura.io/jwt/claims']['x-hasura-user-id']);
console.log('/file post proxy', 'linkId', linkId);
}
catch (e) {
const serializedError = serializeError(e);
console.log('/file post proxy', 'error: ', JSON.stringify(serializedError, null, 2));
}
}
else if (req.query.token) {
userId = +(yield deep.jwt({ token: req.query.token })).linkId;
}
if (!userId)
res.status(403).send('!user (req.query.token || headers.authorization)');
const canResult = (yield deep.can(linkId, userId, deep.idLocal('@deep-foundation/core', 'AllowUpdate'))) || (yield deep.can(null, userId, deep.idLocal('@deep-foundation/core', 'AllowAdmin')));
console.log('/file post proxy', 'can', yield deep.can(linkId, userId, deep.idLocal('@deep-foundation/core', 'AllowUpdate')), 'isAdmin', yield deep.can(null, userId, deep.idLocal('@deep-foundation/core', 'AllowAdmin')));
console.log('/file post proxy', 'userId', userId, typeof (userId));
console.log('/file post proxy', 'canResult', canResult);
if (!canResult)
return res.status(403).send(`You cant update link ##${linkId} as user ##${userId}, and user ##${userId} is not admin.`);
yield createProxyMiddleware((pathname, req) => {
return !!pathname.match(`^/file`);
}, {
target: DEEPLINKS_HASURA_STORAGE_URL,
selfHandleResponse: true,
logLevel: 'debug',
onError: (err, req, res, target) => {
console.log('/file post proxy', 'onError', err);
res.writeHead(500, {
'Content-Type': 'text/plain',
});
res.end('Something went wrong. And we are reporting a custom error message.');
},
onProxyRes: responseInterceptor((responseBuffer, proxyRes, req, res) => __awaiter(void 0, void 0, void 0, function* () {
var _f, _g, _h;
console.log('/file post proxy', 'onProxyRes');
const response = responseBuffer.toString('utf8');
console.log('/file post proxy', `RESPONSE ${response}`);
let files;
try {
files = JSON.parse(response);
console.log('/file post proxy', 'files', files);
if (!files)
return response;
const UPDATE_FILE_LINKID = gql `mutation UPDATE_FILE_LINKID($linkId: bigint, $fileid: uuid, $uploadedByLinkId: bigint) {
updateFiles(where: {id: {_eq: $fileid}}, _set: {link_id: $linkId, uploadedByLinkId: $uploadedByLinkId }){
returning {
id
link_id
uploadedByLinkId
}
}
}`;
console.log('/file post proxy', 'files[0].id', files.id);
const updated = yield client.mutate({
mutation: UPDATE_FILE_LINKID,
variables: {
fileid: files.id,
linkId: linkId,
uploadedByLinkId: userId
},
});
console.log('/file post proxy', 'linkid', linkId);
console.log('/file post proxy', 'data', (_g = (_f = updated === null || updated === void 0 ? void 0 : updated.data) === null || _f === void 0 ? void 0 : _f.updateFiles) === null || _g === void 0 ? void 0 : _g.returning);
}
catch (e) {
const serializedError = serializeError(e);
console.log('/file post proxy', 'try error: ', JSON.stringify(serializedError, null, 2));
if ((_h = files[0]) === null || _h === void 0 ? void 0 : _h.id) {
yield client.mutate({
mutation: gql `mutation DELETE_FILE($fileid: uuid) { deleteFiles(where: {id: {_eq: $fileid}}){ returning { id } } }`,
variables: {
fileid: files.id,
},
});
return JSON.stringify({ error: 'one link - one file' });
}
}
return response;
})),
changeOrigin: true,
pathRewrite: {
"/file": "/v1/files",
},
})(req, res, next);
}));
app.use(['/v1', '/v1alpha1', '/v2', '/console'], createProxyMiddleware({
target: `http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}://${DEEPLINKS_HASURA_PATH}`,
changeOrigin: true,
ws: true,
logLevel: 'debug',
}));
if (MOESIF_TOKEN) {
const moesifMiddleware = moesif({ applicationId: MOESIF_TOKEN });
app.use(moesifMiddleware);
moesifMiddleware.startCaptureOutgoing();
}
app.use(express.json());
app.use('/', router);
const start = () => __awaiter(void 0, void 0, void 0, function* () {
const jwtServer = generateJwtServer(httpServer);
const guestServer = generateGuestServer(httpServer);
const packagerServer = generatePackagerServer(httpServer);
yield jwtServer.start();
yield guestServer.start();
yield packagerServer.start();
jwtServer.applyMiddleware({ path: '/api/jwt', app });
guestServer.applyMiddleware({ path: '/api/guest', app });
packagerServer.applyMiddleware({ path: '/api/packager', app });
yield new Promise(resolve => httpServer.listen({ port: process.env.PORT }, resolve));
log(`Hello bugfixers! Listening ${process.env.PORT} port`);
try {
yield waitOn({ resources: [`http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}-get://${DEEPLINKS_HASURA_PATH}/console`] });
yield axios({
method: 'post',
url: `http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}://${DEEPLINKS_HASURA_PATH}/v1/metadata`,
headers: { 'x-hasura-admin-secret': DEEPLINKS_HASURA_SECRET, 'Content-Type': 'application/json' },
data: { type: 'reload_metadata', args: {} }
}).then(() => {
log('hasura metadata reloaded');
}, () => {
error('hasura metadata broken');
});
}
catch (e) {
const serializedError = serializeError(e);
error(JSON.stringify(serializedError, null, 2));
}
});
start();
const client = generateApolloClient({
path: `${process.env.DEEPLINKS_HASURA_PATH}/v1/graphql`,
ssl: !!+process.env.DEEPLINKS_HASURA_SSL,
secret: process.env.DEEPLINKS_HASURA_SECRET,
});
const deep = new DeepClient({
apolloClient: client,
});
const routesDebug = Debug('deeplinks').extend('eh').extend('routes');
const routesDebugLog = routesDebug.extend('log');
const routesDebugError = routesDebug.extend('error');
let currentServers = {};
let currentPorts = {};
let busy = false;
let portTypeId = 0;
const toJSON = (data) => JSON.stringify(data, Object.getOwnPropertyNames(data), 2);
let mainPort;
const handleRoutes = () => __awaiter(void 0, void 0, void 0, function* () {
var _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
if (busy)
return;
busy = true;
try {
const portTypeId = deep.idLocal('@deep-foundation/core', 'Port');
const handleRouteTypeId = deep.idLocal('@deep-foundation/core', 'HandleRoute');
const routerStringUseTypeId = deep.idLocal('@deep-foundation/core', 'RouterStringUse');
const routerListeningTypeId = deep.idLocal('@deep-foundation/core', 'RouterListening');
try {
if (!mainPort)
mainPort = yield deep.id('@deep-foundation/main-port', 'port');
}
catch (error) { }
const routesResult = yield client.query({
query: gql `
query {
ports: links(where: {
type_id: { _eq: "${portTypeId}" }
}) {
id
port: value
routerListening: in(where: {
type_id: { _eq: "${routerListeningTypeId}" }
}) {
id
router: from {
id
routerStringUse: in(where: {
type_id: { _eq: "${routerStringUseTypeId}" }
}) {
id
routeString: value
route: from {
id
handleRoute: out(where: {
type_id: { _eq: "${handleRouteTypeId}" }
}) {
id
handler: to {
id
supports: from {
id
isolation: from {
id
image: value
}
}
file: to {
id
code: value
}
}
}
}
}
}
}
}
}
`, variables: {}
});
const portsResult = routesResult.data.ports;
routesDebugLog('portsResult', JSON.stringify(portsResult, null, 2));
const ports = {};
for (const port of portsResult) {
const portValue = (_j = port === null || port === void 0 ? void 0 : port.port) === null || _j === void 0 ? void 0 : _j.value;
ports[portValue] = port;
}
routesDebugLog('ports', JSON.stringify(ports, null, 2));
const updatedOrAddedPorts = [];
for (const key in currentPorts) {
if (currentPorts.hasOwnProperty(key)) {
if (ports.hasOwnProperty(key)) {
if (!_.isEqual(currentPorts[key], ports[key])) {
currentPorts[key] = ports[key];
updatedOrAddedPorts.push(ports[key]);
}
else {
}
}
else {
if (currentServers.hasOwnProperty(key)) {
const element = currentServers[key];
element.close();
delete currentServers[key];
}
delete currentPorts[key];
}
}
}
for (const key in ports) {
if (ports.hasOwnProperty(key)) {
if (!currentPorts.hasOwnProperty(key)) {
currentPorts[key] = ports[key];
updatedOrAddedPorts.push(ports[key]);
}
}
}
routesDebugLog('updatedOrAddedPorts', JSON.stringify(updatedOrAddedPorts, null, 2));
routesDebugLog('currentPorts', JSON.stringify(currentPorts, null, 2));
const imageContainers = {};
updatedOrAddedPorts.forEach(port => {
port.routerListening.forEach(routerListening => {
var _a;
(_a = routerListening === null || routerListening === void 0 ? void 0 : routerListening.router) === null || _a === void 0 ? void 0 : _a.routerStringUse.forEach(routerStringUse => {
var _a;
(_a = routerStringUse === null || routerStringUse === void 0 ? void 0 : routerStringUse.route) === null || _a === void 0 ? void 0 : _a.handleRoute.forEach(handleRoute => {
var _a, _b, _c, _d;
imageContainers[(_d = (_c = (_b = (_a = handleRoute === null || handleRoute === void 0 ? void 0 : handleRoute.handler) === null || _a === void 0 ? void 0 : _a.supports) === null || _b === void 0 ? void 0 : _b.isolation) === null || _c === void 0 ? void 0 : _c.image) === null || _d === void 0 ? void 0 : _d.value] = {};
});
});
});
});
const imageList = Object.keys(imageContainers);
routesDebugLog('imageList', imageList);
for (const image of imageList) {
routesDebugLog(`preparing container ${image}`);
imageContainers[image] = yield containerController.newContainer({
handler: image,
forceRestart: true,
publish: +DOCKER ? false : true,
});
}
for (const port of updatedOrAddedPorts) {
const portValue = ((_k = port === null || port === void 0 ? void 0 : port.port) === null || _k === void 0 ? void 0 : _k.value) || PORT;
if (currentServers.hasOwnProperty(portValue)) {
currentServers[portValue].close();
}
if (port.routerListening.length > 0) {
routesDebugLog(`listening on port ${portValue}`);
let portServer;
if (+portValue === +PORT || (port === null || port === void 0 ? void 0 : port.id) === mainPort) {
portServer = nestedApp;
}
else {
portServer = express();
currentServers[portValue] = http.createServer({ maxHeaderSize: 10 * 1024 * 1024 * 1024 }, portServer).listen(portValue);
}
for (const routerListening of port.routerListening) {
const router = routerListening === null || routerListening === void 0 ? void 0 : routerListening.router;
for (const routerStringUse of router.routerStringUse) {
const routeString = (_l = routerStringUse === null || routerStringUse === void 0 ? void 0 : routerStringUse.routeString) === null || _l === void 0 ? void 0 : _l.value;
routesDebugLog(`route string ${routeString}`);
const route = routerStringUse === null || routerStringUse === void 0 ? void 0 : routerStringUse.route;
for (const handleRoute of route.handleRoute) {
const handler = handleRoute === null || handleRoute === void 0 ? void 0 : handleRoute.handler;
const handlerId = handler === null || handler === void 0 ? void 0 : handler.id;
routesDebugLog(`handler id ${handlerId}`);
const { token: jwt } = yield getJwt(handlerId, routesDebugLog);
routesDebugLog(`jwt ${jwt}`);
const image = (_p = (_o = (_m = handler === null || handler === void 0 ? void 0 : handler.supports) === null || _m === void 0 ? void 0 : _m.isolation) === null || _o === void 0 ? void 0 : _o.image) === null || _p === void 0 ? void 0 : _p.value;
routesDebugLog(`image`, image);
const container = imageContainers[image];
routesDebugLog(`container`, JSON.stringify(container, null, 2));
const { data: [_handler] = [] } = yield deep.select({ handler_id: handlerId }, { table: 'handlers', returning: 'dist { value }' });
const code = ((_r = (_q = _handler === null || _handler === void 0 ? void 0 : _handler.dist) === null || _q === void 0 ? void 0 : _q.value) === null || _r === void 0 ? void 0 : _r.value) || ((_t = (_s = handler === null || handler === void 0 ? void 0 : handler.file) === null || _s === void 0 ? void 0 : _s.code) === null || _t === void 0 ? void 0 : _t.value);
routesDebugLog(`code ${code}`);
routesDebugLog('container', container);
const filter = function (pathname, req) {
routesDebugLog('pathname', pathname, req.method, req.originalUrl, !!pathname.match(`^${routeString}`));
return !!pathname.match(`^${routeString}`);
};
const proxy = createProxyMiddleware(filter, {
target: `http://${container.host}:${container.port}`,
changeOrigin: true,
ws: true,
pathRewrite: {
[routeString]: "/http-call",
},
logLevel: 'debug',
onProxyReq: (proxyReq, req, res) => {
var _a;
routesDebugLog('onProxyReq', req.baseUrl);
routesDebugLog('deeplinks request');
routesDebugLog('req.method', req.method);
routesDebugLog('req.body', req.body);
proxyReq.setHeader('deep-call-options', encodeURI(JSON.stringify({
jwt,
code,
data: { deeplinksUrl: (_a = process.env) === null || _a === void 0 ? void 0 : _a.DEEPLINKS_PUBLIC_URL, routeString, path: req.path, originalUrl: req.originalUrl, baseUrl: req.baseUrl, handlerId, routeId: route.id, router: router.id },
})));
return fixRequestBody(proxyReq, req);
},
onProxyRes: (proxyRes, req, res) => {
routesDebugLog('onProxyRes', req.baseUrl);
proxyRes.on('data', function (data) {
return __awaiter(this, void 0, void 0, function* () {
try {
data = data.toString('utf-8');
routesDebugLog('data', data);
if (data.startsWith('{')) {
data = JSON.parse(data);
if (data.hasOwnProperty('rejected')) {
routesDebugLog('rejected', data.rejected);
const handlingErrorTypeId = deep.idLocal('@deep-foundation/core', 'HandlingError');
routesDebugLog('handlingErrorTypeId', handlingErrorTypeId);
}
}
}
catch (e) {
const serializedError = serializeError(e);
routesDebugError('deeplinks response error', JSON.stringify(serializedError, null, 2));
}
});
});
}
});
portServer.use(routeString, proxy);
}
}
}
}
}
}
catch (e) {
const serializedError = serializeError(e);
routesDebugLog(JSON.stringify(serializedError, null, 2));
}
busy = false;
});
let nestedApp = express.Router();
app.use(nestedApp);
const startRouteHandling = () => __awaiter(void 0, void 0, void 0, function* () {
setInterval(handleRoutes, 5000);
});
startRouteHandling();
//# sourceMappingURL=index.js.map