worker.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. /**
  2. * Dedicated Web Worker entry. The Node-compatibility layer this app owns is
  3. * handed to the host assembly as the module table plus the captured request
  4. * listener; the assembly owns everything else (process global, VFS image,
  5. * Cordis tree, tunnel server).
  6. *
  7. * The assembly needs the base image and selected overlays before it can exist;
  8. * they arrive in the tunnel's opening `init` frame. This bundle reads nothing
  9. * from its own URL, so the deployment decides where every archive lives.
  10. * Messages before `init` queue here; requests during boot queue inside the
  11. * host, which attaches its handler before its first await.
  12. */
  13. // Straight to the assembly, not through the package barrel: the barrel also
  14. // publishes the pack-time transform, whose acorn dependency would then be bundled
  15. // into this worker — which never parses JavaScript.
  16. import { createWorkerHost } from './worker-host.ts'
  17. import './node/builtin_modules/implemented/buffer.ts'
  18. import { alsCausality, runAtAsyncContextRoot } from './node/builtin_modules/implemented/async_hooks.ts'
  19. import { installAsyncContextHooks } from './polyfill/async-context/async-context-hooks.ts'
  20. import { createNodeBuiltins, REPLACED_PREFIXES } from './node/builtins.ts'
  21. import { whenRequestListener } from './node/builtin_modules/implemented/http.ts'
  22. import { installTimerGlobals } from './node/globals/timers.ts'
  23. import { installProcessGlobal } from './node/globals/process.ts'
  24. import { installCryptoGlobals } from './node/globals/crypto.ts'
  25. import { isShellStartFrame } from './shell/process/protocol.ts'
  26. import { runShellProcess } from './shell/process/host.ts'
  27. // Before the timer globals, so the wrappers close over the patched platform.
  28. installAsyncContextHooks()
  29. installTimerGlobals()
  30. installCryptoGlobals()
  31. let host: { handleMessage(data: unknown): void } | undefined
  32. let shellRole = false
  33. const pending: unknown[] = []
  34. self.addEventListener('message', (event: MessageEvent) => {
  35. const data = event.data as Record<string, unknown> | null
  36. // Role, decided by the first frame: a worker started by the host's shell
  37. // runs one command and closes. It mounts no image and boots no tree, so the
  38. // whole assembly below never happens in it.
  39. if (host === undefined && isShellStartFrame(data)) {
  40. shellRole = true
  41. // The command's own directory and environment are the only `process` facts
  42. // a shell process needs; bundled code that reads the global (picomatch's
  43. // platform check) must not find it missing.
  44. installProcessGlobal({ cwd: data.cwd, env: data.env })
  45. runShellProcess(data, self)
  46. return
  47. }
  48. if (host === undefined && data !== null && typeof data === 'object' && data.t === 'init') {
  49. if (typeof data.image !== 'string') {
  50. throw new Error('webworker: init frame needs a string image url')
  51. }
  52. if (!Array.isArray(data.overlays) || data.overlays.some(overlay => typeof overlay !== 'string')) {
  53. throw new Error('webworker: init frame needs an array of string overlay urls')
  54. }
  55. const created = createWorkerHost({
  56. staticModules: createNodeBuiltins(),
  57. staticModulePrefixes: REPLACED_PREFIXES,
  58. requestListener: whenRequestListener,
  59. alsCausality,
  60. image: data.image,
  61. overlays: data.overlays as string[],
  62. })
  63. host = created
  64. for (const queued of pending) {
  65. runAtAsyncContextRoot(() => { created.handleMessage(queued) })
  66. }
  67. pending.length = 0
  68. created.start().catch(() => {
  69. // start() already reported the failure to the page through tunnel.fail;
  70. // nothing else can reach this rejection, so only the duplicate
  71. // unhandled-rejection noise is dropped here.
  72. })
  73. return
  74. }
  75. if (host === undefined) {
  76. // A shell-role worker's later frames (fs replies, signals) belong to
  77. // runShellProcess's own listener; parking them here would hold every
  78. // file body until the worker exits.
  79. if (shellRole) return
  80. pending.push(event.data)
  81. return
  82. }
  83. const ready = host
  84. // A tunnel request belongs to no boundary: dispatch it at the context root so
  85. // it cannot inherit whatever ran just before it on this thread.
  86. runAtAsyncContextRoot(() => { ready.handleMessage(event.data) })
  87. })