wrangler 0.0.0-e178d6fb → 0.0.0-e1c2f5b9
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.
- package/README.md +16 -2
- package/bin/wrangler.js +31 -11
- package/miniflare-dist/index.mjs +1340 -330
- package/package.json +40 -13
- package/src/__tests__/access.test.ts +25 -0
- package/src/__tests__/api-dev.test.ts +238 -0
- package/src/__tests__/api-devregistry.test.js +64 -0
- package/src/__tests__/api.test.ts +80 -0
- package/src/__tests__/configuration.test.ts +683 -470
- package/src/__tests__/d1.test.ts +123 -0
- package/src/__tests__/delete.test.ts +263 -0
- package/src/__tests__/deployments.test.ts +105 -0
- package/src/__tests__/dev.test.tsx +453 -70
- package/src/__tests__/dialogs.test.tsx +40 -0
- package/src/__tests__/generate.test.ts +235 -0
- package/src/__tests__/get-host-from-url.test.ts +16 -0
- package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
- package/src/__tests__/helpers/mock-cfetch.ts +92 -26
- package/src/__tests__/helpers/mock-console.ts +11 -2
- package/src/__tests__/helpers/mock-dialogs.ts +1 -1
- package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
- package/src/__tests__/helpers/mock-known-routes.ts +7 -0
- package/src/__tests__/helpers/mock-kv.ts +1 -1
- package/src/__tests__/helpers/mock-oauth-flow.ts +74 -53
- package/src/__tests__/helpers/mock-process.ts +8 -13
- package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
- package/src/__tests__/helpers/msw/handlers/deployments.ts +59 -0
- package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
- package/src/__tests__/helpers/msw/handlers/oauth.ts +31 -0
- package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
- package/src/__tests__/helpers/msw/handlers/script.ts +75 -0
- package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
- package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
- package/src/__tests__/helpers/msw/index.ts +21 -0
- package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
- package/src/__tests__/helpers/worker-scripts/hello-world-worker.js +5 -0
- package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
- package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
- package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
- package/src/__tests__/index.test.ts +103 -97
- package/src/__tests__/init.test.ts +1484 -382
- package/src/__tests__/jest.setup.ts +88 -5
- package/src/__tests__/kv.test.ts +655 -540
- package/src/__tests__/logout.test.ts +47 -0
- package/src/__tests__/metrics.test.ts +95 -45
- package/src/__tests__/middleware.scheduled.test.ts +145 -0
- package/src/__tests__/middleware.test.ts +750 -0
- package/src/__tests__/pages-deployment-tail.test.ts +884 -0
- package/src/__tests__/pages.test.ts +1301 -119
- package/src/__tests__/paths.test.ts +39 -0
- package/src/__tests__/publish.test.ts +2266 -1180
- package/src/__tests__/pubsub.test.ts +211 -88
- package/src/__tests__/queues.test.ts +522 -0
- package/src/__tests__/r2.test.ts +363 -203
- package/src/__tests__/secret.test.ts +261 -59
- package/src/__tests__/tail.test.ts +352 -151
- package/src/__tests__/test-old-node-version.js +3 -3
- package/src/__tests__/tsconfig-sanity.ts +12 -0
- package/src/__tests__/tsconfig.json +8 -0
- package/src/__tests__/tsconfig.tsbuildinfo +1 -0
- package/src/__tests__/type-generation.test.ts +233 -0
- package/src/__tests__/user.test.ts +59 -73
- package/src/__tests__/utils-collectKeyValues.test.ts +47 -0
- package/src/__tests__/validate-dev-props.test.ts +56 -0
- package/src/__tests__/whoami.test.tsx +114 -66
- package/src/__tests__/worker-namespace.test.ts +154 -155
- package/src/api/dev.ts +170 -38
- package/src/api/index.ts +1 -0
- package/src/bundle-reporter.tsx +41 -2
- package/src/bundle.ts +668 -81
- package/src/cfetch/index.ts +40 -3
- package/src/cfetch/internal.ts +144 -32
- package/src/cli.ts +4 -3
- package/src/config/config.ts +27 -3
- package/src/config/environment.ts +131 -7
- package/src/config/index.ts +128 -8
- package/src/config/validation-helpers.ts +19 -6
- package/src/config/validation.ts +411 -14
- package/src/config-cache.ts +3 -2
- package/src/create-worker-preview.ts +31 -6
- package/src/create-worker-upload-form.ts +84 -22
- package/src/d1/backups.tsx +223 -0
- package/src/d1/constants.ts +2 -0
- package/src/d1/create.tsx +63 -0
- package/src/d1/delete.tsx +56 -0
- package/src/d1/execute.tsx +352 -0
- package/src/d1/formatTimeAgo.ts +14 -0
- package/src/d1/index.ts +101 -0
- package/src/d1/list.tsx +48 -0
- package/src/d1/migrations/apply.tsx +196 -0
- package/src/d1/migrations/create.tsx +77 -0
- package/src/d1/migrations/helpers.ts +147 -0
- package/src/d1/migrations/index.tsx +3 -0
- package/src/d1/migrations/list.tsx +79 -0
- package/src/d1/migrations/options.ts +18 -0
- package/src/d1/options.ts +22 -0
- package/src/d1/types.tsx +24 -0
- package/src/d1/utils.ts +48 -0
- package/src/delete.ts +108 -0
- package/src/deployments.ts +83 -0
- package/src/deprecated/index.ts +145 -0
- package/src/dev/dev-vars.ts +11 -8
- package/src/dev/dev.tsx +220 -58
- package/src/dev/get-local-persistence-path.tsx +31 -0
- package/src/dev/local.tsx +740 -181
- package/src/dev/remote.tsx +340 -123
- package/src/dev/start-server.ts +543 -0
- package/src/dev/use-esbuild.ts +61 -7
- package/src/dev/validate-dev-props.ts +31 -0
- package/src/dev-registry.tsx +201 -0
- package/src/dev.tsx +614 -258
- package/src/dialogs.tsx +41 -8
- package/src/{worker-namespace.ts → dispatch-namespace.ts} +23 -22
- package/src/docs/index.ts +97 -0
- package/src/entry.ts +3 -2
- package/src/environment-variables/factory.ts +88 -0
- package/src/environment-variables/misc-variables.ts +30 -0
- package/src/generate/index.ts +300 -0
- package/src/git-client.ts +100 -7
- package/src/index.tsx +249 -1560
- package/src/init.ts +626 -150
- package/src/inspect.ts +137 -32
- package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
- package/src/is-interactive.ts +4 -0
- package/src/jest.d.ts +4 -0
- package/src/{kv.ts → kv/helpers.ts} +3 -3
- package/src/kv/index.ts +596 -0
- package/src/logger.ts +27 -8
- package/src/metrics/index.ts +1 -0
- package/src/metrics/metrics-config.ts +4 -4
- package/src/metrics/metrics-dispatcher.ts +1 -0
- package/src/metrics/metrics-usage-headers.ts +24 -0
- package/src/metrics/send-event.ts +13 -8
- package/src/miniflare-cli/assets.ts +172 -465
- package/src/miniflare-cli/index.ts +149 -24
- package/src/miniflare-cli/tsconfig.json +9 -0
- package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
- package/src/miniflare-cli/types.ts +11 -0
- package/src/module-collection.ts +12 -3
- package/src/pages/build.tsx +108 -42
- package/src/pages/constants.ts +11 -0
- package/src/pages/deployment-tails.tsx +284 -0
- package/src/pages/deployments.tsx +9 -31
- package/src/pages/dev.tsx +417 -108
- package/src/pages/errors.ts +66 -0
- package/src/pages/functions/buildPlugin.ts +86 -84
- package/src/pages/functions/buildWorker.ts +121 -106
- package/src/pages/functions/routes-consolidation.test.ts +250 -0
- package/src/pages/functions/routes-consolidation.ts +73 -0
- package/src/pages/functions/routes-transformation.test.ts +282 -0
- package/src/pages/functions/routes-transformation.ts +115 -0
- package/src/pages/functions/routes-validation.test.ts +403 -0
- package/src/pages/functions/routes-validation.ts +202 -0
- package/src/pages/functions/tsconfig.json +8 -0
- package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
- package/src/pages/functions.tsx +82 -0
- package/src/pages/hash.tsx +13 -0
- package/src/pages/index.tsx +73 -55
- package/src/pages/projects.tsx +9 -3
- package/src/pages/prompt-select-project.tsx +31 -0
- package/src/pages/publish.tsx +183 -60
- package/src/pages/types.ts +1 -0
- package/src/pages/upload.tsx +107 -56
- package/src/pages/utils.ts +13 -2
- package/src/paths.ts +49 -4
- package/src/proxy.ts +375 -157
- package/src/publish/index.ts +270 -0
- package/src/{publish.ts → publish/publish.ts} +431 -188
- package/src/pubsub/pubsub-commands.tsx +4 -3
- package/src/queues/cli/commands/consumer/add.ts +71 -0
- package/src/queues/cli/commands/consumer/index.ts +22 -0
- package/src/queues/cli/commands/consumer/remove.ts +38 -0
- package/src/queues/cli/commands/create.ts +25 -0
- package/src/queues/cli/commands/delete.ts +26 -0
- package/src/queues/cli/commands/index.ts +36 -0
- package/src/queues/cli/commands/list.ts +25 -0
- package/src/queues/client.ts +135 -0
- package/src/queues/utils.ts +18 -0
- package/src/r2/constants.ts +4 -0
- package/src/r2/helpers.ts +131 -0
- package/src/r2/index.ts +289 -0
- package/src/routes.ts +140 -0
- package/src/secret/index.ts +379 -0
- package/src/sites.tsx +3 -3
- package/src/tail/createTail.ts +379 -0
- package/src/tail/filters.ts +4 -6
- package/src/tail/index.ts +199 -284
- package/src/tail/printing.ts +43 -3
- package/src/tsconfig-sanity.ts +16 -0
- package/src/type-generation.ts +181 -0
- package/src/user/access.ts +68 -0
- package/src/user/auth-variables.ts +113 -0
- package/src/user/choose-account.tsx +1 -1
- package/src/user/index.ts +0 -1
- package/src/user/user.tsx +116 -49
- package/src/utils/collectKeyValues.ts +14 -0
- package/src/whoami.tsx +80 -2
- package/src/worker.ts +69 -9
- package/src/yargs-types.ts +24 -0
- package/src/zones.ts +124 -9
- package/templates/__tests__/pages-dev-util.test.ts +128 -0
- package/templates/__tests__/tsconfig-sanity.ts +12 -0
- package/templates/__tests__/tsconfig.json +8 -0
- package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
- package/templates/d1-beta-facade.js +205 -0
- package/templates/facade.d.ts +14 -0
- package/templates/first-party-worker-module-facade.ts +19 -0
- package/templates/format-dev-errors.ts +33 -0
- package/templates/init-tests/test-jest-new-worker.js +23 -0
- package/templates/init-tests/test-vitest-new-worker.js +24 -0
- package/templates/init-tests/test-vitest-new-worker.ts +25 -0
- package/templates/middleware/common.ts +62 -0
- package/templates/middleware/loader-modules.ts +82 -0
- package/templates/middleware/loader-sw.ts +233 -0
- package/templates/middleware/middleware-miniflare3-json-error.ts +20 -0
- package/templates/middleware/middleware-pretty-error.ts +40 -0
- package/templates/middleware/middleware-scheduled.ts +15 -0
- package/templates/new-worker.js +1 -1
- package/templates/pages-dev-pipeline.ts +31 -0
- package/templates/pages-dev-util.ts +52 -0
- package/templates/pages-shim.ts +4 -1
- package/templates/pages-template-plugin.ts +21 -5
- package/templates/pages-template-worker.ts +37 -9
- package/templates/{static-asset-facade.js → serve-static-assets.ts} +23 -7
- package/templates/service-bindings-module-facade.js +54 -0
- package/templates/service-bindings-sw-facade.js +42 -0
- package/templates/tsconfig-sanity.ts +11 -0
- package/templates/tsconfig.init.json +106 -0
- package/templates/tsconfig.json +5 -102
- package/templates/tsconfig.tsbuildinfo +1 -0
- package/wrangler-dist/cli.d.ts +621 -42
- package/wrangler-dist/cli.js +144441 -102606
- package/src/environment-variables.ts +0 -35
- package/src/faye-websocket.d.ts +0 -6
- package/src/generate.ts +0 -33
- package/src/miniflare-cli/enum-keys.ts +0 -17
- package/src/preview.ts +0 -31
- package/src/r2.ts +0 -50
- package/src/user/env-vars.ts +0 -46
package/src/paths.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { assert } from "console";
|
|
1
|
+
import { assert } from "node:console";
|
|
2
|
+
import { relative, basename, resolve } from "node:path";
|
|
2
3
|
|
|
3
4
|
type DiscriminatedPath<Discriminator extends string> = string & {
|
|
4
5
|
_discriminator: Discriminator;
|
|
@@ -17,10 +18,54 @@ export type UrlPath = DiscriminatedPath<"UrlPath">;
|
|
|
17
18
|
* Use this helper to convert a `string` to a `UrlPath` when it is not clear whether the string needs normalizing.
|
|
18
19
|
* Replaces all back-slashes with forward-slashes, and throws an error if the path contains a drive letter (e.g. `C:`).
|
|
19
20
|
*/
|
|
20
|
-
export function toUrlPath(
|
|
21
|
+
export function toUrlPath(filePath: string): UrlPath {
|
|
21
22
|
assert(
|
|
22
|
-
!/^[a-z]:/i.test(
|
|
23
|
+
!/^[a-z]:/i.test(filePath),
|
|
23
24
|
"Tried to convert a Windows file path with a drive to a URL path."
|
|
24
25
|
);
|
|
25
|
-
return
|
|
26
|
+
return filePath.replace(/\\/g, "/") as UrlPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get a human-readable path, relative to process.cwd(), prefixed with ./ if
|
|
31
|
+
* in a nested subdirectory, to aid with readability.
|
|
32
|
+
* Only used for logging e.g. `Loading DB at ${readableRelative(dbPath)}`:
|
|
33
|
+
*
|
|
34
|
+
* E.g. (assuming process.cwd() is /pwd)
|
|
35
|
+
*
|
|
36
|
+
* readableRelative('/pwd/wrangler.toml') => 'wrangler.toml'
|
|
37
|
+
* readableRelative('/wrangler.toml') => '../wrangler.toml'
|
|
38
|
+
* readableRelative('/pwd/subdir/wrangler.toml') => './subdir/wrangler.toml'
|
|
39
|
+
*
|
|
40
|
+
* */
|
|
41
|
+
export function readableRelative(to: string) {
|
|
42
|
+
const relativePath = relative(process.cwd(), to);
|
|
43
|
+
if (
|
|
44
|
+
// No directory nesting, return as-is
|
|
45
|
+
basename(relativePath) === relativePath ||
|
|
46
|
+
// Outside current directory
|
|
47
|
+
relativePath.startsWith(".")
|
|
48
|
+
) {
|
|
49
|
+
return relativePath;
|
|
50
|
+
} else {
|
|
51
|
+
return "./" + relativePath;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The __RELATIVE_PACKAGE_PATH__ is defined either in the esbuild config (for production)
|
|
57
|
+
* or the jest.setup.ts (for unit testing).
|
|
58
|
+
*/
|
|
59
|
+
declare const __RELATIVE_PACKAGE_PATH__: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Use this function (rather than node.js constants like `__dirname`) to specify
|
|
63
|
+
* paths that are relative to the base path of the Wrangler package.
|
|
64
|
+
*
|
|
65
|
+
* It is important to use this function because it reliably maps to the root of the package
|
|
66
|
+
* no matter whether the code has been bundled or not.
|
|
67
|
+
*/
|
|
68
|
+
export function getBasePath(): string {
|
|
69
|
+
// eslint-disable-next-line no-restricted-globals
|
|
70
|
+
return resolve(__dirname, __RELATIVE_PACKAGE_PATH__);
|
|
26
71
|
}
|
package/src/proxy.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createServer as createHttpServer } from "node:http";
|
|
2
2
|
import { connect } from "node:http2";
|
|
3
3
|
import { createServer as createHttpsServer } from "node:https";
|
|
4
|
-
import
|
|
4
|
+
import https from "node:https";
|
|
5
|
+
import { networkInterfaces } from "node:os";
|
|
5
6
|
import { createHttpTerminator } from "http-terminator";
|
|
6
7
|
import { useEffect, useRef, useState } from "react";
|
|
7
8
|
import serveStatic from "serve-static";
|
|
8
9
|
import { getHttpsOptions } from "./https-options";
|
|
9
10
|
import { logger } from "./logger";
|
|
11
|
+
import { getAccessToken } from "./user/access";
|
|
10
12
|
import type { CfPreviewToken } from "./create-worker-preview";
|
|
11
13
|
import type { HttpTerminator } from "http-terminator";
|
|
12
14
|
import type {
|
|
@@ -18,12 +20,7 @@ import type {
|
|
|
18
20
|
} from "node:http";
|
|
19
21
|
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
20
22
|
import type { Server as HttpsServer } from "node:https";
|
|
21
|
-
import type
|
|
22
|
-
|
|
23
|
-
interface IWebsocket extends ws {
|
|
24
|
-
// Pipe implements .on("message", ...)
|
|
25
|
-
pipe<T>(fn: T): IWebsocket;
|
|
26
|
-
}
|
|
23
|
+
import type { Duplex, Writable } from "node:stream";
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* `usePreviewServer` is a React hook that creates a local development
|
|
@@ -46,6 +43,27 @@ function addCfPreviewTokenHeader(
|
|
|
46
43
|
headers["cf-workers-preview-token"] = previewTokenValue;
|
|
47
44
|
}
|
|
48
45
|
|
|
46
|
+
export async function addCfAccessToken(
|
|
47
|
+
headers: IncomingHttpHeaders,
|
|
48
|
+
domain: string,
|
|
49
|
+
accessTokenRef: { current: string | undefined | null }
|
|
50
|
+
) {
|
|
51
|
+
if (accessTokenRef.current === null) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (typeof accessTokenRef.current === "string") {
|
|
55
|
+
headers[
|
|
56
|
+
"cookie"
|
|
57
|
+
] = `${headers["cookie"]};CF_Authorization=${accessTokenRef.current}`;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const token = await getAccessToken(domain);
|
|
61
|
+
accessTokenRef.current = token;
|
|
62
|
+
if (token)
|
|
63
|
+
headers[
|
|
64
|
+
"cookie"
|
|
65
|
+
] = `${headers["cookie"]};CF_Authorization=${accessTokenRef.current}`;
|
|
66
|
+
}
|
|
49
67
|
/**
|
|
50
68
|
* Rewrite references in request headers
|
|
51
69
|
* from the preview host to the local host.
|
|
@@ -69,11 +87,107 @@ function rewriteRemoteHostToLocalHostInHeaders(
|
|
|
69
87
|
}
|
|
70
88
|
}
|
|
71
89
|
|
|
90
|
+
function writeHead(
|
|
91
|
+
socket: Writable,
|
|
92
|
+
res: Pick<
|
|
93
|
+
IncomingMessage,
|
|
94
|
+
"httpVersion" | "statusCode" | "statusMessage" | "headers"
|
|
95
|
+
>
|
|
96
|
+
) {
|
|
97
|
+
socket.write(
|
|
98
|
+
`HTTP/${res.httpVersion} ${res.statusCode} ${res.statusMessage}\r\n`
|
|
99
|
+
);
|
|
100
|
+
for (const [key, values] of Object.entries(res.headers)) {
|
|
101
|
+
if (Array.isArray(values)) {
|
|
102
|
+
for (const value of values) socket.write(`${key}: ${value}\r\n`);
|
|
103
|
+
} else {
|
|
104
|
+
socket.write(`${key}: ${values}\r\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
socket.write("\r\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
72
110
|
type PreviewProxy = {
|
|
73
111
|
server: HttpServer | HttpsServer;
|
|
74
112
|
terminator: HttpTerminator;
|
|
75
113
|
};
|
|
76
114
|
|
|
115
|
+
export async function startPreviewServer({
|
|
116
|
+
previewToken,
|
|
117
|
+
assetDirectory,
|
|
118
|
+
localProtocol,
|
|
119
|
+
localPort: port,
|
|
120
|
+
ip,
|
|
121
|
+
onReady,
|
|
122
|
+
}: {
|
|
123
|
+
previewToken: CfPreviewToken;
|
|
124
|
+
assetDirectory: string | undefined;
|
|
125
|
+
localProtocol: "https" | "http";
|
|
126
|
+
localPort: number;
|
|
127
|
+
ip: string;
|
|
128
|
+
onReady: ((readyIp: string, readyPort: number) => void) | undefined;
|
|
129
|
+
}) {
|
|
130
|
+
try {
|
|
131
|
+
const abortController = new AbortController();
|
|
132
|
+
|
|
133
|
+
const server = await createProxyServer(localProtocol);
|
|
134
|
+
const proxy = {
|
|
135
|
+
server,
|
|
136
|
+
terminator: createHttpTerminator({
|
|
137
|
+
server,
|
|
138
|
+
gracefulTerminationTimeout: 0,
|
|
139
|
+
}),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// We have a token. Let's proxy requests to the preview end point.
|
|
143
|
+
const streamBufferRef = { current: [] };
|
|
144
|
+
const requestResponseBufferRef = { current: [] };
|
|
145
|
+
const accessTokenRef = { current: undefined };
|
|
146
|
+
const cleanupListeners = configureProxyServer({
|
|
147
|
+
proxy,
|
|
148
|
+
previewToken,
|
|
149
|
+
streamBufferRef,
|
|
150
|
+
requestResponseBufferRef,
|
|
151
|
+
retryServerSetup: () => {}, // no-op outside of React
|
|
152
|
+
assetDirectory,
|
|
153
|
+
localProtocol,
|
|
154
|
+
port,
|
|
155
|
+
accessTokenRef,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await waitForPortToBeAvailable(port, {
|
|
159
|
+
retryPeriod: 200,
|
|
160
|
+
timeout: 2000,
|
|
161
|
+
abortSignal: abortController.signal,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
proxy.server.on("listening", () => {
|
|
165
|
+
const address = proxy.server.address();
|
|
166
|
+
const usedPort =
|
|
167
|
+
address && typeof address === "object" ? address.port : port;
|
|
168
|
+
logger.log(`⬣ Listening at ${localProtocol}://${ip}:${usedPort}`);
|
|
169
|
+
const accessibleHosts = ip !== "0.0.0.0" ? [ip] : getAccessibleHosts();
|
|
170
|
+
for (const accessibleHost of accessibleHosts) {
|
|
171
|
+
logger.log(`- ${localProtocol}://${accessibleHost}:${usedPort}`);
|
|
172
|
+
}
|
|
173
|
+
onReady?.(ip, usedPort);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
proxy.server.listen(port, ip);
|
|
177
|
+
return {
|
|
178
|
+
stop: () => {
|
|
179
|
+
abortController.abort();
|
|
180
|
+
cleanupListeners?.forEach((cleanup) => cleanup());
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if ((err as { code: string }).code !== "ABORT_ERR") {
|
|
185
|
+
logger.error(`Failed to start server: ${err}`);
|
|
186
|
+
}
|
|
187
|
+
logger.error("Failed to create proxy server:", err);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
77
191
|
export function usePreviewServer({
|
|
78
192
|
previewToken,
|
|
79
193
|
assetDirectory,
|
|
@@ -125,6 +239,7 @@ export function usePreviewServer({
|
|
|
125
239
|
const requestResponseBufferRef = useRef<
|
|
126
240
|
{ request: IncomingMessage; response: ServerResponse }[]
|
|
127
241
|
>([]);
|
|
242
|
+
const accessTokenRef = useRef<string | undefined | null>(undefined);
|
|
128
243
|
|
|
129
244
|
/**
|
|
130
245
|
* The session doesn't last forever, and will eventually drop
|
|
@@ -138,156 +253,19 @@ export function usePreviewServer({
|
|
|
138
253
|
}
|
|
139
254
|
|
|
140
255
|
useEffect(() => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// If we don't have a token, that means either we're just starting up,
|
|
146
|
-
// or we're refreshing the token.
|
|
147
|
-
if (!previewToken) {
|
|
148
|
-
const cleanupListeners: (() => void)[] = [];
|
|
149
|
-
const bufferStream = (
|
|
150
|
-
stream: ServerHttp2Stream,
|
|
151
|
-
headers: IncomingHttpHeaders
|
|
152
|
-
) => {
|
|
153
|
-
// store the stream in a buffer so we can replay it later
|
|
154
|
-
streamBufferRef.current.push({ stream, headers });
|
|
155
|
-
};
|
|
156
|
-
proxy.server.on("stream", bufferStream);
|
|
157
|
-
cleanupListeners.push(() => proxy.server.off("stream", bufferStream));
|
|
158
|
-
|
|
159
|
-
const bufferRequestResponse = (
|
|
160
|
-
request: IncomingMessage,
|
|
161
|
-
response: ServerResponse
|
|
162
|
-
) => {
|
|
163
|
-
// store the request and response in a buffer so we can replay it later
|
|
164
|
-
requestResponseBufferRef.current.push({ request, response });
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
proxy.server.on("request", bufferRequestResponse);
|
|
168
|
-
cleanupListeners.push(() =>
|
|
169
|
-
proxy.server.off("request", bufferRequestResponse)
|
|
170
|
-
);
|
|
171
|
-
return () => {
|
|
172
|
-
cleanupListeners.forEach((cleanup) => cleanup());
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// We have a token. Let's proxy requests to the preview end point.
|
|
177
|
-
const cleanupListeners: (() => void)[] = [];
|
|
178
|
-
|
|
179
|
-
// create a ClientHttp2Session
|
|
180
|
-
const remote = connect(`https://${previewToken.host}`);
|
|
181
|
-
cleanupListeners.push(() => remote.destroy());
|
|
182
|
-
|
|
183
|
-
// As mentioned above, the session may die at any point,
|
|
184
|
-
// so we need to restart the effect.
|
|
185
|
-
remote.on("close", retryServerSetup);
|
|
186
|
-
cleanupListeners.push(() => remote.off("close", retryServerSetup));
|
|
187
|
-
|
|
188
|
-
/** HTTP/2 -> HTTP/2 */
|
|
189
|
-
const handleStream = createStreamHandler(
|
|
256
|
+
const cleanupListeners = configureProxyServer({
|
|
257
|
+
proxy,
|
|
190
258
|
previewToken,
|
|
191
|
-
|
|
259
|
+
streamBufferRef,
|
|
260
|
+
requestResponseBufferRef,
|
|
261
|
+
retryServerSetup,
|
|
262
|
+
assetDirectory,
|
|
263
|
+
localProtocol,
|
|
192
264
|
port,
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
proxy.server.on("stream", handleStream);
|
|
196
|
-
cleanupListeners.push(() => proxy.server.off("stream", handleStream));
|
|
197
|
-
|
|
198
|
-
// flush and replay buffered streams
|
|
199
|
-
streamBufferRef.current.forEach((buffer) =>
|
|
200
|
-
handleStream(buffer.stream, buffer.headers)
|
|
201
|
-
);
|
|
202
|
-
streamBufferRef.current = [];
|
|
203
|
-
|
|
204
|
-
/** HTTP/1 -> HTTP/2 */
|
|
205
|
-
const handleRequest: RequestListener = (
|
|
206
|
-
message: IncomingMessage,
|
|
207
|
-
response: ServerResponse
|
|
208
|
-
) => {
|
|
209
|
-
const { httpVersionMajor, headers, method, url } = message;
|
|
210
|
-
if (httpVersionMajor >= 2) {
|
|
211
|
-
return; // Already handled by the "stream" event.
|
|
212
|
-
}
|
|
213
|
-
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
214
|
-
headers[":method"] = method;
|
|
215
|
-
headers[":path"] = url;
|
|
216
|
-
headers[":authority"] = previewToken.host;
|
|
217
|
-
headers[":scheme"] = "https";
|
|
218
|
-
for (const name of Object.keys(headers)) {
|
|
219
|
-
if (HTTP1_HEADERS.has(name.toLowerCase())) {
|
|
220
|
-
delete headers[name];
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const request = message.pipe(remote.request(headers));
|
|
224
|
-
request.on("response", (responseHeaders) => {
|
|
225
|
-
const status = responseHeaders[":status"] ?? 500;
|
|
226
|
-
|
|
227
|
-
// log all requests to terminal
|
|
228
|
-
logger.log(new Date().toLocaleTimeString(), method, url, status);
|
|
229
|
-
|
|
230
|
-
rewriteRemoteHostToLocalHostInHeaders(
|
|
231
|
-
responseHeaders,
|
|
232
|
-
previewToken.host,
|
|
233
|
-
port,
|
|
234
|
-
localProtocol
|
|
235
|
-
);
|
|
236
|
-
for (const name of Object.keys(responseHeaders)) {
|
|
237
|
-
if (name.startsWith(":")) {
|
|
238
|
-
delete responseHeaders[name];
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
response.writeHead(status, responseHeaders);
|
|
242
|
-
request.pipe(response, { end: true });
|
|
243
|
-
});
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// If an asset path is defined, check the file system
|
|
247
|
-
// for a file first and serve if it exists.
|
|
248
|
-
const actualHandleRequest = assetDirectory
|
|
249
|
-
? createHandleAssetsRequest(assetDirectory, handleRequest)
|
|
250
|
-
: handleRequest;
|
|
251
|
-
|
|
252
|
-
proxy.server.on("request", actualHandleRequest);
|
|
253
|
-
cleanupListeners.push(() =>
|
|
254
|
-
proxy.server.off("request", actualHandleRequest)
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
// flush and replay buffered requests
|
|
258
|
-
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
259
|
-
actualHandleRequest(request, response)
|
|
260
|
-
);
|
|
261
|
-
requestResponseBufferRef.current = [];
|
|
262
|
-
|
|
263
|
-
/** HTTP/1 -> WebSocket (over HTTP/1) */
|
|
264
|
-
const handleUpgrade = (
|
|
265
|
-
message: IncomingMessage,
|
|
266
|
-
socket: WebSocket,
|
|
267
|
-
body: Buffer
|
|
268
|
-
) => {
|
|
269
|
-
const { headers, url } = message;
|
|
270
|
-
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
271
|
-
headers["host"] = previewToken.host;
|
|
272
|
-
const localWebsocket = new WebSocket(message, socket, body) as IWebsocket;
|
|
273
|
-
// TODO(soon): Custom WebSocket protocol is not working?
|
|
274
|
-
const remoteWebsocketClient = new WebSocket.Client(
|
|
275
|
-
`wss://${previewToken.host}${url}`,
|
|
276
|
-
[],
|
|
277
|
-
{ headers }
|
|
278
|
-
) as IWebsocket;
|
|
279
|
-
localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
|
|
280
|
-
// We close down websockets whenever we refresh the token.
|
|
281
|
-
cleanupListeners.push(() => {
|
|
282
|
-
localWebsocket.close();
|
|
283
|
-
remoteWebsocketClient.close();
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
|
-
proxy.server.on("upgrade", handleUpgrade);
|
|
287
|
-
cleanupListeners.push(() => proxy.server.off("upgrade", handleUpgrade));
|
|
288
|
-
|
|
265
|
+
accessTokenRef,
|
|
266
|
+
});
|
|
289
267
|
return () => {
|
|
290
|
-
cleanupListeners
|
|
268
|
+
cleanupListeners?.forEach((cleanup) => cleanup());
|
|
291
269
|
};
|
|
292
270
|
}, [
|
|
293
271
|
previewToken,
|
|
@@ -317,7 +295,15 @@ export function usePreviewServer({
|
|
|
317
295
|
})
|
|
318
296
|
.then(() => {
|
|
319
297
|
proxy.server.on("listening", () => {
|
|
320
|
-
|
|
298
|
+
const address = proxy.server.address();
|
|
299
|
+
const usedPort =
|
|
300
|
+
address && typeof address === "object" ? address.port : port;
|
|
301
|
+
logger.log(`⬣ Listening at ${localProtocol}://${ip}:${usedPort}`);
|
|
302
|
+
const accessibleHosts =
|
|
303
|
+
ip !== "0.0.0.0" ? [ip] : getAccessibleHosts();
|
|
304
|
+
for (const accessibleHost of accessibleHosts) {
|
|
305
|
+
logger.log(`- ${localProtocol}://${accessibleHost}:${usedPort}`);
|
|
306
|
+
}
|
|
321
307
|
});
|
|
322
308
|
proxy.server.listen(port, ip);
|
|
323
309
|
})
|
|
@@ -338,6 +324,220 @@ export function usePreviewServer({
|
|
|
338
324
|
}, [port, ip, proxy, localProtocol]);
|
|
339
325
|
}
|
|
340
326
|
|
|
327
|
+
function configureProxyServer({
|
|
328
|
+
proxy,
|
|
329
|
+
previewToken,
|
|
330
|
+
streamBufferRef,
|
|
331
|
+
requestResponseBufferRef,
|
|
332
|
+
retryServerSetup,
|
|
333
|
+
port,
|
|
334
|
+
localProtocol,
|
|
335
|
+
assetDirectory,
|
|
336
|
+
accessTokenRef,
|
|
337
|
+
}: {
|
|
338
|
+
proxy: PreviewProxy | undefined;
|
|
339
|
+
previewToken: CfPreviewToken | undefined;
|
|
340
|
+
// normally the type of streamBufferRef should be
|
|
341
|
+
// React.MutableRefObject<T>, but we don't want to require react
|
|
342
|
+
streamBufferRef: {
|
|
343
|
+
current: { stream: ServerHttp2Stream; headers: IncomingHttpHeaders }[];
|
|
344
|
+
};
|
|
345
|
+
// normally the type of requestResponseBufferRef should be
|
|
346
|
+
// React.MutableRefObject<T>, but we don't want to require react
|
|
347
|
+
requestResponseBufferRef: {
|
|
348
|
+
current: { request: IncomingMessage; response: ServerResponse }[];
|
|
349
|
+
};
|
|
350
|
+
retryServerSetup: () => void;
|
|
351
|
+
port: number;
|
|
352
|
+
localProtocol: "https" | "http";
|
|
353
|
+
assetDirectory: string | undefined;
|
|
354
|
+
accessTokenRef: { current: string | null | undefined };
|
|
355
|
+
}) {
|
|
356
|
+
if (proxy === undefined) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// If we don't have a token, that means either we're just starting up,
|
|
361
|
+
// or we're refreshing the token.
|
|
362
|
+
if (!previewToken) {
|
|
363
|
+
const cleanupListeners: (() => void)[] = [];
|
|
364
|
+
const bufferStream = (
|
|
365
|
+
stream: ServerHttp2Stream,
|
|
366
|
+
headers: IncomingHttpHeaders
|
|
367
|
+
) => {
|
|
368
|
+
// store the stream in a buffer so we can replay it later
|
|
369
|
+
streamBufferRef.current.push({ stream, headers });
|
|
370
|
+
};
|
|
371
|
+
proxy.server.on("stream", bufferStream);
|
|
372
|
+
cleanupListeners.push(() => proxy.server.off("stream", bufferStream));
|
|
373
|
+
|
|
374
|
+
const bufferRequestResponse = (
|
|
375
|
+
request: IncomingMessage,
|
|
376
|
+
response: ServerResponse
|
|
377
|
+
) => {
|
|
378
|
+
// store the request and response in a buffer so we can replay it later
|
|
379
|
+
requestResponseBufferRef.current.push({ request, response });
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
proxy.server.on("request", bufferRequestResponse);
|
|
383
|
+
cleanupListeners.push(() =>
|
|
384
|
+
proxy.server.off("request", bufferRequestResponse)
|
|
385
|
+
);
|
|
386
|
+
return cleanupListeners;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// We have a token. Let's proxy requests to the preview end point.
|
|
390
|
+
const cleanupListeners: (() => void)[] = [];
|
|
391
|
+
|
|
392
|
+
// create a ClientHttp2Session
|
|
393
|
+
logger.debug("PREVIEW URL:", `https://${previewToken.host}`);
|
|
394
|
+
const remote = connect(`https://${previewToken.host}`);
|
|
395
|
+
cleanupListeners.push(() => remote.destroy());
|
|
396
|
+
|
|
397
|
+
// As mentioned above, the session may die at any point,
|
|
398
|
+
// so we need to restart the effect.
|
|
399
|
+
remote.on("close", retryServerSetup);
|
|
400
|
+
cleanupListeners.push(() => remote.off("close", retryServerSetup));
|
|
401
|
+
|
|
402
|
+
/** HTTP/2 -> HTTP/2 */
|
|
403
|
+
const handleStream = createStreamHandler(
|
|
404
|
+
previewToken,
|
|
405
|
+
remote,
|
|
406
|
+
port,
|
|
407
|
+
localProtocol,
|
|
408
|
+
accessTokenRef
|
|
409
|
+
);
|
|
410
|
+
proxy.server.on("stream", handleStream);
|
|
411
|
+
cleanupListeners.push(() => proxy.server.off("stream", handleStream));
|
|
412
|
+
|
|
413
|
+
// flush and replay buffered streams
|
|
414
|
+
streamBufferRef.current.forEach(
|
|
415
|
+
(buffer: { stream: ServerHttp2Stream; headers: IncomingHttpHeaders }) =>
|
|
416
|
+
handleStream(buffer.stream, buffer.headers)
|
|
417
|
+
);
|
|
418
|
+
streamBufferRef.current = [];
|
|
419
|
+
|
|
420
|
+
/** HTTP/1 -> HTTP/2 */
|
|
421
|
+
const handleRequest: RequestListener = async (
|
|
422
|
+
message: IncomingMessage,
|
|
423
|
+
response: ServerResponse
|
|
424
|
+
) => {
|
|
425
|
+
const { httpVersionMajor, headers, method, url } = message;
|
|
426
|
+
if (httpVersionMajor >= 2) {
|
|
427
|
+
return; // Already handled by the "stream" event.
|
|
428
|
+
}
|
|
429
|
+
await addCfAccessToken(headers, previewToken.host, accessTokenRef);
|
|
430
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
431
|
+
headers[":method"] = method;
|
|
432
|
+
headers[":path"] = url;
|
|
433
|
+
headers[":authority"] = previewToken.host;
|
|
434
|
+
headers[":scheme"] = "https";
|
|
435
|
+
for (const name of Object.keys(headers)) {
|
|
436
|
+
if (HTTP1_HEADERS.has(name.toLowerCase())) {
|
|
437
|
+
delete headers[name];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const request = message.pipe(remote.request(headers));
|
|
441
|
+
logger.debug(
|
|
442
|
+
"WORKER REQUEST",
|
|
443
|
+
new Date().toLocaleTimeString(),
|
|
444
|
+
method,
|
|
445
|
+
url
|
|
446
|
+
);
|
|
447
|
+
logger.debug("HEADERS", JSON.stringify(headers, null, 2));
|
|
448
|
+
logger.debug("PREVIEW TOKEN", previewToken);
|
|
449
|
+
|
|
450
|
+
request.on("response", (responseHeaders) => {
|
|
451
|
+
const status = responseHeaders[":status"] ?? 500;
|
|
452
|
+
// log all requests to terminal
|
|
453
|
+
logger.log(new Date().toLocaleTimeString(), method, url, status);
|
|
454
|
+
|
|
455
|
+
rewriteRemoteHostToLocalHostInHeaders(
|
|
456
|
+
responseHeaders,
|
|
457
|
+
previewToken.host,
|
|
458
|
+
port,
|
|
459
|
+
localProtocol
|
|
460
|
+
);
|
|
461
|
+
for (const name of Object.keys(responseHeaders)) {
|
|
462
|
+
if (name.startsWith(":")) {
|
|
463
|
+
delete responseHeaders[name];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
response.writeHead(status, responseHeaders);
|
|
467
|
+
request.pipe(response, { end: true });
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// If an asset path is defined, check the file system
|
|
472
|
+
// for a file first and serve if it exists.
|
|
473
|
+
const actualHandleRequest = assetDirectory
|
|
474
|
+
? createHandleAssetsRequest(assetDirectory, handleRequest)
|
|
475
|
+
: handleRequest;
|
|
476
|
+
|
|
477
|
+
proxy.server.on("request", actualHandleRequest);
|
|
478
|
+
cleanupListeners.push(() => proxy.server.off("request", actualHandleRequest));
|
|
479
|
+
|
|
480
|
+
// flush and replay buffered requests
|
|
481
|
+
requestResponseBufferRef.current.forEach(
|
|
482
|
+
({
|
|
483
|
+
request,
|
|
484
|
+
response,
|
|
485
|
+
}: {
|
|
486
|
+
request: IncomingMessage;
|
|
487
|
+
response: ServerResponse;
|
|
488
|
+
}) => actualHandleRequest(request, response)
|
|
489
|
+
);
|
|
490
|
+
requestResponseBufferRef.current = [];
|
|
491
|
+
|
|
492
|
+
/** HTTP/1 -> WebSocket (over HTTP/1) */
|
|
493
|
+
const handleUpgrade = async (
|
|
494
|
+
originalMessage: IncomingMessage,
|
|
495
|
+
originalSocket: Duplex,
|
|
496
|
+
originalHead: Buffer
|
|
497
|
+
) => {
|
|
498
|
+
const { headers, method, url } = originalMessage;
|
|
499
|
+
await addCfAccessToken(headers, previewToken.host, accessTokenRef);
|
|
500
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
501
|
+
headers["host"] = previewToken.host;
|
|
502
|
+
|
|
503
|
+
if (originalHead?.byteLength) originalSocket.unshift(originalHead);
|
|
504
|
+
|
|
505
|
+
const runtimeRequest = https.request(
|
|
506
|
+
{
|
|
507
|
+
hostname: previewToken.host,
|
|
508
|
+
path: url,
|
|
509
|
+
method,
|
|
510
|
+
headers,
|
|
511
|
+
},
|
|
512
|
+
(runtimeResponse) => {
|
|
513
|
+
if (!(runtimeResponse as { upgrade?: boolean }).upgrade) {
|
|
514
|
+
writeHead(originalSocket, runtimeResponse);
|
|
515
|
+
runtimeResponse.pipe(originalSocket);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
runtimeRequest.on(
|
|
521
|
+
"upgrade",
|
|
522
|
+
(runtimeResponse, runtimeSocket, runtimeHead) => {
|
|
523
|
+
if (runtimeHead?.byteLength) runtimeSocket.unshift(runtimeHead);
|
|
524
|
+
writeHead(originalSocket, {
|
|
525
|
+
httpVersion: "1.1",
|
|
526
|
+
statusCode: 101,
|
|
527
|
+
statusMessage: "Switching Protocols",
|
|
528
|
+
headers: runtimeResponse.headers,
|
|
529
|
+
});
|
|
530
|
+
runtimeSocket.pipe(originalSocket).pipe(runtimeSocket);
|
|
531
|
+
}
|
|
532
|
+
);
|
|
533
|
+
originalMessage.pipe(runtimeRequest);
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
proxy.server.on("upgrade", handleUpgrade);
|
|
537
|
+
cleanupListeners.push(() => proxy.server.off("upgrade", handleUpgrade));
|
|
538
|
+
return cleanupListeners;
|
|
539
|
+
}
|
|
540
|
+
|
|
341
541
|
function createHandleAssetsRequest(
|
|
342
542
|
assetDirectory: string,
|
|
343
543
|
handleRequest: RequestListener
|
|
@@ -392,12 +592,14 @@ function createStreamHandler(
|
|
|
392
592
|
previewToken: CfPreviewToken,
|
|
393
593
|
remote: ClientHttp2Session,
|
|
394
594
|
localPort: number,
|
|
395
|
-
localProtocol: "https" | "http"
|
|
595
|
+
localProtocol: "https" | "http",
|
|
596
|
+
accessTokenRef: { current: string | undefined | null }
|
|
396
597
|
) {
|
|
397
|
-
return function handleStream(
|
|
598
|
+
return async function handleStream(
|
|
398
599
|
stream: ServerHttp2Stream,
|
|
399
600
|
headers: IncomingHttpHeaders
|
|
400
601
|
) {
|
|
602
|
+
await addCfAccessToken(headers, previewToken.host, accessTokenRef);
|
|
401
603
|
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
402
604
|
headers[":authority"] = previewToken.host;
|
|
403
605
|
const request = stream.pipe(remote.request(headers));
|
|
@@ -449,6 +651,11 @@ export async function waitForPortToBeAvailable(
|
|
|
449
651
|
}
|
|
450
652
|
|
|
451
653
|
function checkPort() {
|
|
654
|
+
if (port === 0) {
|
|
655
|
+
doResolve();
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
452
659
|
// Testing whether a port is 'available' involves simply
|
|
453
660
|
// trying to make a server listen on that port, and retrying
|
|
454
661
|
// until it succeeds.
|
|
@@ -474,3 +681,14 @@ export async function waitForPortToBeAvailable(
|
|
|
474
681
|
}
|
|
475
682
|
});
|
|
476
683
|
}
|
|
684
|
+
|
|
685
|
+
function getAccessibleHosts(): string[] {
|
|
686
|
+
const hosts: string[] = [];
|
|
687
|
+
Object.values(networkInterfaces()).forEach((net) => {
|
|
688
|
+
net?.forEach(({ family, address }) => {
|
|
689
|
+
// @ts-expect-error the `family` property is numeric as of Node.js 18.0.0
|
|
690
|
+
if (family === "IPv4" || family === 4) hosts.push(address);
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
return hosts;
|
|
694
|
+
}
|