1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.Bridge = void 0;
|
4 | const http_1 = require("http");
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | process.on('unhandledRejection', err => {
|
13 | console.error('Unhandled rejection:', err);
|
14 | process.exit(1);
|
15 | });
|
16 | function normalizeNowProxyEvent(event) {
|
17 | let bodyBuffer;
|
18 | const { method, path, headers, encoding, body } = JSON.parse(event.body);
|
19 | if (body) {
|
20 | if (encoding === 'base64') {
|
21 | bodyBuffer = Buffer.from(body, encoding);
|
22 | }
|
23 | else if (encoding === undefined) {
|
24 | bodyBuffer = Buffer.from(body);
|
25 | }
|
26 | else {
|
27 | throw new Error(`Unsupported encoding: ${encoding}`);
|
28 | }
|
29 | }
|
30 | else {
|
31 | bodyBuffer = Buffer.alloc(0);
|
32 | }
|
33 | return { isApiGateway: false, method, path, headers, body: bodyBuffer };
|
34 | }
|
35 | function normalizeAPIGatewayProxyEvent(event) {
|
36 | let bodyBuffer;
|
37 | const { httpMethod: method, path, headers, body } = event;
|
38 | if (body) {
|
39 | if (event.isBase64Encoded) {
|
40 | bodyBuffer = Buffer.from(body, 'base64');
|
41 | }
|
42 | else {
|
43 | bodyBuffer = Buffer.from(body);
|
44 | }
|
45 | }
|
46 | else {
|
47 | bodyBuffer = Buffer.alloc(0);
|
48 | }
|
49 | return { isApiGateway: true, method, path, headers, body: bodyBuffer };
|
50 | }
|
51 | function normalizeEvent(event) {
|
52 | if ('Action' in event) {
|
53 | if (event.Action === 'Invoke') {
|
54 | return normalizeNowProxyEvent(event);
|
55 | }
|
56 | else {
|
57 | throw new Error(`Unexpected event.Action: ${event.Action}`);
|
58 | }
|
59 | }
|
60 | else {
|
61 | return normalizeAPIGatewayProxyEvent(event);
|
62 | }
|
63 | }
|
64 | class Bridge {
|
65 | constructor(server, shouldStoreEvents = false) {
|
66 | this.events = {};
|
67 | this.reqIdSeed = 1;
|
68 | this.shouldStoreEvents = false;
|
69 | this.server = null;
|
70 | this.shouldStoreEvents = shouldStoreEvents;
|
71 | if (server) {
|
72 | this.setServer(server);
|
73 | }
|
74 | this.launcher = this.launcher.bind(this);
|
75 |
|
76 |
|
77 | this.resolveListening = (_info) => { };
|
78 | this.listening = new Promise(resolve => {
|
79 | this.resolveListening = resolve;
|
80 | });
|
81 | }
|
82 | setServer(server) {
|
83 | this.server = server;
|
84 | }
|
85 | listen() {
|
86 | const { server, resolveListening } = this;
|
87 | if (!server) {
|
88 | throw new Error('Server has not been set!');
|
89 | }
|
90 | if (typeof server.timeout === 'number' && server.timeout > 0) {
|
91 |
|
92 |
|
93 | server.timeout = 0;
|
94 | }
|
95 | return server.listen({
|
96 | host: '127.0.0.1',
|
97 | port: 0,
|
98 | }, function listeningCallback() {
|
99 | if (!this || typeof this.address !== 'function') {
|
100 | throw new Error('Missing server.address() function on `this` in server.listen()');
|
101 | }
|
102 | const addr = this.address();
|
103 | if (!addr) {
|
104 | throw new Error('`server.address()` returned `null`');
|
105 | }
|
106 | if (typeof addr === 'string') {
|
107 | throw new Error(`Unexpected string for \`server.address()\`: ${addr}`);
|
108 | }
|
109 | resolveListening(addr);
|
110 | });
|
111 | }
|
112 | async launcher(event, context) {
|
113 | context.callbackWaitsForEmptyEventLoop = false;
|
114 | const { port } = await this.listening;
|
115 | const normalizedEvent = normalizeEvent(event);
|
116 | const { isApiGateway, method, path, headers, body } = normalizedEvent;
|
117 | if (this.shouldStoreEvents) {
|
118 | const reqId = `${this.reqIdSeed++}`;
|
119 | this.events[reqId] = normalizedEvent;
|
120 | headers['x-now-bridge-request-id'] = reqId;
|
121 | }
|
122 |
|
123 | return new Promise((resolve, reject) => {
|
124 | const opts = { hostname: '127.0.0.1', port, path, method };
|
125 | const req = http_1.request(opts, res => {
|
126 | const response = res;
|
127 | const respBodyChunks = [];
|
128 | response.on('data', chunk => respBodyChunks.push(Buffer.from(chunk)));
|
129 | response.on('error', reject);
|
130 | response.on('end', () => {
|
131 | const bodyBuffer = Buffer.concat(respBodyChunks);
|
132 | delete response.headers.connection;
|
133 | if (isApiGateway) {
|
134 | delete response.headers['content-length'];
|
135 | }
|
136 | else if (response.headers['content-length']) {
|
137 | response.headers['content-length'] = String(bodyBuffer.length);
|
138 | }
|
139 | resolve({
|
140 | statusCode: response.statusCode || 200,
|
141 | headers: response.headers,
|
142 | body: bodyBuffer.toString('base64'),
|
143 | encoding: 'base64',
|
144 | });
|
145 | });
|
146 | });
|
147 | req.on('error', error => {
|
148 | setTimeout(() => {
|
149 |
|
150 |
|
151 | reject(error);
|
152 | }, 2);
|
153 | });
|
154 | for (const [name, value] of Object.entries(headers)) {
|
155 | if (value === undefined) {
|
156 | console.error('Skipping HTTP request header %j because value is undefined', name);
|
157 | continue;
|
158 | }
|
159 | try {
|
160 | req.setHeader(name, value);
|
161 | }
|
162 | catch (err) {
|
163 | console.error('Skipping HTTP request header: %j', `${name}: ${value}`);
|
164 | console.error(err.message);
|
165 | }
|
166 | }
|
167 | if (body)
|
168 | req.write(body);
|
169 | req.end();
|
170 | });
|
171 | }
|
172 | consumeEvent(reqId) {
|
173 | const event = this.events[reqId];
|
174 | delete this.events[reqId];
|
175 | return event;
|
176 | }
|
177 | }
|
178 | exports.Bridge = Bridge;
|