Jelajahi Sumber

fix(webworker): register a node:https placeholder for the proxy agent factory

The VFS packer sweeps module requests statically, so dsh-http-proxy's agent
factory made the preview image unpackable: it names node:https for the SDKs
that post through Node's core HTTP modules, a path the worker never takes.
Mock it the way node:net is mocked rather than hiding the request.
Yichen Jiang 4 minggu lalu
induk
melakukan
35bc2d1d45

+ 1 - 0
packages/experimental/webworker-runtime/src/module-proxies.ts

@@ -41,6 +41,7 @@ export const MODULE_PROXIES: Record<string, string> = {
   // `process` are absent on purpose — the worker host installs that global
   // (`./globals/process.ts`).
   'node:http': './node/builtin_modules/implemented/http.ts',
+  'node:https': './node/builtin_modules/mock/https.ts',
   // Sync-stack AsyncLocalStorage semantics.
   'node:async_hooks': './node/builtin_modules/implemented/async_hooks.ts',
   // Real implementations over browser primitives.

+ 49 - 0
packages/experimental/webworker-runtime/src/node/builtin_modules/mock/https.ts

@@ -0,0 +1,49 @@
+/**
+ * `node:https` for the worker. Nothing here dials TLS: the only module that reaches for this one is
+ * `dsh-http-proxy`, whose agent factory serves SDKs that post through Node's core HTTP modules —
+ * a path the worker never takes, since its own requests go through `fetch`.
+ */
+
+/** Constructible placeholder: an agent built here would have no transport to pool. */
+export class Agent {
+  /** Teardown is accepted so disposal paths stay quiet. */
+  destroy(): void {
+    // No socket pool was ever held.
+  }
+}
+
+/**
+ * TLS requests have no carrier in a worker.
+ * @returns Never — it throws naming the unavailable member.
+ */
+export function request(): never {
+  throw new Error('web-preview: node:https.request is not available in the worker host')
+}
+
+/**
+ * Counterpart of {@link request} for the GET shorthand.
+ * @returns Never — it throws naming the unavailable member.
+ */
+export function get(): never {
+  throw new Error('web-preview: node:https.get is not available in the worker host')
+}
+
+/**
+ * TLS listening belongs to the host, not to a worker.
+ * @returns Never — it throws naming the unavailable member.
+ */
+export function createServer(): never {
+  throw new Error('web-preview: node:https.createServer is not available in the worker host')
+}
+
+/** CommonJS interop marker: the worker loader hands `default` to default imports (see ./builtins.ts). */
+export const __esModule = true
+
+/**
+ * The `node:https` declarations this module stands in for. `Agent` keeps this module's own class:
+ * Node declares it over a socket pool that a placeholder holding no connection cannot expose.
+ */
+type NodeFace = Partial<Omit<typeof import('node:https'), 'Agent'>> & Record<'Agent', unknown>
+
+/** CommonJS default export: the members `require()` hands a caller of this module. */
+export default { Agent, request, get, createServer } satisfies NodeFace

+ 16 - 0
packages/experimental/webworker-runtime/tests/node/node-stubs.spec.ts

@@ -16,6 +16,7 @@ import { notAvailableError, notImplementedFail } from '../../src/node/notImpleme
 import * as childProcess from '../../src/node/builtin_modules/implemented/child_process.ts'
 import * as dnsPromises from '../../src/node/builtin_modules/mock/dns/promises.ts'
 import * as net from '../../src/node/builtin_modules/mock/net.ts'
+import * as https from '../../src/node/builtin_modules/mock/https.ts'
 import * as sqlite from '../../src/node/builtin_modules/mock/sqlite.ts'
 import * as stream from '../../src/node/builtin_modules/implemented/stream.ts'
 import * as vm from '../../src/node/builtin_modules/mock/vm.ts'
@@ -128,6 +129,21 @@ describe('replaced external packages', () => {
   })
 })
 
+describe('node:https placeholder', () => {
+  it('constructs an Agent but refuses every transport member', () => {
+    const agent = new https.Agent()
+    // Disposal paths run against agents that never pooled a socket.
+    expect(() => { agent.destroy() }).not.toThrow()
+    expect(() => https.request()).toThrow(/https.request is not available/)
+    expect(() => https.get()).toThrow(/https.get is not available/)
+    expect(() => https.createServer()).toThrow(/https.createServer is not available/)
+  })
+
+  it('exposes the same members through its CommonJS default', () => {
+    expect(Object.keys(https.default).sort()).toEqual(['Agent', 'createServer', 'get', 'request'])
+  })
+})
+
 describe('node:net address predicates', () => {
   it('classifies IPv4, IPv6, and neither', () => {
     expect([net.isIPv4('127.0.0.1'), net.isIPv4('255.255.255.255')]).toEqual([true, true])