@lvce-editor/server 0.40.0 → 0.40.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ This project incorporates components from the projects listed below, that may ha
2
2
  differing from this project:
3
3
 
4
4
 
5
- 1) License Notice for static/c76328b/icons (from https://github.com/microsoft/vscode-icons)
5
+ 1) License Notice for static/38827dc/icons (from https://github.com/microsoft/vscode-icons)
6
6
  ---------------------------------------
7
7
 
8
8
  Attribution 4.0 International
@@ -402,7 +402,7 @@ public licenses.
402
402
  Creative Commons may be contacted at creativecommons.org.
403
403
 
404
404
 
405
- 2) License Notice for static/c76328b/fonts/FiraCode-VariableFont.ttf (from https://github.com/tonsky/FiraCode)
405
+ 2) License Notice for static/38827dc/fonts/FiraCode-VariableFont.ttf (from https://github.com/tonsky/FiraCode)
406
406
  ---------------------------------------
407
407
 
408
408
  Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/server",
3
- "version": "0.40.0",
3
+ "version": "0.40.2",
4
4
  "description": "Run LVCE Editor as a server.",
5
5
  "main": "index.js",
6
6
  "bin": "bin/server.js",
@@ -20,7 +20,7 @@
20
20
  "node": ">=18"
21
21
  },
22
22
  "dependencies": {
23
- "@lvce-editor/shared-process": "0.40.0",
24
- "@lvce-editor/static-server": "0.40.0"
23
+ "@lvce-editor/shared-process": "0.40.2",
24
+ "@lvce-editor/static-server": "0.40.2"
25
25
  }
26
26
  }
package/src/server.js CHANGED
@@ -1,22 +1,12 @@
1
1
  // based on https://github.com/microsoft/vscode/tree/1.64.2/src/vs/server/node/webClientServer.ts (License MIT)
2
2
 
3
- import { sharedProcessPath } from '@lvce-editor/shared-process'
4
3
  import { ChildProcess, fork } from 'node:child_process'
5
- import { createReadStream } from 'node:fs'
6
- import { readFile, readdir, stat } from 'node:fs/promises'
7
- import { IncomingMessage, ServerResponse, createServer } from 'node:http'
8
- import { dirname, extname, join, resolve } from 'node:path'
9
- import { pipeline } from 'node:stream/promises'
10
- import { fileURLToPath, pathToFileURL } from 'node:url'
4
+ import { createServer } from 'node:http'
5
+ import { dirname, join, resolve } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
11
7
 
12
8
  const __dirname = dirname(fileURLToPath(import.meta.url))
13
9
  const ROOT = resolve(__dirname, '../')
14
- const staticServerPath = fileURLToPath(import.meta.resolve('@lvce-editor/static-server'))
15
- const staticPath = join(staticServerPath, '..', '..', 'static')
16
- const STATIC = staticPath
17
- const builtinExtensionsPath = join(STATIC, 'c76328b', 'extensions')
18
-
19
- const isProduction = true
20
10
 
21
11
  const { argv, env } = process
22
12
 
@@ -24,6 +14,7 @@ const PORT = env.PORT ? parseInt(env.PORT) : 3000
24
14
 
25
15
  let argv2 = argv[2]
26
16
 
17
+ // TODO pass argv to shared process instead of using environment variables / global variables
27
18
  const argvSliced = argv.slice(2)
