zastro-websockets-cloudflare 12.6.0 → 12.6.12-10

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.
@@ -1,3 +1,6 @@
1
+ import { imageConfig } from "astro:assets";
2
+ import { isRemotePath } from "@astrojs/internal-helpers/path";
3
+ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
1
4
  const prerender = false;
2
5
  const GET = (ctx) => {
3
6
  const href = ctx.url.searchParams.get("href");
@@ -7,7 +10,18 @@ const GET = (ctx) => {
7
10
  statusText: "Missing 'href' query parameter"
8
11
  });
9
12
  }
10
- return fetch(new URL(href, ctx.url.origin));
13
+ if (isRemotePath(href)) {
14
+ if (isRemoteAllowed(href, imageConfig) === false) {
15
+ return new Response("Forbidden", { status: 403 });
16
+ } else {
17
+ return Response.redirect(href, 302);
18
+ }
19
+ }
20
+ const proxied = new URL(href, ctx.url.origin);
21
+ if (proxied.origin !== ctx.url.origin) {
22
+ return new Response("Forbidden", { status: 403 });
23
+ }
24
+ return fetch(proxied);
11
25
  };
12
26
  export {
13
27
  GET,
@@ -15,9 +15,9 @@ const service = {
15
15
  if (isESMImportedImage(options.src)) {
16
16
  imageSource = options.src.src;
17
17
  } else if (isRemoteAllowed(options.src, imageConfig)) {
18
- imageSource = String(options.src);
18
+ imageSource = options.src;
19
19
  } else {
20
- return String(options.src);
20
+ return options.src;
21
21
  }
22
22
  const imageEndpoint = joinPaths(
23
23
  // @ts-expect-error Can't recognise import.meta.env
@@ -5,7 +5,6 @@ type Env = {
5
5
  ASSETS: {
6
6
  fetch: (req: Request | string) => Promise<Response>;
7
7
  };
8
- ASTRO_STUDIO_APP_TOKEN?: string;
9
8
  };
10
9
  export declare function createExports(manifest: SSRManifest): {
11
10
  default: {
package/dist/index.d.ts CHANGED
@@ -91,4 +91,22 @@ export type Options = {
91
91
  namedExports?: string[];
92
92
  };
93
93
  };
94
+ declare global {
95
+ namespace App {
96
+ interface Locals {
97
+ isUpgradeRequest?: boolean;
98
+ upgradeWebSocket?: () => {
99
+ socket: import('./websocket/websocket.js').WebSocket;
100
+ response: Response;
101
+ };
102
+ runtime?: {
103
+ env: any;
104
+ cf: any;
105
+ ctx: any;
106
+ caches: any;
107
+ waitUntil: (promise: Promise<any>) => void;
108
+ };
109
+ }
110
+ }
111
+ }
94
112
  export default function createIntegration(args?: Options): AstroIntegration;
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { createReadStream } from "node:fs";
2
2
  import { appendFile, stat } from "node:fs/promises";
3
3
  import { createRequire } from "node:module";
4
4
  import { createInterface } from "node:readline/promises";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { pathToFileURL } from "node:url";
6
6
  import {
7
7
  appendForwardSlash,
8
8
  prependForwardSlash,
@@ -39,6 +39,7 @@ function createIntegration(args) {
39
39
  args?.cloudflareModules ?? true
40
40
  );
41
41
  let _routes;
42
+ const SESSION_KV_BINDING_NAME = args?.sessionKVBindingName ?? "SESSION";
42
43
  return {
43
44
  name: "zastro-websockets-cloudflare",
44
45
  hooks: {
@@ -48,34 +49,21 @@ function createIntegration(args) {
48
49
  updateConfig,
49
50
  logger,
50
51
  addWatchFile,
51
- addMiddleware,
52
- createCodegenDir
52
+ addMiddleware
53
53
  }) => {
54
54
  let session = config.session;
55
- const isBuild = command === "build";
56
55
  if (!session?.driver) {
57
- const sessionDir = isBuild ? void 0 : createCodegenDir();
58
- const bindingName = args?.sessionKVBindingName ?? "SESSION";
59
- if (isBuild) {
60
- logger.info(
61
- `Enabling sessions with Cloudflare KV for production with the "${bindingName}" KV binding.`
62
- );
63
- logger.info(
64
- `If you see the error "Invalid binding \`${bindingName}\`" in your build output, you need to add the binding to your wrangler config file.`
65
- );
66
- }
67
- session = isBuild ? {
56
+ logger.info(
57
+ `Enabling sessions with Cloudflare KV with the "${SESSION_KV_BINDING_NAME}" KV binding.`
58
+ );
59
+ logger.info(
60
+ `If you see the error "Invalid binding \`${SESSION_KV_BINDING_NAME}\`" in your build output, you need to add the binding to your wrangler config file.`
61
+ );
62
+ session = {
68
63
  ...session,
69
64
  driver: "cloudflare-kv-binding",
70
65
  options: {
71
- binding: bindingName,
72
- ...session?.options
73
- }
74
- } : {
75
- ...session,
76
- driver: "fs-lite",
77
- options: {
78
- base: fileURLToPath(new URL("sessions", sessionDir)),
66
+ binding: SESSION_KV_BINDING_NAME,
79
67
  ...session?.options
80
68
  }
81
69
  };
@@ -122,12 +110,7 @@ function createIntegration(args) {
122
110
  "astro:routes:resolved": ({ routes }) => {
123
111
  _routes = routes;
124
112
  },
125
- "astro:config:done": ({ setAdapter, config, buildOutput, logger }) => {
126
- if (buildOutput === "static") {
127
- logger.warn(
128
- "[@astrojs/cloudflare] This adapter is intended to be used with server rendered pages, which this project does not contain any of. As such, this adapter is unnecessary."
129
- );
130
- }
113
+ "astro:config:done": ({ setAdapter, config, buildOutput }) => {
131
114
  _config = config;
132
115
  finalBuildOutput = buildOutput;
133
116
  let customWorkerEntryPoint;
@@ -142,7 +125,7 @@ function createIntegration(args) {
142
125
  setAdapter({
143
126
  name: "zastro-websockets-cloudflare",
144
127
  serverEntrypoint: customWorkerEntryPoint ?? "zastro-websockets-cloudflare/entrypoints/server.js",
145
- exports: args?.workerEntryPoint?.namedExports ? ["default", ...args.workerEntryPoint.namedExports] : ["default"],
128
+ exports: [.../* @__PURE__ */ new Set(["default", ...args?.workerEntryPoint?.namedExports ?? []])],
146
129
  adapterFeatures: {
147
130
  edgeMiddleware: false,
148
131
  buildOutput: "server"
@@ -170,6 +153,8 @@ function createIntegration(args) {
170
153
  await platformProxy.dispose();
171
154
  });
172
155
  setProcessEnv(_config, platformProxy.env);
156
+ globalThis.__env__ ??= {};
157
+ globalThis.__env__[SESSION_KV_BINDING_NAME] = platformProxy.env[SESSION_KV_BINDING_NAME];
173
158
  const clientLocalsSymbol = Symbol.for("astro.locals");
174
159
  server.middlewares.use(async function middleware(req, _res, next) {
175
160
  Reflect.set(req, clientLocalsSymbol, {
@@ -222,9 +207,7 @@ function createIntegration(args) {
222
207
  vite.define = {
223
208
  "process.env": "process.env",
224
209
  // Allows the request handler to know what the binding name is
225
- "globalThis.__ASTRO_SESSION_BINDING_NAME": JSON.stringify(
226
- args?.sessionKVBindingName ?? "SESSION"
227
- ),
210
+ "globalThis.__ASTRO_SESSION_BINDING_NAME": JSON.stringify(SESSION_KV_BINDING_NAME),
228
211
  ...vite.define
229
212
  };
230
213
  }
@@ -1,49 +1,5 @@
1
1
  import { isRemotePath } from "@astrojs/internal-helpers/path";
2
- function matchHostname(url, hostname, allowWildcard) {
3
- if (!hostname) {
4
- return true;
5
- }
6
- if (!allowWildcard || !hostname.startsWith("*")) {
7
- return hostname === url.hostname;
8
- }
9
- if (hostname.startsWith("**.")) {
10
- const slicedHostname = hostname.slice(2);
11
- return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname);
12
- }
13
- if (hostname.startsWith("*.")) {
14
- const slicedHostname = hostname.slice(1);
15
- const additionalSubdomains = url.hostname.replace(slicedHostname, "").split(".").filter(Boolean);
16
- return additionalSubdomains.length === 1;
17
- }
18
- return false;
19
- }
20
- function matchPort(url, port) {
21
- return !port || port === url.port;
22
- }
23
- function matchProtocol(url, protocol) {
24
- return !protocol || protocol === url.protocol.slice(0, -1);
25
- }
26
- function matchPathname(url, pathname, allowWildcard) {
27
- if (!pathname) {
28
- return true;
29
- }
30
- if (!allowWildcard || !pathname.endsWith("*")) {
31
- return pathname === url.pathname;
32
- }
33
- if (pathname.endsWith("/**")) {
34
- const slicedPathname = pathname.slice(0, -2);
35
- return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname);
36
- }
37
- if (pathname.endsWith("/*")) {
38
- const slicedPathname = pathname.slice(0, -1);
39
- const additionalPathChunks = url.pathname.replace(slicedPathname, "").split("/").filter(Boolean);
40
- return additionalPathChunks.length === 1;
41
- }
42
- return false;
43
- }
44
- function matchPattern(url, remotePattern) {
45
- return matchProtocol(url, remotePattern.protocol) && matchHostname(url, remotePattern.hostname, true) && matchPort(url, remotePattern.port) && matchPathname(url, remotePattern.pathname, true);
46
- }
2
+ import { matchHostname, matchPattern } from "@astrojs/internal-helpers/remote";
47
3
  function isRemoteAllowed(src, {
48
4
  domains = [],
49
5
  remotePatterns = []
@@ -6,7 +6,6 @@ type Env = {
6
6
  ASSETS: {
7
7
  fetch: (req: Request | string) => Promise<Response>;
8
8
  };
9
- ASTRO_STUDIO_APP_TOKEN?: string;
10
9
  };
11
10
  export interface Runtime<T extends object = object> {
12
11
  runtime: {
@@ -20,11 +20,6 @@ async function handle(manifest, app, request, env, context) {
20
20
  }
21
21
  }
22
22
  Reflect.set(request, Symbol.for("astro.clientAddress"), request.headers.get("cf-connecting-ip"));
23
- process.env.ASTRO_STUDIO_APP_TOKEN ??= (() => {
24
- if (typeof env.ASTRO_STUDIO_APP_TOKEN === "string") {
25
- return env.ASTRO_STUDIO_APP_TOKEN;
26
- }
27
- })();
28
23
  const locals = {
29
24
  runtime: {
30
25
  env,
@@ -9,15 +9,12 @@ const onRequest = async function cloudflareWebSocketMiddleware(context, next) {
9
9
  }
10
10
  const webSocketPair = new WebSocketPair();
11
11
  const [client, server] = [webSocketPair[0], webSocketPair[1]];
12
+ server.accept();
12
13
  const socket = new WebSocket(request.url);
13
14
  attach(socket, server);
14
15
  const response = new Response(null, {
15
16
  status: 101,
16
17
  statusText: "Switching Protocols",
17
- headers: {
18
- "Upgrade": "websocket",
19
- "Connection": "Upgrade"
20
- },
21
18
  webSocket: client
22
19
  });
23
20
  return { socket, response };
@@ -8,6 +8,7 @@ function createWebSocketHandler(app) {
8
8
  }
9
9
  const webSocketPair = new WebSocketPair();
10
10
  const [client, server] = [webSocketPair[0], webSocketPair[1]];
11
+ server.accept();
11
12
  const response = await app.render(request, {
12
13
  locals: {
13
14
  isUpgradeRequest: true,
@@ -17,10 +18,6 @@ function createWebSocketHandler(app) {
17
18
  const upgradeResponse = new Response(null, {
18
19
  status: 101,
19
20
  statusText: "Switching Protocols",
20
- headers: {
21
- "Upgrade": "websocket",
22
- "Connection": "Upgrade"
23
- },
24
21
  webSocket: client
25
22
  });
26
23
  return { socket, response: upgradeResponse };
@@ -34,15 +31,10 @@ function createWebSocketHandler(app) {
34
31
  }
35
32
  }
36
33
  });
37
- if (response.status === 101 && response.headers.get("upgrade") === "websocket") {
38
- server.accept();
34
+ if (response.status === 101) {
39
35
  return new Response(null, {
40
36
  status: 101,
41
37
  statusText: "Switching Protocols",
42
- headers: {
43
- "Upgrade": "websocket",
44
- "Connection": "Upgrade"
45
- },
46
38
  webSocket: client
47
39
  });
48
40
  }
@@ -6,17 +6,16 @@ export interface WebSocketUpgrade {
6
6
  response: Response;
7
7
  }
8
8
  export declare class WebSocket extends EventTarget {
9
- private _ws;
9
+ static readonly CONNECTING: 0;
10
+ static readonly OPEN: 1;
11
+ static readonly CLOSING: 2;
12
+ static readonly CLOSED: 3;
10
13
  private _readyState;
11
14
  private _binaryType;
12
15
  private _url;
13
16
  private _protocol;
14
17
  private _extensions;
15
- private _bufferedAmount;
16
- static readonly CONNECTING: 0;
17
- static readonly OPEN: 1;
18
- static readonly CLOSING: 2;
19
- static readonly CLOSED: 3;
18
+ private _ws;
20
19
  readonly CONNECTING: 0;
21
20
  readonly OPEN: 1;
22
21
  readonly CLOSING: 2;
@@ -1,16 +1,15 @@
1
1
  class WebSocket extends EventTarget {
2
- _ws;
3
- _readyState = 0;
4
- // Use numeric literal instead of static property
5
- _binaryType = "blob";
6
- _url = "";
7
- _protocol = "";
8
- _extensions = "";
9
- _bufferedAmount = 0;
10
2
  static CONNECTING = 0;
11
3
  static OPEN = 1;
12
4
  static CLOSING = 2;
13
5
  static CLOSED = 3;
6
+ // CloudFlare doesn't use private fields - use simple approach
7
+ _readyState = WebSocket.CONNECTING;
8
+ _binaryType = "blob";
9
+ _url = "";
10
+ _protocol = "";
11
+ _extensions = "";
12
+ _ws;
14
13
  // Instance constants
15
14
  CONNECTING = 0;
16
15
  OPEN = 1;
@@ -32,7 +31,7 @@ class WebSocket extends EventTarget {
32
31
  return this._readyState;
33
32
  }
34
33
  get bufferedAmount() {
35
- return this._bufferedAmount;
34
+ return 0;
36
35
  }
37
36
  get extensions() {
38
37
  return this._extensions;
@@ -53,11 +52,6 @@ class WebSocket extends EventTarget {
53
52
  this._readyState = WebSocket.CLOSING;
54
53
  if (this._ws) {
55
54
  this._ws.close(code, reason);
56
- } else {
57
- this._readyState = WebSocket.CLOSED;
58
- const event = new CloseEvent("close", { code: code || 1e3, reason: reason || "", wasClean: true });
59
- this.onclose?.(event);
60
- this.dispatchEvent(event);
61
55
  }
62
56
  }
63
57
  send(data) {
@@ -65,11 +59,7 @@ class WebSocket extends EventTarget {
65
59
  throw new Error("WebSocket is not open");
66
60
  }
67
61
  if (this._ws) {
68
- if (data instanceof Blob) {
69
- data.arrayBuffer().then((buffer) => this._ws.send(buffer));
70
- } else {
71
- this._ws.send(data);
72
- }
62
+ this._ws.send(data);
73
63
  }
74
64
  }
75
65
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zastro-websockets-cloudflare",
3
3
  "description": "Deploy your site to Cloudflare Workers/Pages with WebSocket support",
4
- "version": "12.6.0",
4
+ "version": "12.6.12-10",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "Zach Handley <zach@zachhandley.com>",
@@ -23,20 +23,23 @@
23
23
  "./image-endpoint": "./dist/entrypoints/image-endpoint.js",
24
24
  "./handler": "./dist/utils/handler.js",
25
25
  "./package.json": "./package.json",
26
- "./websocket": "./dist/websocket/index.js"
26
+ "./websocket": "./dist/websocket/index.js",
27
+ "./websocket/middleware.js": "./dist/websocket/middleware.js",
28
+ "./websocket/websocket.js": "./dist/websocket/websocket.js",
29
+ "./websocket/server.js": "./dist/websocket/server.js"
27
30
  },
28
31
  "files": [
29
32
  "dist"
30
33
  ],
31
34
  "dependencies": {
32
- "@astrojs/internal-helpers": "^0.6.1",
35
+ "@astrojs/internal-helpers": "^0.7.5",
33
36
  "@astrojs/underscore-redirects": "^1.0.0",
34
- "@cloudflare/workers-types": "^4.20250507.0",
35
- "tinyglobby": "^0.2.13",
36
- "vite": "^6.3.5",
37
- "wrangler": "^4.14.1"
37
+ "@cloudflare/workers-types": "^4.20260116.0",
38
+ "tinyglobby": "^0.2.15",
39
+ "vite": "^6.4.1",
40
+ "wrangler": "4.59.2"
38
41
  },
39
42
  "peerDependencies": {
40
- "astro": "^5.0.0"
43
+ "astro": "^5.7.0"
41
44
  }
42
45
  }