zastro-websockets-node 9.3.1-1 → 9.5.2-2

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/dist/index.d.ts CHANGED
@@ -1,4 +1,15 @@
1
1
  import type { AstroAdapter, AstroIntegration } from 'astro';
2
2
  import type { Options, UserOptions } from './types.js';
3
3
  export declare function getAdapter(options: Options): AstroAdapter;
4
+ declare global {
5
+ namespace App {
6
+ interface Locals {
7
+ isUpgradeRequest?: boolean;
8
+ upgradeWebSocket?: () => {
9
+ socket: import('./websocket/websocket.js').WebSocket;
10
+ response: import('./websocket/response.js').UpgradeResponse;
11
+ };
12
+ }
13
+ }
14
+ }
4
15
  export default function createIntegration(userOptions: UserOptions): AstroIntegration;
package/dist/index.js CHANGED
@@ -24,17 +24,24 @@ function getAdapter(options) {
24
24
  }
25
25
  };
26
26
  }
27
+ const protocols = ["http:", "https:"];
27
28
  function createIntegration(userOptions) {
28
29
  if (!userOptions?.mode) {
29
30
  throw new AstroError(`Setting the 'mode' option is required.`);
30
31
  }
32
+ const { experimentalErrorPageHost } = userOptions;
33
+ if (experimentalErrorPageHost && (!URL.canParse(experimentalErrorPageHost) || !protocols.includes(new URL(experimentalErrorPageHost).protocol))) {
34
+ throw new AstroError(
35
+ `Invalid experimentalErrorPageHost: ${experimentalErrorPageHost}. It should be a valid URL.`
36
+ );
37
+ }
31
38
  let _options;
32
39
  let _config = void 0;
33
40
  let _routeToHeaders = void 0;
34
41
  return {
35
42
  name: "zastro-websockets-node",
36
43
  hooks: {
37
- "astro:config:setup": async ({ updateConfig, config, logger }) => {
44
+ "astro:config:setup": async ({ updateConfig, config, logger, command }) => {
38
45
  let session = config.session;
39
46
  _config = config;
40
47
  if (!session?.driver) {
@@ -48,10 +55,13 @@ function createIntegration(userOptions) {
48
55
  };
49
56
  }
50
57
  updateConfig({
58
+ build: {
59
+ redirects: false
60
+ },
51
61
  image: {
52
62
  endpoint: {
53
63
  route: config.image.endpoint.route ?? "_image",
54
- entrypoint: config.image.endpoint.entrypoint ?? "astro/assets/endpoint/node"
64
+ entrypoint: config.image.endpoint.entrypoint ?? (command === "dev" ? "astro/assets/endpoint/dev" : "astro/assets/endpoint/node")
55
65
  }
56
66
  },
57
67
  session,
@@ -73,7 +83,8 @@ function createIntegration(userOptions) {
73
83
  host: config.server.host,
74
84
  port: config.server.port,
75
85
  assets: config.build.assets,
76
- experimentalStaticHeaders: userOptions.experimentalStaticHeaders ?? false
86
+ experimentalStaticHeaders: userOptions.experimentalStaticHeaders ?? false,
87
+ experimentalErrorPageHost
77
88
  };
78
89
  setAdapter(getAdapter(_options));
79
90
  },
@@ -1,5 +1,5 @@
1
1
  import type { NodeApp } from 'astro/app/node';
2
- import type { RequestHandler } from './types.js';
2
+ import type { Options, RequestHandler } from './types.js';
3
3
  /**
4
4
  * Creates a middleware that can be used with Express, Connect, etc.
5
5
  *
@@ -8,4 +8,4 @@ import type { RequestHandler } from './types.js';
8
8
  *
9
9
  * https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
10
10
  */
11
- export default function createMiddleware(app: NodeApp): RequestHandler;
11
+ export default function createMiddleware(app: NodeApp, options: Options): RequestHandler;
@@ -1,6 +1,6 @@
1
1
  import { createAppHandler } from "./serve-app.js";
2
- function createMiddleware(app) {
3
- const handler = createAppHandler(app);
2
+ function createMiddleware(app, options) {
3
+ const handler = createAppHandler(app, options);
4
4
  const logger = app.getAdapterLogger();
5
5
  return async (...args) => {
6
6
  const [req, res, next, locals] = args;
@@ -1,8 +1,8 @@
1
1
  import { NodeApp } from 'astro/app/node';
2
- import type { RequestHandler } from './types.js';
2
+ import type { Options, RequestHandler } from './types.js';
3
3
  /**
4
4
  * Creates a Node.js http listener for on-demand rendered pages, compatible with http.createServer and Connect middleware.
5
5
  * If the next callback is provided, it will be called if the request does not have a matching route.
6
6
  * Intended to be used in both standalone and middleware mode.
7
7
  */
8
- export declare function createAppHandler(app: NodeApp): RequestHandler;
8
+ export declare function createAppHandler(app: NodeApp, options: Options): RequestHandler;
package/dist/serve-app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { NodeApp } from "astro/app/node";
3
- function createAppHandler(app) {
3
+ function createAppHandler(app, options) {
4
4
  const als = new AsyncLocalStorage();
5
5
  const logger = app.getAdapterLogger();
6
6
  process.on("unhandledRejection", (reason) => {
@@ -8,10 +8,19 @@ function createAppHandler(app) {
8
8
  logger.error(`Unhandled rejection while rendering ${requestUrl}`);
9
9
  console.error(reason);
10
10
  });
11
+ const originUrl = options.experimentalErrorPageHost ? new URL(options.experimentalErrorPageHost) : void 0;
12
+ const prerenderedErrorPageFetch = originUrl ? (url) => {
13
+ const errorPageUrl = new URL(url);
14
+ errorPageUrl.protocol = originUrl.protocol;
15
+ errorPageUrl.host = originUrl.host;
16
+ return fetch(errorPageUrl);
17
+ } : void 0;
11
18
  return async (req, res, next, locals = {}) => {
12
19
  let request;
13
20
  try {
14
- request = NodeApp.createRequest(req);
21
+ request = NodeApp.createRequest(req, {
22
+ allowedDomains: app.getAllowedDomains?.() ?? []
23
+ });
15
24
  } catch (err) {
16
25
  logger.error(`Could not render ${req.url}`);
17
26
  console.error(err);
@@ -19,27 +28,22 @@ function createAppHandler(app) {
19
28
  res.end("Internal Server Error");
20
29
  return;
21
30
  }
22
- Object.assign(locals, {
23
- isUpgradeRequest: false,
24
- upgradeWebSocket() {
25
- throw new Error("The request must be an upgrade request to upgrade the connection to a WebSocket.");
26
- }
27
- });
28
- const routeData = app.match(request);
31
+ const routeData = app.match(request, true);
29
32
  if (routeData) {
30
33
  const response = await als.run(
31
34
  request.url,
32
35
  () => app.render(request, {
33
36
  addCookieHeader: true,
34
37
  locals,
35
- routeData
38
+ routeData,
39
+ prerenderedErrorPageFetch
36
40
  })
37
41
  );
38
42
  await NodeApp.writeResponse(response, res);
39
43
  } else if (next) {
40
44
  return next();
41
45
  } else {
42
- const response = await app.render(req, { addCookieHeader: true });
46
+ const response = await app.render(req, { addCookieHeader: true, prerenderedErrorPageFetch });
43
47
  await NodeApp.writeResponse(response, res);
44
48
  }
45
49
  };
@@ -1,13 +1,17 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import url from "node:url";
4
- import { hasFileExtension } from "@astrojs/internal-helpers/path";
4
+ import { hasFileExtension, isInternalPath } from "@astrojs/internal-helpers/path";
5
5
  import send from "send";
6
6
  function createStaticHandler(app, options) {
7
7
  const client = resolveClientDir(options);
8
8
  return (req, res, ssr) => {
9
9
  if (req.url) {
10
- const [urlPath, urlQuery] = req.url.split("?");
10
+ let fullUrl = req.url;
11
+ if (req.url.includes("#")) {
12
+ fullUrl = fullUrl.slice(0, req.url.indexOf("#"));
13
+ }
14
+ const [urlPath, urlQuery] = fullUrl.split("?");
11
15
  const filePath = path.join(client, app.removeBase(urlPath));
12
16
  let isDirectory = false;
13
17
  try {
@@ -48,7 +52,7 @@ function createStaticHandler(app, options) {
48
52
  break;
49
53
  }
50
54
  case "always": {
51
- if (!hasSlash && !hasFileExtension(urlPath)) {
55
+ if (!hasSlash && !hasFileExtension(urlPath) && !isInternalPath(urlPath)) {
52
56
  pathname = urlPath + "/" + (urlQuery ? "?" + urlQuery : "");
53
57
  res.statusCode = 301;
54
58
  res.setHeader("Location", pathname);
package/dist/server.d.ts CHANGED
@@ -14,5 +14,6 @@ export declare function createExports(manifest: SSRManifest, options: Options):
14
14
  };
15
15
  done: Promise<void>;
16
16
  };
17
+ websocketHandler: (req: import("http").IncomingMessage, socket: import("stream").Duplex, head: NonSharedBuffer) => void;
17
18
  };
18
19
  export declare function start(manifest: SSRManifest, options: Options): void;
package/dist/server.js CHANGED
@@ -5,6 +5,7 @@ import { setGetEnv } from "astro/env/setup";
5
5
  import createMiddleware from "./middleware.js";
6
6
  import { STATIC_HEADERS_FILE } from "./shared.js";
7
7
  import startServer, { createStandaloneHandler } from "./standalone.js";
8
+ import { createWebsocketHandler } from "./websocket/serve-websocket.js";
8
9
  setGetEnv((key) => process.env[key]);
9
10
  function createExports(manifest, options) {
10
11
  const app = new NodeApp(manifest, !options.experimentalDisableStreaming);
@@ -18,8 +19,9 @@ function createExports(manifest, options) {
18
19
  options.trailingSlash = manifest.trailingSlash;
19
20
  return {
20
21
  options,
21
- handler: options.mode === "middleware" ? createMiddleware(app) : createStandaloneHandler(app, options),
22
- startServer: () => startServer(app, options)
22
+ handler: options.mode === "middleware" ? createMiddleware(app, options) : createStandaloneHandler(app, options),
23
+ startServer: () => startServer(app, options),
24
+ websocketHandler: createWebsocketHandler(app)
23
25
  };
24
26
  }
25
27
  function start(manifest, options) {
@@ -28,7 +28,7 @@ function standalone(app, options) {
28
28
  };
29
29
  }
30
30
  function createStandaloneHandler(app, options) {
31
- const appHandler = createAppHandler(app);
31
+ const appHandler = createAppHandler(app, options);
32
32
  const staticHandler = createStaticHandler(app, options);
33
33
  return (req, res) => {
34
34
  try {
package/dist/types.d.ts CHANGED
@@ -19,6 +19,14 @@ export interface UserOptions {
19
19
  * - The CSP header of the static pages is added when CSP support is enabled.
20
20
  */
21
21
  experimentalStaticHeaders?: boolean;
22
+ /**
23
+ * The host that should be used if the server needs to fetch the prerendered error page.
24
+ * If not provided, this will default to the host of the server. This should be set if the server
25
+ * should fetch prerendered error pages from a different host than the public URL of the server.
26
+ * This is useful for example if the server is behind a reverse proxy or a load balancer, or if
27
+ * static files are hosted on a different domain. Do not include a path in the URL: it will be ignored.
28
+ */
29
+ experimentalErrorPageHost?: string | URL;
22
30
  }
23
31
  export interface Options extends UserOptions {
24
32
  host: string | boolean;
@@ -1,3 +1,15 @@
1
1
  import type * as ws from "ws";
2
2
  import type { WebSocket } from "./websocket.js";
3
+ /**
4
+ * To keep the internals hidden, the function that attaches the
5
+ * ws.WebSocket to the WebSocket instance is created within
6
+ * WebSocket's static block, and assigned to this variable.
7
+ */
8
+ export declare const attacher: {
9
+ attach: null | typeof attach;
10
+ };
11
+ /**
12
+ * Attach a ws.WebSocket connected to I/O to the implementation
13
+ * of the standard WebSocket class exposed to the public API.
14
+ */
3
15
  export declare function attach(standard: WebSocket, ws: ws.WebSocket): void;
@@ -1,15 +1,8 @@
1
- const wsMap = /* @__PURE__ */ new WeakMap();
2
1
  const attacher = { attach: null };
3
- function attachImpl(standard, ws) {
4
- if (wsMap.has(standard)) {
5
- throw new Error("WebSocket already attached");
6
- }
7
- wsMap.set(standard, ws);
8
- }
9
- attacher.attach = attachImpl;
10
2
  function attach(standard, ws) {
11
3
  return attacher.attach?.(standard, ws);
12
4
  }
13
5
  export {
14
- attach
6
+ attach,
7
+ attacher
15
8
  };
@@ -1 +1,12 @@
1
- export declare const onRequest: (context: any, next: () => Promise<Response>) => Promise<Response>;
1
+ import type { APIContext, AstroIntegration, MiddlewareNext } from "astro";
2
+ export type ViteDevServer = Parameters<NonNullable<AstroIntegration["hooks"]["astro:server:setup"]>>[0]["server"];
3
+ /**
4
+ * This dev-only middleware is responsible for all requests
5
+ * that have been made as a result of an upgrade request.
6
+ *
7
+ * It checks whether the request is running within the context
8
+ * of an `upgradeRequestStorage`, which are created below in
9
+ * `hookIntoViteDevServer()`, and only runs if it is.
10
+ */
11
+ export declare const onRequest: (context: APIContext, next: MiddlewareNext) => Promise<Response>;
12
+ export declare function handleUpgradeRequests(viteDevServer: ViteDevServer): void;
@@ -1,12 +1,95 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import * as ws from "ws";
3
+ import { UpgradeResponse, writeResponseToSocket } from "./response.js";
4
+ import { WebSocket } from "./websocket.js";
5
+ import { attach as _attach } from "./attach.js";
6
+ const upgradeRequestStorage = (
7
+ // @ts-expect-error
8
+ globalThis.__upgradeRequestStorage ??= new AsyncLocalStorage()
9
+ );
10
+ const responseToSocketMap = (
11
+ // @ts-expect-error
12
+ globalThis.__responseToSocketMap ??= /* @__PURE__ */ new WeakMap()
13
+ );
14
+ globalThis.__UpgradeResponse = UpgradeResponse;
15
+ function newUpgradeResponse() {
16
+ return new globalThis.__UpgradeResponse();
17
+ }
18
+ globalThis.__WebSocket = WebSocket;
19
+ function newWebSocket() {
20
+ return new globalThis.__WebSocket();
21
+ }
22
+ globalThis.__attach = _attach;
23
+ function attach(...args) {
24
+ return globalThis.__attach(...args);
25
+ }
1
26
  const onRequest = async function websocketDevMiddleware(context, next) {
2
- const { request, locals } = context;
3
- const isUpgradeRequest = request.headers.get("upgrade") === "websocket";
4
- locals.isUpgradeRequest = isUpgradeRequest;
5
- locals.upgradeWebSocket = () => {
6
- throw new Error("The request must be an upgrade request to upgrade the connection to a WebSocket.");
7
- };
8
- return next();
27
+ const upgradeRequest = upgradeRequestStorage.getStore();
28
+ if (upgradeRequest === void 0) {
29
+ if (!context.locals.isUpgradeRequest) {
30
+ Object.assign(context.locals, {
31
+ isUpgradeRequest: false,
32
+ upgradeWebSocket() {
33
+ throw new Error("The request must be an upgrade request to upgrade the connection to a WebSocket.");
34
+ }
35
+ });
36
+ }
37
+ return next();
38
+ }
39
+ let response;
40
+ let error;
41
+ try {
42
+ response = await next();
43
+ } catch (e) {
44
+ error = e;
45
+ }
46
+ if (response) {
47
+ if (response instanceof UpgradeResponse) {
48
+ const standardWebSocket = responseToSocketMap.get(response);
49
+ const [wsServer, req, socket, head] = upgradeRequest;
50
+ wsServer.handleUpgrade(req, socket, head, (ws2) => attach(standardWebSocket, ws2));
51
+ } else {
52
+ const socket = upgradeRequest[2];
53
+ await writeResponseToSocket(socket, response);
54
+ }
55
+ return response;
56
+ }
57
+ await writeResponseToSocket(upgradeRequest[2], new Response(null, { status: 500 }));
58
+ if (error && error instanceof Error) throw error;
59
+ throw new Error("Unknown error", { cause: error });
60
+ };
61
+ const devLocals = {
62
+ isUpgradeRequest: true,
63
+ upgradeWebSocket() {
64
+ const response = newUpgradeResponse();
65
+ const socket = newWebSocket();
66
+ responseToSocketMap.set(response, socket);
67
+ return { socket, response };
68
+ }
69
+ };
70
+ function handleUpgradeRequests(viteDevServer) {
71
+ const astroDevHandler = viteDevServer.middlewares.stack.find((stackItem) => "name" in stackItem.handle && stackItem.handle.name === "astroDevHandler").handle;
72
+ const wsServer = new ws.WebSocketServer({ noServer: true });
73
+ const httpServer = viteDevServer.httpServer;
74
+ httpServer.on("upgrade", (req, socket, head) => {
75
+ if (req.headers["sec-websocket-protocol"] === "vite-hmr") return;
76
+ req[Symbol.for("astro.locals")] = devLocals;
77
+ upgradeRequestStorage.run([wsServer, req, socket, head], astroDevHandler, req, fakeResponse);
78
+ });
79
+ }
80
+ const fakeResponse = {
81
+ setHeader() {
82
+ },
83
+ write() {
84
+ },
85
+ writeHead() {
86
+ },
87
+ end() {
88
+ },
89
+ on() {
90
+ }
9
91
  };
10
92
  export {
93
+ handleUpgradeRequests,
11
94
  onRequest
12
95
  };
@@ -1,5 +1,13 @@
1
+ /**
2
+ * Custom subclass because spec-compliant Response objects can't have a status of 101.
3
+ */
1
4
  export declare class UpgradeResponse extends Response {
2
5
  readonly status = 101;
3
- constructor();
4
6
  }
7
+ /**
8
+ * The "upgrade" event callback doesn't provide a response object.
9
+ * If the userland code decides that protocol should not be upgraded,
10
+ * the rejection response must be manually streamed into the lower
11
+ * level socket.
12
+ */
5
13
  export declare function writeResponseToSocket(socket: import("node:stream").Duplex, response: Response): Promise<void>;
@@ -1,32 +1,16 @@
1
1
  import { pipeline } from "node:stream/promises";
2
2
  class UpgradeResponse extends Response {
3
3
  status = 101;
4
- constructor() {
5
- super(null, {
6
- status: 200,
7
- statusText: "Switching Protocols",
8
- headers: {
9
- "Upgrade": "websocket",
10
- "Connection": "Upgrade"
11
- }
12
- });
13
- Object.defineProperty(this, "status", {
14
- value: 101,
15
- writable: false,
16
- enumerable: true,
17
- configurable: false
18
- });
19
- }
20
4
  }
5
+ const { Headers } = globalThis;
21
6
  async function writeResponseToSocket(socket, response) {
22
- let head = `HTTP/1.1 ${response.status}`;
23
- if (response.statusText) head += ` ${response.statusText}`;
24
- head += `\r
25
- `;
26
- for (const [name, value] of response.headers) {
27
- head += `${name}: ${value}\r
7
+ const { headers, status, statusText } = response;
8
+ let head = `HTTP/1.1 ${status} ${statusText}\r
28
9
  `;
10
+ for (const [header, value] of new Headers(headers).entries()) {
11
+ head += header + ": " + value + "\r\n";
29
12
  }
13
+ socket.on("error", console.error);
30
14
  socket.write(head + "\r\n");
31
15
  if (response.body) {
32
16
  await pipeline(response.clone().body, socket);
@@ -3,6 +3,7 @@ import { NodeApp } from "astro/app/node";
3
3
  import { WebSocket } from "./websocket.js";
4
4
  import { attach } from "./attach.js";
5
5
  import { UpgradeResponse, writeResponseToSocket } from "./response.js";
6
+ import { registerConnection } from "./stats.js";
6
7
  function createWebsocketHandler(app) {
7
8
  const responseToSocketMap = /* @__PURE__ */ new WeakMap();
8
9
  const server = new ws.WebSocketServer({ noServer: true });
@@ -12,16 +13,19 @@ function createWebsocketHandler(app) {
12
13
  locals: {
13
14
  isUpgradeRequest: true,
14
15
  upgradeWebSocket() {
15
- const socket2 = new WebSocket();
16
+ const websocket = new WebSocket();
16
17
  const response2 = new UpgradeResponse();
17
- responseToSocketMap.set(response2, socket2);
18
- return { socket: socket2, response: response2 };
18
+ responseToSocketMap.set(response2, websocket);
19
+ return { socket: websocket, response: response2 };
19
20
  }
20
21
  }
21
22
  });
22
23
  if (response instanceof UpgradeResponse) {
23
24
  const websocket = responseToSocketMap.get(response);
24
- server.handleUpgrade(req, socket, head, (wsSocket) => attach(websocket, wsSocket));
25
+ server.handleUpgrade(req, socket, head, (wsSocket) => {
26
+ registerConnection(websocket, wsSocket);
27
+ attach(websocket, wsSocket);
28
+ });
25
29
  } else {
26
30
  await writeResponseToSocket(socket, response);
27
31
  }
@@ -4,6 +4,7 @@ export declare const WebSocketStats: {
4
4
  getConnectionCount: () => number;
5
5
  getConnectionStats: () => {
6
6
  totalConnections: number;
7
+ totalConnectionsEver: number;
7
8
  };
8
9
  shutdown: () => void;
9
10
  };
@@ -1,19 +1,30 @@
1
1
  class WebSocketStatsManager {
2
2
  connections = /* @__PURE__ */ new Map();
3
3
  connectionCounter = 0;
4
+ lifetimeConnections = 0;
4
5
  registerConnection(socket, wsSocket) {
5
6
  const id = `ws_${++this.connectionCounter}_${Date.now()}`;
6
7
  this.connections.set(id, { socket, wsSocket });
8
+ this.lifetimeConnections++;
9
+ wsSocket.on("close", () => {
10
+ this.connections.delete(id);
11
+ });
7
12
  return id;
8
13
  }
9
14
  getConnectionCount() {
10
15
  return this.connections.size;
11
16
  }
17
+ getConnectionStats() {
18
+ return {
19
+ totalConnections: this.connections.size,
20
+ totalConnectionsEver: this.lifetimeConnections
21
+ };
22
+ }
12
23
  }
13
24
  const statsManager = new WebSocketStatsManager();
14
25
  const WebSocketStats = {
15
26
  getConnectionCount: () => statsManager.getConnectionCount(),
16
- getConnectionStats: () => ({ totalConnections: statsManager.getConnectionCount() }),
27
+ getConnectionStats: () => statsManager.getConnectionStats(),
17
28
  shutdown: () => {
18
29
  }
19
30
  };
@@ -1,9 +1,6 @@
1
- import type * as ws from "ws";
2
1
  type WebSocketInterface = globalThis.WebSocket;
3
- export declare const attacher: {
4
- attach: null | typeof attachImpl;
5
- };
6
2
  export declare class WebSocket extends EventTarget implements WebSocketInterface {
3
+ #private;
7
4
  static readonly CONNECTING: 0;
8
5
  static readonly OPEN: 1;
9
6
  static readonly CLOSING: 2;
@@ -26,7 +23,6 @@ export declare class WebSocket extends EventTarget implements WebSocketInterface
26
23
  set binaryType(value: "arraybuffer" | "blob");
27
24
  send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
28
25
  }
29
- declare function attachImpl(standard: WebSocket, ws: ws.WebSocket): void;
30
26
  export declare class ErrorEvent extends Event {
31
27
  readonly error: Error;
32
28
  readonly message: string;
@@ -38,7 +34,6 @@ export declare class CloseEvent extends Event implements globalThis.CloseEvent {
38
34
  readonly wasClean: boolean;
39
35
  constructor(type: string, eventInitDict: CloseEventInit);
40
36
  }
41
- export declare function attach(standard: WebSocket, ws: ws.WebSocket): void;
42
37
  interface CloseEventInit extends EventInit {
43
38
  code?: number;
44
39
  reason?: string;
@@ -1,57 +1,49 @@
1
- const wsMap = /* @__PURE__ */ new WeakMap();
2
- const attacher = { attach: null };
1
+ import { attacher } from "./attach.js";
3
2
  class WebSocket extends EventTarget {
3
+ // Use private field like the original patch
4
+ #ws;
4
5
  static CONNECTING = 0;
5
6
  static OPEN = 1;
6
7
  static CLOSING = 2;
7
8
  static CLOSED = 3;
8
9
  get url() {
9
- const ws = wsMap.get(this);
10
- return ws?.url ?? "";
10
+ return this.#ws?.url ?? "";
11
11
  }
12
12
  get readyState() {
13
- const ws = wsMap.get(this);
14
- return ws?.readyState ?? this.CONNECTING;
13
+ return this.#ws?.readyState ?? this.CONNECTING;
15
14
  }
16
15
  get bufferedAmount() {
17
- const ws = wsMap.get(this);
18
- return ws?.bufferedAmount ?? 0;
16
+ return this.#ws?.bufferedAmount ?? 0;
19
17
  }
20
- // networking
18
+ // networking event handlers
21
19
  onopen = null;
22
20
  onerror = null;
23
21
  onclose = null;
24
22
  get extensions() {
25
- const ws = wsMap.get(this);
26
- return ws?.extensions ?? "";
23
+ return this.#ws?.extensions ?? "";
27
24
  }
28
25
  get protocol() {
29
- const ws = wsMap.get(this);
30
- return ws?.protocol ?? "";
26
+ return this.#ws?.protocol ?? "";
31
27
  }
32
28
  close() {
33
- const ws = wsMap.get(this);
34
- if (ws) ws.close();
29
+ if (this.#ws) this.#ws.close();
35
30
  else this.addEventListener("open", () => this.close(), { once: true });
36
31
  }
37
32
  // messaging
38
33
  onmessage = null;
39
34
  get binaryType() {
40
- const ws = wsMap.get(this);
41
- return ws?.binaryType ?? "blob";
35
+ return this.#ws?.binaryType ?? "blob";
42
36
  }
43
37
  set binaryType(value) {
44
- const ws = wsMap.get(this);
45
- if (ws) {
46
- Object.assign(ws, { binaryType: value });
38
+ if (this.#ws) {
39
+ this.#ws.binaryType = value;
47
40
  } else {
48
41
  this.addEventListener("open", () => this.binaryType = value, { once: true });
49
42
  }
50
43
  }
51
44
  send(data) {
52
- const ws = wsMap.get(this);
53
- if (data instanceof Blob) data.arrayBuffer().then((buffer) => ws.send(buffer));
54
- else ws.send(data);
45
+ if (data instanceof Blob) data.arrayBuffer().then((buffer) => this.#ws.send(buffer));
46
+ else this.#ws.send(data);
55
47
  }
56
48
  static {
57
49
  Object.assign(this.prototype, {
@@ -62,18 +54,18 @@ class WebSocket extends EventTarget {
62
54
  });
63
55
  Object.freeze(this.prototype);
64
56
  Object.freeze(this);
65
- attacher.attach = attachImpl;
57
+ attacher.attach = (standard, ws) => {
58
+ if (standard.#ws) {
59
+ throw new Error("WebSocket already attached");
60
+ }
61
+ standard.#ws = ws;
62
+ init(standard, ws);
63
+ return standard;
64
+ };
66
65
  }
67
66
  }
68
- function attachImpl(standard, ws) {
69
- if (wsMap.has(standard)) {
70
- throw new Error("WebSocket already attached");
71
- }
72
- wsMap.set(standard, ws);
73
- init(standard, ws);
74
- }
75
67
  function init(standard, ws) {
76
- Object.assign(ws, { binaryType: "blob" });
68
+ ws.binaryType = "blob";
77
69
  if (ws.readyState === ws.OPEN) {
78
70
  const event = new Event("open");
79
71
  standard.onopen?.(event);
@@ -118,13 +110,8 @@ class CloseEvent extends Event {
118
110
  this.wasClean = eventInitDict.wasClean ?? false;
119
111
  }
120
112
  }
121
- function attach(standard, ws) {
122
- return attacher.attach?.(standard, ws);
123
- }
124
113
  export {
125
114
  CloseEvent,
126
115
  ErrorEvent,
127
- WebSocket,
128
- attach,
129
- attacher
116
+ WebSocket
130
117
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zastro-websockets-node",
3
3
  "description": "Deploy your site to a Node.js server with WebSocket support",
4
- "version": "9.3.1-1",
4
+ "version": "9.5.2-2",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "Zach Handley <zach@zachhandley.com>",
@@ -20,19 +20,25 @@
20
20
  "./server.js": "./dist/server.js",
21
21
  "./preview.js": "./dist/preview.js",
22
22
  "./package.json": "./package.json",
23
+ "./stats": "./dist/websocket/stats.js",
23
24
  "./websocket": "./dist/websocket/index.js",
24
- "./stats": "./dist/websocket/stats.js"
25
+ "./websocket/dev-middleware.js": "./dist/websocket/dev-middleware.js",
26
+ "./websocket/websocket.js": "./dist/websocket/websocket.js",
27
+ "./websocket/response.js": "./dist/websocket/response.js",
28
+ "./websocket/serve-websocket.js": "./dist/websocket/serve-websocket.js",
29
+ "./websocket/attach.js": "./dist/websocket/attach.js",
30
+ "./websocket/stats.js": "./dist/websocket/stats.js"
25
31
  },
26
32
  "files": [
27
33
  "dist"
28
34
  ],
29
35
  "dependencies": {
30
- "@astrojs/internal-helpers": "^0.6.1",
31
- "send": "^1.2.0",
36
+ "@astrojs/internal-helpers": "^0.7.5",
37
+ "send": "^1.2.1",
32
38
  "server-destroy": "^1.0.1",
33
39
  "ws": "^8.18.0"
34
40
  },
35
41
  "peerDependencies": {
36
- "astro": "^5.3.0"
42
+ "astro": "^5.14.3"
37
43
  }
38
44
  }