1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | var __importStar = (this && this.__importStar) || function (mod) {
|
6 | if (mod && mod.__esModule) return mod;
|
7 | var result = {};
|
8 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
9 | result["default"] = mod;
|
10 | return result;
|
11 | };
|
12 | Object.defineProperty(exports, "__esModule", { value: true });
|
13 | const fs_extra_1 = __importDefault(require("fs-extra"));
|
14 | const core_1 = require("@fab/core");
|
15 | const cli_1 = require("@fab/cli");
|
16 | const utils_1 = require("./utils");
|
17 | const v8_isolate_1 = __importDefault(require("./sandboxes/v8-isolate"));
|
18 | const cache_1 = require("./cache");
|
19 | const sandbox_node_vm_1 = __importDefault(require("@fab/sandbox-node-vm"));
|
20 | const url_1 = __importDefault(require("url"));
|
21 | const http_1 = __importDefault(require("http"));
|
22 | const express_1 = __importDefault(require("express"));
|
23 | const concat_stream_1 = __importDefault(require("concat-stream"));
|
24 | const cross_fetch_1 = __importStar(require("cross-fetch"));
|
25 | const file_to_sha512_1 = require("file-to-sha512");
|
26 | const stream_1 = __importDefault(require("stream"));
|
27 | const cli_2 = require("@fab/cli");
|
28 | const http_proxy_1 = __importDefault(require("http-proxy"));
|
29 |
|
30 | const readable_stream_node_to_web_1 = __importDefault(require("readable-stream-node-to-web"));
|
31 | function isRequest(fetch_res) {
|
32 | var _a;
|
33 | return (fetch_res instanceof cross_fetch_1.Request || ((_a = fetch_res.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'Request');
|
34 | }
|
35 | const log = cli_1._log(`Server`);
|
36 | async function streamResponse(fetch_res, res) {
|
37 | res.status(fetch_res.status);
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | const response_headers = fetch_res.headers.raw();
|
43 | delete response_headers['content-encoding'];
|
44 | Object.keys(response_headers).forEach((header) => {
|
45 | const values = response_headers[header];
|
46 | res.set(header, values.length === 1 ? values[0] : values);
|
47 | });
|
48 | const shouldSetChunkedTransferEncoding = !response_headers['content-length'] && !response_headers['transfer-encoding'];
|
49 | const body = fetch_res.body;
|
50 | if (body) {
|
51 | if (typeof body.getReader === 'function') {
|
52 | if (shouldSetChunkedTransferEncoding)
|
53 | res.set('transfer-encoding', 'chunked');
|
54 | const reader = body.getReader();
|
55 | let x;
|
56 | while ((x = await reader.read())) {
|
57 | const { done, value } = x;
|
58 | if (done)
|
59 | break;
|
60 | if (value) {
|
61 | if (typeof value === 'string') {
|
62 | res.write(value);
|
63 | }
|
64 | else {
|
65 | res.write(Buffer.from(value));
|
66 | }
|
67 | }
|
68 | }
|
69 | res.end();
|
70 | }
|
71 | else if (body instanceof stream_1.default) {
|
72 | if (!response_headers['transfer-encoding'])
|
73 | res.set('transfer-encoding', 'chunked');
|
74 | await new Promise((resolve, reject) => {
|
75 | body.on('data', (chunk) => res.write(chunk));
|
76 | body.on('error', reject);
|
77 | body.on('end', resolve);
|
78 | });
|
79 | res.end();
|
80 | }
|
81 | else {
|
82 | const blob = await fetch_res.arrayBuffer();
|
83 | res.send(Buffer.from(blob));
|
84 | }
|
85 | }
|
86 | else {
|
87 | res.end();
|
88 | }
|
89 | }
|
90 | function createEnhancedFetch(port) {
|
91 | return async function enchanched_fetch(url, init) {
|
92 | const request_url = typeof url === 'string' ? url : url.url;
|
93 | const fetch_url = request_url.startsWith('/')
|
94 | ?
|
95 | `http://localhost:${port}${request_url}`
|
96 | : url;
|
97 | const response = await cross_fetch_1.default(fetch_url, init);
|
98 | return Object.create(response, {
|
99 | body: {
|
100 | value: Object.create(response.body, {
|
101 | getReader: {
|
102 | get() {
|
103 | const webStream = readable_stream_node_to_web_1.default(response.body);
|
104 | return webStream.getReader.bind(webStream);
|
105 | },
|
106 | },
|
107 | }),
|
108 | },
|
109 | });
|
110 | };
|
111 | }
|
112 | class Server {
|
113 | constructor(filename, args) {
|
114 | this.filename = filename;
|
115 | console.log('HELLO!');
|
116 | this.port = parseInt(args.port);
|
117 |
|
118 | if (isNaN(this.port)) {
|
119 | throw new cli_1.InvalidConfigError(`Invalid port, expected a number, got '${args.port}'`);
|
120 | }
|
121 | this.config = args.config;
|
122 | this.env = args.env;
|
123 | this.enchanched_fetch = createEnhancedFetch(this.port);
|
124 | }
|
125 | async createRenderer(src, runtimeType) {
|
126 | const renderer = (await runtimeType) === core_1.SandboxType.v8isolate
|
127 | ? await v8_isolate_1.default(src)
|
128 | : await sandbox_node_vm_1.default(src, this.enchanched_fetch);
|
129 | const bundle_id = (await file_to_sha512_1.pathToSHA512(this.filename)).slice(0, 32);
|
130 | const cache = new cache_1.Cache();
|
131 |
|
132 | if (typeof renderer.initialize === 'function') {
|
133 | renderer.initialize({ bundle_id, cache });
|
134 | }
|
135 | return renderer;
|
136 | }
|
137 | async renderReq(renderer, req, settings_overrides) {
|
138 | var _a;
|
139 | const method = req.method;
|
140 | const headers = req.headers;
|
141 | const url = `${req.protocol}://${req.headers.host}${req.url}`;
|
142 | const fetch_req = new cross_fetch_1.Request(url, {
|
143 | method,
|
144 | headers,
|
145 | ...(method === 'POST' ? { body: req.body } : {}),
|
146 | });
|
147 | const production_settings = (_a = renderer.metadata) === null || _a === void 0 ? void 0 : _a.production_settings;
|
148 | let fetch_res;
|
149 | try {
|
150 | fetch_res = await renderer.render(
|
151 |
|
152 | fetch_req, Object.assign({}, production_settings, settings_overrides));
|
153 | }
|
154 | catch (err) {
|
155 | const msg = `An error occurred calling the render method on the FAB: \nError: \n${err}`;
|
156 | throw new Error(msg);
|
157 | }
|
158 | try {
|
159 | if (fetch_res && isRequest(fetch_res)) {
|
160 | fetch_res = await this.enchanched_fetch(fetch_res);
|
161 | }
|
162 | }
|
163 | catch (err) {
|
164 | const msg = `An error occurred proxying a request returned from the FAB: \nError:\n${err}\nRequest:\n${fetch_res}`;
|
165 | throw new Error(msg);
|
166 | }
|
167 | if (!fetch_res) {
|
168 | const msg = `Nothing was returned from the FAB renderer.`;
|
169 | throw new Error(msg);
|
170 | }
|
171 | return fetch_res;
|
172 | }
|
173 | setupExpress(renderer, settings_overrides, files) {
|
174 | const app = express_1.default();
|
175 | app.use((req, res, next) => {
|
176 | try {
|
177 | next();
|
178 | }
|
179 | catch (err) {
|
180 | log(`ERROR serving: ${req.url}`);
|
181 | log(err);
|
182 | if (!res.headersSent) {
|
183 | res.writeHead(500, `Internal Error:\n${err}`);
|
184 | }
|
185 | res.end();
|
186 | }
|
187 | });
|
188 | app.use((req, _res, next) => {
|
189 | req.pipe(concat_stream_1.default((data) => {
|
190 | req.body = data.toString();
|
191 | next();
|
192 | }));
|
193 | });
|
194 | app.use((req, _res, next) => {
|
195 | log(`🖤${req.url}🖤`);
|
196 | next();
|
197 | });
|
198 | app.get('/_assets/*', (req, res) => {
|
199 | const pathname = url_1.default.parse(req.url).pathname;
|
200 | res.setHeader('Content-Type', core_1.getContentType(pathname));
|
201 | res.setHeader('Cache-Control', 'immutable');
|
202 | res.end(files[pathname]);
|
203 | });
|
204 | app.all('*', async (req, res) => {
|
205 | const fetch_res = await this.renderReq(renderer, req, settings_overrides);
|
206 | streamResponse(fetch_res, res);
|
207 | });
|
208 | return app;
|
209 | }
|
210 | async createHandler(runtimeType) {
|
211 | log(`Reading 💛${this.filename}💛...`);
|
212 | const files = await utils_1.readFilesFromZip(this.filename);
|
213 | const src_buffer = files['/server.js'];
|
214 | if (!src_buffer) {
|
215 | throw new cli_1.FabServerError('Malformed FAB. Missing /server.js');
|
216 | }
|
217 | const src = src_buffer.toString('utf8');
|
218 | log.tick(`Done. Booting VM...`);
|
219 | const settings_overrides = await this.getSettingsOverrides();
|
220 | const renderer = await this.createRenderer(src, runtimeType);
|
221 | log.tick(`Done. Booting FAB server...`);
|
222 | return this.setupExpress(renderer, settings_overrides, files);
|
223 | }
|
224 | async serve(runtimeType, watching = false, proxyWs) {
|
225 | if (!(await fs_extra_1.default.pathExists(this.filename))) {
|
226 | throw new cli_1.FabServerError(`Could not find file '${this.filename}'`);
|
227 | }
|
228 | let app;
|
229 | let proxy;
|
230 | let server;
|
231 | const bootServer = async () => {
|
232 | app = await this.createHandler(runtimeType);
|
233 | await new Promise((resolve, _reject) => {
|
234 | if (!server) {
|
235 | server = http_1.default.createServer((req, res) => app(req, res));
|
236 | if (proxyWs) {
|
237 | if (!proxy) {
|
238 | proxy = http_proxy_1.default.createProxyServer({
|
239 | target: `ws://localhost:${proxyWs}`,
|
240 | ws: true,
|
241 | });
|
242 | }
|
243 |
|
244 | server.on('upgrade', (req, socket, head) => {
|
245 | proxy.ws(req, socket, head);
|
246 | });
|
247 | }
|
248 | server.listen(this.port, resolve);
|
249 | }
|
250 | else {
|
251 | resolve();
|
252 | }
|
253 | });
|
254 | };
|
255 | if (watching) {
|
256 | log.note(`Watching 💛${this.filename}💛 for changes...`);
|
257 | await cli_2.watcher([this.filename], bootServer, {
|
258 | awaitWriteFinish: {
|
259 | stabilityThreshold: 200,
|
260 | pollInterval: 50,
|
261 | },
|
262 | });
|
263 | }
|
264 | else {
|
265 | await bootServer();
|
266 | }
|
267 | }
|
268 | async getSettingsOverrides() {
|
269 | var _a;
|
270 | if (!this.env)
|
271 | return {};
|
272 | const config = await cli_1.JSON5Config.readFrom(this.config);
|
273 | const overrides = (_a = config.data.settings) === null || _a === void 0 ? void 0 : _a[this.env];
|
274 | if (!overrides) {
|
275 | throw new cli_1.InvalidConfigError(`No environment '${this.env}' found in ${this.config}!`);
|
276 | }
|
277 | return overrides;
|
278 | }
|
279 | }
|
280 | const createServer = (filename, args) => new Server(filename, args);
|
281 | const serverExports = { createServer };
|
282 | exports.default = serverExports;
|
283 |
|
\ | No newline at end of file |