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.
Files changed (239) hide show
  1. package/README.md +16 -2
  2. package/bin/wrangler.js +31 -11
  3. package/miniflare-dist/index.mjs +1340 -330
  4. package/package.json +40 -13
  5. package/src/__tests__/access.test.ts +25 -0
  6. package/src/__tests__/api-dev.test.ts +238 -0
  7. package/src/__tests__/api-devregistry.test.js +64 -0
  8. package/src/__tests__/api.test.ts +80 -0
  9. package/src/__tests__/configuration.test.ts +683 -470
  10. package/src/__tests__/d1.test.ts +123 -0
  11. package/src/__tests__/delete.test.ts +263 -0
  12. package/src/__tests__/deployments.test.ts +105 -0
  13. package/src/__tests__/dev.test.tsx +453 -70
  14. package/src/__tests__/dialogs.test.tsx +40 -0
  15. package/src/__tests__/generate.test.ts +235 -0
  16. package/src/__tests__/get-host-from-url.test.ts +16 -0
  17. package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
  18. package/src/__tests__/helpers/mock-cfetch.ts +92 -26
  19. package/src/__tests__/helpers/mock-console.ts +11 -2
  20. package/src/__tests__/helpers/mock-dialogs.ts +1 -1
  21. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  22. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  23. package/src/__tests__/helpers/mock-kv.ts +1 -1
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +74 -53
  25. package/src/__tests__/helpers/mock-process.ts +8 -13
  26. package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
  27. package/src/__tests__/helpers/msw/handlers/deployments.ts +59 -0
  28. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  29. package/src/__tests__/helpers/msw/handlers/oauth.ts +31 -0
  30. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  31. package/src/__tests__/helpers/msw/handlers/script.ts +75 -0
  32. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  33. package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
  34. package/src/__tests__/helpers/msw/index.ts +21 -0
  35. package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
  36. package/src/__tests__/helpers/worker-scripts/hello-world-worker.js +5 -0
  37. package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
  38. package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
  39. package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
  40. package/src/__tests__/index.test.ts +103 -97
  41. package/src/__tests__/init.test.ts +1484 -382
  42. package/src/__tests__/jest.setup.ts +88 -5
  43. package/src/__tests__/kv.test.ts +655 -540
  44. package/src/__tests__/logout.test.ts +47 -0
  45. package/src/__tests__/metrics.test.ts +95 -45
  46. package/src/__tests__/middleware.scheduled.test.ts +145 -0
  47. package/src/__tests__/middleware.test.ts +750 -0
  48. package/src/__tests__/pages-deployment-tail.test.ts +884 -0
  49. package/src/__tests__/pages.test.ts +1301 -119
  50. package/src/__tests__/paths.test.ts +39 -0
  51. package/src/__tests__/publish.test.ts +2266 -1180
  52. package/src/__tests__/pubsub.test.ts +211 -88
  53. package/src/__tests__/queues.test.ts +522 -0
  54. package/src/__tests__/r2.test.ts +363 -203
  55. package/src/__tests__/secret.test.ts +261 -59
  56. package/src/__tests__/tail.test.ts +352 -151
  57. package/src/__tests__/test-old-node-version.js +3 -3
  58. package/src/__tests__/tsconfig-sanity.ts +12 -0
  59. package/src/__tests__/tsconfig.json +8 -0
  60. package/src/__tests__/tsconfig.tsbuildinfo +1 -0
  61. package/src/__tests__/type-generation.test.ts +233 -0
  62. package/src/__tests__/user.test.ts +59 -73
  63. package/src/__tests__/utils-collectKeyValues.test.ts +47 -0
  64. package/src/__tests__/validate-dev-props.test.ts +56 -0
  65. package/src/__tests__/whoami.test.tsx +114 -66
  66. package/src/__tests__/worker-namespace.test.ts +154 -155
  67. package/src/api/dev.ts +170 -38
  68. package/src/api/index.ts +1 -0
  69. package/src/bundle-reporter.tsx +41 -2
  70. package/src/bundle.ts +668 -81
  71. package/src/cfetch/index.ts +40 -3
  72. package/src/cfetch/internal.ts +144 -32
  73. package/src/cli.ts +4 -3
  74. package/src/config/config.ts +27 -3
  75. package/src/config/environment.ts +131 -7
  76. package/src/config/index.ts +128 -8
  77. package/src/config/validation-helpers.ts +19 -6
  78. package/src/config/validation.ts +411 -14
  79. package/src/config-cache.ts +3 -2
  80. package/src/create-worker-preview.ts +31 -6
  81. package/src/create-worker-upload-form.ts +84 -22
  82. package/src/d1/backups.tsx +223 -0
  83. package/src/d1/constants.ts +2 -0
  84. package/src/d1/create.tsx +63 -0
  85. package/src/d1/delete.tsx +56 -0
  86. package/src/d1/execute.tsx +352 -0
  87. package/src/d1/formatTimeAgo.ts +14 -0
  88. package/src/d1/index.ts +101 -0
  89. package/src/d1/list.tsx +48 -0
  90. package/src/d1/migrations/apply.tsx +196 -0
  91. package/src/d1/migrations/create.tsx +77 -0
  92. package/src/d1/migrations/helpers.ts +147 -0
  93. package/src/d1/migrations/index.tsx +3 -0
  94. package/src/d1/migrations/list.tsx +79 -0
  95. package/src/d1/migrations/options.ts +18 -0
  96. package/src/d1/options.ts +22 -0
  97. package/src/d1/types.tsx +24 -0
  98. package/src/d1/utils.ts +48 -0
  99. package/src/delete.ts +108 -0
  100. package/src/deployments.ts +83 -0
  101. package/src/deprecated/index.ts +145 -0
  102. package/src/dev/dev-vars.ts +11 -8
  103. package/src/dev/dev.tsx +220 -58
  104. package/src/dev/get-local-persistence-path.tsx +31 -0
  105. package/src/dev/local.tsx +740 -181
  106. package/src/dev/remote.tsx +340 -123
  107. package/src/dev/start-server.ts +543 -0
  108. package/src/dev/use-esbuild.ts +61 -7
  109. package/src/dev/validate-dev-props.ts +31 -0
  110. package/src/dev-registry.tsx +201 -0
  111. package/src/dev.tsx +614 -258
  112. package/src/dialogs.tsx +41 -8
  113. package/src/{worker-namespace.ts → dispatch-namespace.ts} +23 -22
  114. package/src/docs/index.ts +97 -0
  115. package/src/entry.ts +3 -2
  116. package/src/environment-variables/factory.ts +88 -0
  117. package/src/environment-variables/misc-variables.ts +30 -0
  118. package/src/generate/index.ts +300 -0
  119. package/src/git-client.ts +100 -7
  120. package/src/index.tsx +249 -1560
  121. package/src/init.ts +626 -150
  122. package/src/inspect.ts +137 -32
  123. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  124. package/src/is-interactive.ts +4 -0
  125. package/src/jest.d.ts +4 -0
  126. package/src/{kv.ts → kv/helpers.ts} +3 -3
  127. package/src/kv/index.ts +596 -0
  128. package/src/logger.ts +27 -8
  129. package/src/metrics/index.ts +1 -0
  130. package/src/metrics/metrics-config.ts +4 -4
  131. package/src/metrics/metrics-dispatcher.ts +1 -0
  132. package/src/metrics/metrics-usage-headers.ts +24 -0
  133. package/src/metrics/send-event.ts +13 -8
  134. package/src/miniflare-cli/assets.ts +172 -465
  135. package/src/miniflare-cli/index.ts +149 -24
  136. package/src/miniflare-cli/tsconfig.json +9 -0
  137. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
  138. package/src/miniflare-cli/types.ts +11 -0
  139. package/src/module-collection.ts +12 -3
  140. package/src/pages/build.tsx +108 -42
  141. package/src/pages/constants.ts +11 -0
  142. package/src/pages/deployment-tails.tsx +284 -0
  143. package/src/pages/deployments.tsx +9 -31
  144. package/src/pages/dev.tsx +417 -108
  145. package/src/pages/errors.ts +66 -0
  146. package/src/pages/functions/buildPlugin.ts +86 -84
  147. package/src/pages/functions/buildWorker.ts +121 -106
  148. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  149. package/src/pages/functions/routes-consolidation.ts +73 -0
  150. package/src/pages/functions/routes-transformation.test.ts +282 -0
  151. package/src/pages/functions/routes-transformation.ts +115 -0
  152. package/src/pages/functions/routes-validation.test.ts +403 -0
  153. package/src/pages/functions/routes-validation.ts +202 -0
  154. package/src/pages/functions/tsconfig.json +8 -0
  155. package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
  156. package/src/pages/functions.tsx +82 -0
  157. package/src/pages/hash.tsx +13 -0
  158. package/src/pages/index.tsx +73 -55
  159. package/src/pages/projects.tsx +9 -3
  160. package/src/pages/prompt-select-project.tsx +31 -0
  161. package/src/pages/publish.tsx +183 -60
  162. package/src/pages/types.ts +1 -0
  163. package/src/pages/upload.tsx +107 -56
  164. package/src/pages/utils.ts +13 -2
  165. package/src/paths.ts +49 -4
  166. package/src/proxy.ts +375 -157
  167. package/src/publish/index.ts +270 -0
  168. package/src/{publish.ts → publish/publish.ts} +431 -188
  169. package/src/pubsub/pubsub-commands.tsx +4 -3
  170. package/src/queues/cli/commands/consumer/add.ts +71 -0
  171. package/src/queues/cli/commands/consumer/index.ts +22 -0
  172. package/src/queues/cli/commands/consumer/remove.ts +38 -0
  173. package/src/queues/cli/commands/create.ts +25 -0
  174. package/src/queues/cli/commands/delete.ts +26 -0
  175. package/src/queues/cli/commands/index.ts +36 -0
  176. package/src/queues/cli/commands/list.ts +25 -0
  177. package/src/queues/client.ts +135 -0
  178. package/src/queues/utils.ts +18 -0
  179. package/src/r2/constants.ts +4 -0
  180. package/src/r2/helpers.ts +131 -0
  181. package/src/r2/index.ts +289 -0
  182. package/src/routes.ts +140 -0
  183. package/src/secret/index.ts +379 -0
  184. package/src/sites.tsx +3 -3
  185. package/src/tail/createTail.ts +379 -0
  186. package/src/tail/filters.ts +4 -6
  187. package/src/tail/index.ts +199 -284
  188. package/src/tail/printing.ts +43 -3
  189. package/src/tsconfig-sanity.ts +16 -0
  190. package/src/type-generation.ts +181 -0
  191. package/src/user/access.ts +68 -0
  192. package/src/user/auth-variables.ts +113 -0
  193. package/src/user/choose-account.tsx +1 -1
  194. package/src/user/index.ts +0 -1
  195. package/src/user/user.tsx +116 -49
  196. package/src/utils/collectKeyValues.ts +14 -0
  197. package/src/whoami.tsx +80 -2
  198. package/src/worker.ts +69 -9
  199. package/src/yargs-types.ts +24 -0
  200. package/src/zones.ts +124 -9
  201. package/templates/__tests__/pages-dev-util.test.ts +128 -0
  202. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  203. package/templates/__tests__/tsconfig.json +8 -0
  204. package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
  205. package/templates/d1-beta-facade.js +205 -0
  206. package/templates/facade.d.ts +14 -0
  207. package/templates/first-party-worker-module-facade.ts +19 -0
  208. package/templates/format-dev-errors.ts +33 -0
  209. package/templates/init-tests/test-jest-new-worker.js +23 -0
  210. package/templates/init-tests/test-vitest-new-worker.js +24 -0
  211. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  212. package/templates/middleware/common.ts +62 -0
  213. package/templates/middleware/loader-modules.ts +82 -0
  214. package/templates/middleware/loader-sw.ts +233 -0
  215. package/templates/middleware/middleware-miniflare3-json-error.ts +20 -0
  216. package/templates/middleware/middleware-pretty-error.ts +40 -0
  217. package/templates/middleware/middleware-scheduled.ts +15 -0
  218. package/templates/new-worker.js +1 -1
  219. package/templates/pages-dev-pipeline.ts +31 -0
  220. package/templates/pages-dev-util.ts +52 -0
  221. package/templates/pages-shim.ts +4 -1
  222. package/templates/pages-template-plugin.ts +21 -5
  223. package/templates/pages-template-worker.ts +37 -9
  224. package/templates/{static-asset-facade.js → serve-static-assets.ts} +23 -7
  225. package/templates/service-bindings-module-facade.js +54 -0
  226. package/templates/service-bindings-sw-facade.js +42 -0
  227. package/templates/tsconfig-sanity.ts +11 -0
  228. package/templates/tsconfig.init.json +106 -0
  229. package/templates/tsconfig.json +5 -102
  230. package/templates/tsconfig.tsbuildinfo +1 -0
  231. package/wrangler-dist/cli.d.ts +621 -42
  232. package/wrangler-dist/cli.js +144441 -102606
  233. package/src/environment-variables.ts +0 -35
  234. package/src/faye-websocket.d.ts +0 -6
  235. package/src/generate.ts +0 -33
  236. package/src/miniflare-cli/enum-keys.ts +0 -17
  237. package/src/preview.ts +0 -31
  238. package/src/r2.ts +0 -50
  239. 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(path: string): UrlPath {
21
+ export function toUrlPath(filePath: string): UrlPath {
21
22
  assert(
22
- !/^[a-z]:/i.test(path),
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 path.replace(/\\/g, "/") as UrlPath;
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 WebSocket from "faye-websocket";
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 ws from "ws";
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
- if (proxy === undefined) {
142
- return;
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
- remote,
259
+ streamBufferRef,
260
+ requestResponseBufferRef,
261
+ retryServerSetup,
262
+ assetDirectory,
263
+ localProtocol,
192
264
  port,
193
- localProtocol
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.forEach((cleanup) => cleanup());
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
- logger.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
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
+ }