UNPKG

@deep-foundation/deeplinks

Version:

[![npm](https://img.shields.io/npm/v/@deep-foundation/deeplinks.svg)](https://www.npmjs.com/package/@deep-foundation/deeplinks) [![Gitpod](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/deep-fo

532 lines 26.6 kB
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