28
19
  for (const arg of argvSliced) {
29
20
  if (arg.startsWith('--only-extension=')) {
@@ -33,86 +24,12 @@ for (const arg of argvSliced) {
33
24
  }
34
25
  }
35
26
 
36
- process.env.BUILTIN_EXTENSIONS_PATH = builtinExtensionsPath
37
-
38
27
  if (!argv2) {
39
28
  argv2 = resolve(__dirname, '../../../playground')
40
29
  }
41
30
 
42
- const isImmutable = true
43
-
44
31
  const isPublic = argv.includes('--public')
45
32
 
46
- const addSemicolon = (line) => {
47
- return line + ';'
48
- }
49
-
50
- const ContentSecurityPolicy = {
51
- key: 'Content-Security-Policy',
52
- value: [
53
- `default-src 'none'`,
54
- `connect-src 'self'`,
55
- `font-src 'self'`,
56
- `frame-src *`,
57
- `img-src 'self' https: data: blob:`,
58
- `script-src 'self'`,
59
- `media-src 'self'`,
60
- `manifest-src 'self'`,
61
- `style-src 'self'`,
62
- ]
63
- .map(addSemicolon)
64
- .join(' '),
65
- }
66
-
67
- const CrossOriginResourcePolicy = {
68
- key: 'Cross-Origin-Resource-Policy',
69
- value: 'same-origin',
70
- }
71
-
72
- const ContentSecurityPolicyRendererWorker = {
73
- key: 'Content-Security-Policy',
74
- value: [`default-src 'none'`, `connect-src 'self'`, `script-src 'self'`, `font-src 'self'`].map(addSemicolon).join(' '),
75
- }
76
-
77
- const ContentSecurityPolicyExtensionHostWorker = {
78
- key: 'Content-Security-Policy',
79
- value: [`default-src 'none'`, `connect-src 'self'`, `script-src 'self'`, `font-src 'self'`].map(addSemicolon).join(' '),
80
- }
81
-
82
- const ContentSecurityPolicyTerminalWorker = {
83
- key: 'Content-Security-Policy',
84
- value: [`default-src 'none'`, `connect-src 'self'`, `script-src 'self'`].map(addSemicolon).join(' '),
85
- }
86
-
87
- const CrossOriginOpenerPolicy = {
88
- key: 'Cross-Origin-Opener-Policy',
89
- value: 'same-origin',
90
- }
91
-
92
- const CrossOriginEmbedderPolicy = {
93
- key: 'Cross-Origin-Embedder-Policy',
94
- value: 'require-corp',
95
- }
96
-
97
- const textMimeType = {
98
- '.html': 'text/html',
99
- '.js': 'text/javascript',
100
- '.ts': 'text/javascript',
101
- '.mjs': 'text/javascript',
102
- '.json': 'application/json',
103
- '.css': 'text/css',
104
- '.svg': 'image/svg+xml',
105
- '.avif': 'image/avif',
106
- '.woff': 'application/font-woff',
107
- '.ttf': 'font/ttf',
108
- '.png': 'image/png',
109
- '.jpe': 'image/jpg',
110
- '.ico': 'image/x-icon',
111
- '.jpeg': 'image/jpg',
112
- '.jpg': 'image/jpg',
113
- '.webp': 'image/webp',
114
- }
115
-
116
33
  /**
117
34
  * @enum {string}
118
35
  */
@@ -124,296 +41,29 @@ const ErrorCodes = {
124
41
  EADDRINUSE: 'EADDRINUSE',
125
42
  }
126
43
 
127
- /**
128
- * @enum {string}
129
- */
130
- const CachingHeaders = {
131
- Empty: '',
132
- NoCache: 'public, max-age=0, must-revalidate',
133
- OneYear: 'public, max-age=31536000, immutable',
134
- }
135
-
136
- /**
137
- * @enum {number}
138
- */
139
- const StatusCode = {
140
- NotFound: 404,
141
- ServerError: 500,
142
- Ok: 200,
143
- MultipleChoices: 300,
144
- NotModified: 304,
145
- }
146
-
147
- const getPath = (url) => {
148
- return url.split(/[?#]/)[0]
149
- }
150
-
151
- const getContentType = (filePath) => {
152
- return textMimeType[extname(filePath)] || 'text/plain'
153
- }
154
-
155
- const getPathName = (request) => {
156
- const { pathname } = new URL(request.url || '', `https://${request.headers.host}`)
157
- return pathname
158
- }
159
-
160
- const isWorkerUrl = (url) => {
161
- return url.endsWith('WorkerMain.js') || url.endsWith('WorkerMain.ts')
162
- }
163
-
164
- const isRendererWorkerUrl = (url) => {
165
- return url.endsWith('rendererWorkerMain.js') || url.endsWith('rendererWorkerMain.ts')
166
- }
167
-
168
- const isExtensionHostWorkerUrl = (url) => {
169
- return url.endsWith('extensionHostWorkerMain.js') || url.endsWith('extensionHostWorkerMain.ts')
170
- }
171
-
172
- const isTerminalWorkerUrl = (url) => {
173
- return url.endsWith('terminalWorkerMain.js') || url.endsWith('terminalWorkerMain.ts')
174
- }
175
-
176
- const getEtag = (fileStat) => {
177
- return `W/"${[fileStat.ino, fileStat.size, fileStat.mtime.getTime()].join('-')}"`
178
- }
179
-
180
- const serveStatic = (root, skip = '') =>
181
- async function serveStatic(req, res, next) {
182
- const pathName = getPathName(req)
183
- let relativePath = getPath(pathName.slice(skip.length))
184
- if (relativePath.endsWith('/')) {
185
- relativePath += 'index.html'
186
- }
187
- // TODO on linux this could be more optimized because it is already encoded correctly (no backslashes)
188
- const filePath = fileURLToPath(`file://${root}${relativePath}`)
189
- let fileStat
190
- try {
191
- fileStat = await stat(filePath)
192
- } catch {
193
- return next()
194
- }
195
- const etag = getEtag(fileStat)
196
- if (req.headers['if-none-match'] === etag) {
197
- res.writeHead(StatusCode.NotModified)
198
- return res.end()
199
- }
200
- const isHtml = relativePath.endsWith('index.html')
201
- let cachingHeader = CachingHeaders.NoCache
202
- if (isHtml) {
203
- cachingHeader = CachingHeaders.NoCache
204
- } else if (isImmutable && root === STATIC) {
205
- cachingHeader = CachingHeaders.OneYear
206
- }
207
- const contentType = getContentType(filePath)
208
- const headers = {
209
- 'Content-Type': contentType,
210
- Etag: etag,
211
- 'Cache-Control': cachingHeader,
212
- [CrossOriginResourcePolicy.key]: CrossOriginResourcePolicy.value,
213
- }
214
- if (contentType === 'text/html') {
215
- headers[ContentSecurityPolicy.key] = ContentSecurityPolicy.value
216
- // enables access for performance.measureUserAgentSpecificMemory, see https://web.dev/monitor-total-page-memory-usage/
217
- headers[CrossOriginEmbedderPolicy.key] = CrossOriginEmbedderPolicy.value
218
- headers[CrossOriginOpenerPolicy.key] = CrossOriginOpenerPolicy.value
219
- }
220
- if (isRendererWorkerUrl(filePath)) {
221
- headers[CrossOriginEmbedderPolicy.key] = CrossOriginEmbedderPolicy.value
222
- headers[ContentSecurityPolicyRendererWorker.key] = ContentSecurityPolicyRendererWorker.value
223
- } else if (isExtensionHostWorkerUrl(filePath)) {
224
- headers[CrossOriginEmbedderPolicy.key] = CrossOriginEmbedderPolicy.value
225
- headers[ContentSecurityPolicyExtensionHostWorker.key] = ContentSecurityPolicyExtensionHostWorker.value
226
- } else if (isTerminalWorkerUrl(filePath)) {
227
- headers[CrossOriginEmbedderPolicy.key] = CrossOriginEmbedderPolicy.value
228
- headers[ContentSecurityPolicyTerminalWorker.key] = ContentSecurityPolicyTerminalWorker.value
229
- } else if (isWorkerUrl(filePath)) {
230
- headers[CrossOriginEmbedderPolicy.key] = CrossOriginEmbedderPolicy.value
231
- }
232
- res.writeHead(StatusCode.Ok, headers)
233
- try {
234
- await pipeline(createReadStream(filePath), res)
235
- } catch (error) {
236
- // @ts-ignore
237
- if (error && error.code === ErrorCodes.ERR_STREAM_PREMATURE_CLOSE) {
238
- return
239
- }
240
- // @ts-ignore
241
- if (error && error.code === ErrorCodes.EISDIR) {
242
- res.writeHead(StatusCode.NotFound)
243
- res.end()
244
- return
245
- }
246
- console.info('failed to send request', error)
247
- // TODO it is unclear how to handle errors here
248
- }
249
- }
250
-
251
- const serve404 = () =>
252
- function serve404(req, res, next) {
253
- console.info(`[server] Failed to serve static file "${req.url}"`)
254
- const headers = {
255
- 'Content-Type': 'text/plain',
256
- }
257
- if (isWorkerUrl(req.url)) {
258
- headers[CrossOriginEmbedderPolicy.key] = CrossOriginEmbedderPolicy.value
259
- headers[ContentSecurityPolicyRendererWorker.key] = ContentSecurityPolicyRendererWorker.value
260
- }
261
- res.writeHead(StatusCode.NotFound, headers)
262
- return res.end('Not found')
263
- }
264
-
265
- const createApp = () => {
266
- const handlerMap = Object.create(null)
267
- const callback = (req, res) => {
268
- // TODO avoid closure
269
- req.on('error', (error) => {
270
- // @ts-ignore
271
- if (error && error.code === ErrorCodes.ECONNRESET) {
272
- return
273
- }
274
- console.info('[info: request error]', error)
275
- })
276
- // @ts-ignore
277
- const pathName = getPathName(req)
278
- const lastSlashIndex = pathName.indexOf('/', 1)
279
- const prefix = lastSlashIndex === -1 ? pathName : pathName.slice(0, lastSlashIndex)
280
- const handlers = handlerMap[prefix] || handlerMap['*']
281
- let i = 0
282
- const next = () => {
283
- const fn = i < handlers.length ? handlers[i++] : serve404()
284
- fn(req, res, next)
285
- }
286
- next()
44
+ const isStatic = (url) => {
45
+ if(url === '/'){
46
+ return true
287
47
  }
288
- callback.use = (path, ...handlers) => {
289
- handlerMap[path] = handlers
48
+ if (url.startsWith('/38827dc')) {
49
+ return true
290
50
  }
291
- return callback
292
- }
293
-
294
- const app = createApp()
295
-
296
- /**
297
- *
298
- * @param {IncomingMessage} req
299
- * @param {ServerResponse} res
300
- */
301
- const serveTests = async (req, res, next) => {
302
- // TODO figure out if shared process can
303
- // find out where the static folder is located
304
- const indexHtmlPath = join(ROOT, 'static', 'index.html')
305
- sendHandleSharedProcess(req, res.socket, 'HandleRequestTest.handleRequestTest', indexHtmlPath)
306
- }
307
-
308
- /**
309
- *
310
- * @param {IncomingMessage} req
311
- * @param {ServerResponse} res
312
- */
313
- const servePackages = async (req, res, next) => {
314
- sendHandleSharedProcess(req, res.socket, 'HandleRemoteRequest.handleRemoteRequest')
315
- }
316
-
317
- const getAbsolutePath = (extensionName) => {
318
- return join(ROOT, 'extensions', extensionName, 'extension.json')
319
- }
320
-
321
- const isLanguageBasics = (name) => {
322
- return name.startsWith('builtin.language-basics')
323
- }
324
-
325
- const readJson = async (path) => {
326
- const content = await readFile(path, 'utf8')
327
- return JSON.parse(content)
328
- }
329
-
330
- const combineLanguages = (extensions) => {
331
- const languages = []
332
- for (const extension of extensions) {
333
- if (extension.languages) {
334
- for (const language of extension.languages) {
335
- languages.push({
336
- ...language,
337
- tokenize: `/extensions/${extension.id}/${language.tokenize}`,
338
- })
339
- }
340
- }
51
+ if (url.startsWith('/favicon.ico')) {
52
+ return true
341
53
  }
342
- return languages
343
- }
344
-
345
- const getLanguagesJson = async () => {
346
- const extensionNames = await readdir(join(ROOT, 'extensions'))
347
- const languageBasics = extensionNames.filter(isLanguageBasics)
348
- const extensionPaths = languageBasics.map(getAbsolutePath)
349
- const extensions = await Promise.all(extensionPaths.map(readJson))
350
- const languages = combineLanguages(extensions)
351
- return languages
352
- }
353
-
354
- const sendFile = async (path, res) => {
355
- const type = getContentType(path)
356
- res.setHeader('Content-Type', type)
357
- try {
358
- await pipeline(createReadStream(path), res)
359
- } catch (error) {
360
- // @ts-ignore
361
- if (error && error.code === ErrorCodes.ERR_STREAM_PREMATURE_CLOSE) {
362
- return
363
- }
364
- // @ts-ignore
365
- if (error && error.code === ErrorCodes.EISDIR) {
366
- res.statusCode = StatusCode.NotFound
367
- res.end()
368
- return
369
- }
370
- console.info('failed to send request', error)
371
- res.statusCode = StatusCode.ServerError
372
- // TODO escape error html
373
- res.end(`${error}`)
54
+ if (url.startsWith('/manifest.ico')) {
55
+ return true
374
56
  }
375
- return
57
+ return false
376
58
  }
377
59
 
378
- const serveConfig = async (req, res, next) => {
379
- const pathName = getPathName(req)
380
- if (pathName === '/config/languages.json') {
381
- const languagesJson = await getLanguagesJson()
382
- res.statusCode = StatusCode.Ok
383
- res.end(JSON.stringify(languagesJson, null, 2))
384
- return
385
- }
386
- switch (pathName) {
387
- case '/config/defaultKeyBindings.json':
388
- case '/config/builtinCommands.json':
389
- case '/config/defaultSettings.json':
390
- case '/config/webExtensions.json':
391
- case '/config/fileMap.json':
392
- return sendFile(join(ROOT, 'static', pathName), res)
393
- default:
394
- break
60
+ const handleRequest = (req, res) => {
61
+ if (isStatic(req.url)) {
62
+ return sendHandleStaticServerProcess(req, res.socket, 'HandleRequest.handleRequest')
395
63
  }
396
- next()
397
- }
398
-
399
- const handleRemote = (req, res) => {
400
- sendHandleSharedProcess(req, res.socket, 'HandleRemoteRequest.handleRemoteRequest')
401
- }
402
-
403
- const serveWithStaticServer = (req, res) => {
404
- sendHandleStaticServerProcess(req, res.socket, 'HandleRequest.handleRequest')
64
+ return sendHandleSharedProcess(req, res.socket, 'HandleRequest.handleRequest')
405
65
  }
406
66
 
407
- // serve other files in shared process
408
- app.use('/remote', handleRemote)
409
- app.use('/tests', serveTests, serve404())
410
- app.use('/config', serveConfig, serve404())
411
- app.use('/packages', servePackages, serve404())
412
-
413
-
414
- // TODO deprecate this part, serve files statically or in shared process
415
- app.use('*', serveStatic(ROOT), serveStatic(STATIC), serve404())
416
-
417
67
  const state = {
418
68
  /**
419
69
  * @type {Promise<ChildProcess>|undefined}
@@ -482,6 +132,7 @@ const launchProcess = async (processPath, execArgv) => {
482
132
  * @returns {Promise<ChildProcess>}
483
133
  */
484
134
  const launchSharedProcess = async () => {
135
+ const { sharedProcessPath } = await import('@lvce-editor/shared-process')
485
136
  return launchProcess(sharedProcessPath, ['--enable-source-maps', '--ipc-type=node-forked-process', ...argvSliced])
486
137
  }
487
138
 
@@ -501,6 +152,7 @@ const getOrCreateSharedProcess = () => {
501
152
  * @returns {Promise<ChildProcess>}
502
153
  */
503
154
  const launchStaticServerProcess = async () => {
155
+ const staticServerPath = fileURLToPath(import.meta.resolve('@lvce-editor/static-server'))
504
156
  return launchProcess(staticServerPath, [])
505
157
  }
506
158
 
@@ -610,7 +262,7 @@ const handleUncaughtExceptionMonitor = (error, origin) => {
610
262
  const main = () => {
611
263
  process.on('message', handleMessageFromParent)
612
264
  process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
613
- const server = createServer(app)
265
+ const server = createServer(handleRequest)
614
266
  server.on('listening', handleAppReady)
615
267
  server.on('upgrade', handleUpgrade)
616
268
  server.on('error', handleServerError)