zastro-websockets-node 0.0.0-729d313 → 0.0.0-e6992d0
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 +745 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
# zastro-websockets 🔌
|
|
2
|
+
|
|
3
|
+
Universal WebSocket support for Astro SSR Apps with **pre-patched adapters** - no manual patching required!
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
ZAstroWebsockets is a monorepo containing WebSocket-enabled Astro adapters for Node.js and Cloudflare Workers. Each adapter is distributed as a separate package with WebSocket support built-in, eliminating the need for manual patching.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- ✅ **Pre-patched adapters**: Ships with WebSocket-enabled versions of Astro adapters
|
|
12
|
+
- ✅ **Multi-runtime support**: Node.js and Cloudflare Workers
|
|
13
|
+
- ✅ **Works with current Astro versions** (v4 & v5)
|
|
14
|
+
- ✅ **Unified API**: Same WebSocket API across all runtimes
|
|
15
|
+
- ✅ **TypeScript support**: Full type safety and IntelliSense
|
|
16
|
+
- ✅ **Drop-in replacement**: Simply replace your adapter import
|
|
17
|
+
- ✅ **Monorepo structure**: Separate packages for each runtime
|
|
18
|
+
- ✅ **Auto-versioning**: Synced with upstream Astro submodule
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Install the specific adapter package you need:
|
|
23
|
+
|
|
24
|
+
### For Node.js
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install zastro-websockets-node
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### For Cloudflare Workers
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install zastro-websockets-cloudflare
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Node.js Adapter
|
|
39
|
+
|
|
40
|
+
Replace your existing `@astrojs/node` import with the WebSocket-enabled version:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
// astro.config.mjs
|
|
44
|
+
import { defineConfig } from "astro/config"
|
|
45
|
+
import node from "zastro-websockets-node"
|
|
46
|
+
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
output: "server",
|
|
49
|
+
adapter: node({ mode: "standalone" }),
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Cloudflare Adapter
|
|
54
|
+
|
|
55
|
+
Replace your existing `@astrojs/cloudflare` import with the WebSocket-enabled version:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
// astro.config.mjs
|
|
59
|
+
import { defineConfig } from "astro/config"
|
|
60
|
+
import cloudflare from "zastro-websockets-cloudflare"
|
|
61
|
+
|
|
62
|
+
export default defineConfig({
|
|
63
|
+
output: "server",
|
|
64
|
+
adapter: cloudflare(),
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## WebSocket API
|
|
70
|
+
|
|
71
|
+
The WebSocket API is consistent across all runtimes:
|
|
72
|
+
|
|
73
|
+
### API Routes
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// src/pages/api/ws.ts
|
|
77
|
+
import type { APIRoute } from "astro"
|
|
78
|
+
|
|
79
|
+
export const GET: APIRoute = (ctx) => {
|
|
80
|
+
// Check if this is a WebSocket upgrade request
|
|
81
|
+
if (ctx.locals.isUpgradeRequest) {
|
|
82
|
+
// Upgrade the connection to a WebSocket
|
|
83
|
+
const { response, socket } = ctx.locals.upgradeWebSocket()
|
|
84
|
+
|
|
85
|
+
// Set up your WebSocket handlers
|
|
86
|
+
socket.onopen = () => {
|
|
87
|
+
console.log("WebSocket connection opened")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
socket.onmessage = (event) => {
|
|
91
|
+
console.log("Received:", event.data)
|
|
92
|
+
if (event.data === "ping") {
|
|
93
|
+
socket.send("pong")
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
socket.onclose = () => {
|
|
98
|
+
console.log("WebSocket connection closed")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
socket.onerror = (error) => {
|
|
102
|
+
console.error("WebSocket error:", error)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Return the upgrade response
|
|
106
|
+
return response
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Return error for non-WebSocket requests
|
|
110
|
+
return new Response("Upgrade required", { status: 426 })
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Client-side JavaScript
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<!-- src/pages/index.astro -->
|
|
118
|
+
<script>
|
|
119
|
+
const ws = new WebSocket("ws://localhost:4321/api/ws")
|
|
120
|
+
|
|
121
|
+
ws.onopen = () => {
|
|
122
|
+
console.log("Connected to WebSocket")
|
|
123
|
+
ws.send("Hello, Server!")
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ws.onmessage = (event) => {
|
|
127
|
+
console.log("Received:", event.data)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
ws.onclose = () => {
|
|
131
|
+
console.log("Disconnected from WebSocket")
|
|
132
|
+
}
|
|
133
|
+
</script>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Project Structure
|
|
137
|
+
|
|
138
|
+
This is a monorepo containing:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
zastro-websockets/
|
|
142
|
+
├── packages/
|
|
143
|
+
│ ├── node/ # zastro-websockets-node package
|
|
144
|
+
│ └── cloudflare/ # zastro-websockets-cloudflare package
|
|
145
|
+
├── tests/projects/ # Test projects for both adapters
|
|
146
|
+
├── patches/ # Patch files for upstream Astro adapters
|
|
147
|
+
└── scripts/ # Build and sync scripts
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Available Packages
|
|
151
|
+
|
|
152
|
+
- **`zastro-websockets-node`** - WebSocket-enabled Node.js adapter
|
|
153
|
+
- **`zastro-websockets-cloudflare`** - WebSocket-enabled Cloudflare Workers adapter
|
|
154
|
+
|
|
155
|
+
## TypeScript Support
|
|
156
|
+
|
|
157
|
+
Both packages provide full TypeScript support with proper type definitions:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
// Types are automatically available in your Astro.locals
|
|
161
|
+
declare global {
|
|
162
|
+
namespace App {
|
|
163
|
+
interface Locals {
|
|
164
|
+
isUpgradeRequest: boolean
|
|
165
|
+
upgradeWebSocket(): { socket: WebSocket, response: Response }
|
|
166
|
+
runtime?: any // Runtime-specific context (Cloudflare only)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## How the Dynamic Build System Works
|
|
173
|
+
|
|
174
|
+
### Modern Code-Transformation Approach
|
|
175
|
+
|
|
176
|
+
Instead of maintaining fragile patch files, **ZAstroWebsockets uses a modern dynamic build system** that applies WebSocket modifications as **code transformations** directly to the upstream Astro source code.
|
|
177
|
+
|
|
178
|
+
### Node.js Adapter Architecture
|
|
179
|
+
|
|
180
|
+
The Node.js adapter build system performs **live code modifications** to integrate WebSocket support:
|
|
181
|
+
|
|
182
|
+
#### Build Process Steps
|
|
183
|
+
|
|
184
|
+
1. **Copy Upstream Source**
|
|
185
|
+
- Copies fresh source from `astro-upstream/packages/integrations/node/`
|
|
186
|
+
- Maintains sync with latest Astro releases
|
|
187
|
+
|
|
188
|
+
2. **Apply Code Transformations**
|
|
189
|
+
- **Modifies `serve-app.ts`**: Adds WebSocket upgrade handling
|
|
190
|
+
- **Modifies `standalone.ts`**: Integrates WebSocket server with HTTP server
|
|
191
|
+
- **Modifies `types.ts`**: Exports WebSocket types
|
|
192
|
+
- **Modifies `index.ts`**: Updates package name references
|
|
193
|
+
- **Creates WebSocket Files**: Complete WebSocket implementation
|
|
194
|
+
|
|
195
|
+
3. **WebSocket Server Integration**
|
|
196
|
+
```typescript
|
|
197
|
+
// Dynamically injected into serve-app.ts
|
|
198
|
+
const wsServer = new ws.WebSocketServer({ noServer: true })
|
|
199
|
+
|
|
200
|
+
// Handles HTTP upgrade requests
|
|
201
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
202
|
+
wsServer.handleUpgrade(req, socket, head, (ws) => {
|
|
203
|
+
attach(websocket, ws, metadata)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
4. **Generated WebSocket Files**
|
|
209
|
+
- **`websocket/index.ts`**: Main WebSocket wrapper with browser-compatible API
|
|
210
|
+
- **`websocket/stats.ts`**: Connection tracking and statistics
|
|
211
|
+
- **`websocket/connection-manager.ts`**: Advanced connection lifecycle management
|
|
212
|
+
- **`websocket/attach.ts`**: WebSocket attachment utilities
|
|
213
|
+
- **`websocket/dev-middleware.ts`**: Development server integration
|
|
214
|
+
- **`websocket/serve-websocket.ts`**: Production server integration
|
|
215
|
+
|
|
216
|
+
5. **Package Configuration**
|
|
217
|
+
- **Adds Dependencies**: `ws@^8.18.0` and `@types/ws@^8.5.12`
|
|
218
|
+
- **Adds Exports**: `./websocket` and `./stats` exports
|
|
219
|
+
- **Resolves Workspace Deps**: Converts `@astrojs/internal-helpers@workspace:*` to actual versions
|
|
220
|
+
|
|
221
|
+
### Cloudflare Adapter Architecture
|
|
222
|
+
|
|
223
|
+
The Cloudflare adapter uses the same **dynamic transformation approach**:
|
|
224
|
+
|
|
225
|
+
#### Build Process Steps
|
|
226
|
+
|
|
227
|
+
1. **Copy Upstream Source**
|
|
228
|
+
- Copies fresh source from `astro-upstream/packages/integrations/cloudflare/`
|
|
229
|
+
- Maintains compatibility with Cloudflare Workers runtime
|
|
230
|
+
|
|
231
|
+
2. **Apply Code Transformations**
|
|
232
|
+
- **Modifies `index.ts`**: Updates package name references
|
|
233
|
+
- **Creates WebSocket Files**: Cloudflare Workers-specific implementation
|
|
234
|
+
|
|
235
|
+
3. **WebSocket Implementation**
|
|
236
|
+
```typescript
|
|
237
|
+
// Dynamically generated in websocket/index.ts
|
|
238
|
+
export function upgradeWebSocket(): WebSocketUpgrade {
|
|
239
|
+
// Uses Cloudflare's WebSocketPair API
|
|
240
|
+
const { 0: client, 1: server } = new WebSocketPair()
|
|
241
|
+
|
|
242
|
+
// Create upgrade response
|
|
243
|
+
const response = createWebSocketResponse(client)
|
|
244
|
+
|
|
245
|
+
// Return wrapped server socket
|
|
246
|
+
return { socket: new CloudflareWebSocket(server), response }
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
4. **Generated WebSocket Files**
|
|
251
|
+
- **`websocket/index.ts`**: Main WebSocket implementation for Cloudflare Workers
|
|
252
|
+
- **`websocket/response.ts`**: HTTP upgrade response handling
|
|
253
|
+
- **`websocket/websocket.ts`**: WebSocket wrapper classes
|
|
254
|
+
- **`websocket/dev-middleware.ts`**: Development server simulation
|
|
255
|
+
|
|
256
|
+
5. **WebSocket Wrapper Classes**
|
|
257
|
+
- **`CloudflareWebSocket`**: Production wrapper for native WebSocket
|
|
258
|
+
- **`DevWebSocket`**: Development simulation with same API
|
|
259
|
+
- **`UpgradeResponse`**: Proper HTTP 101 upgrade response
|
|
260
|
+
|
|
261
|
+
### Build System Architecture
|
|
262
|
+
|
|
263
|
+
The build system uses **3 main scripts**:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// scripts/dynamic-build.ts - Main orchestrator
|
|
267
|
+
export function buildAll() {
|
|
268
|
+
// 1. Clean and prepare upstream
|
|
269
|
+
// 2. Install upstream dependencies
|
|
270
|
+
// 3. Build Node.js adapter
|
|
271
|
+
// 4. Build Cloudflare adapter
|
|
272
|
+
// 5. Copy README.md to packages
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// scripts/dynamic-build-node.ts - Node.js specific
|
|
276
|
+
export function buildNodeAdapter() {
|
|
277
|
+
// 1. Copy upstream source
|
|
278
|
+
// 2. Apply all WebSocket modifications
|
|
279
|
+
// 3. Update package.json and resolve dependencies
|
|
280
|
+
// 4. Build TypeScript locally
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// scripts/dynamic-build-cloudflare.ts - Cloudflare specific
|
|
284
|
+
export function buildCloudflareAdapter() {
|
|
285
|
+
// 1. Copy upstream source
|
|
286
|
+
// 2. Apply WebSocket modifications
|
|
287
|
+
// 3. Update package.json
|
|
288
|
+
// 4. Build TypeScript locally
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Automatic Version Management
|
|
293
|
+
|
|
294
|
+
The build system uses **adapter-specific versioning**:
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
// GitHub Actions workflow
|
|
298
|
+
const nodeAdapterVersion = require('./packages/integrations/node/package.json').version
|
|
299
|
+
const cloudflareAdapterVersion = require('./packages/integrations/cloudflare/package.json').version
|
|
300
|
+
const ourCommit = process.env.GITHUB_SHA.substring(0, 7)
|
|
301
|
+
|
|
302
|
+
// Version format: 8.3.4-abc1234 (adapter version + our commit)
|
|
303
|
+
const finalVersion = `${nodeAdapterVersion}-${ourCommit}`
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Key Advantages of Dynamic Build System
|
|
307
|
+
|
|
308
|
+
| Aspect | Old Patch System | New Dynamic System |
|
|
309
|
+
|--------|------------------|-------------------|
|
|
310
|
+
| **Reliability** | Patches break on updates | Code transformations adapt |
|
|
311
|
+
| **Maintenance** | Manual patch updates | Automated code generation |
|
|
312
|
+
| **Debugging** | Patch application errors | Clear TypeScript errors |
|
|
313
|
+
| **Dependency Management** | Workspace conflicts | Isolated local dependencies |
|
|
314
|
+
| **Build Speed** | Slow patch application | Fast code transformations |
|
|
315
|
+
| **Flexibility** | Limited to patch changes | Full code control |
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## Migration from Official Adapters
|
|
319
|
+
|
|
320
|
+
### From @astrojs/node
|
|
321
|
+
|
|
322
|
+
```diff
|
|
323
|
+
- import node from "@astrojs/node"
|
|
324
|
+
+ import node from "zastro-websockets-node"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### From @astrojs/cloudflare
|
|
328
|
+
|
|
329
|
+
```diff
|
|
330
|
+
- import cloudflare from "@astrojs/cloudflare"
|
|
331
|
+
+ import cloudflare from "zastro-websockets-cloudflare"
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
That's it! No other changes needed.
|
|
335
|
+
|
|
336
|
+
## Comparison with Manual Patching
|
|
337
|
+
|
|
338
|
+
| Feature | Manual Patching | zastro-websockets |
|
|
339
|
+
|---------|----------------|-------------------|
|
|
340
|
+
| Setup complexity | High (manual patch application) | Low (just change import) |
|
|
341
|
+
| Reliability | Depends on patch compatibility | High (pre-tested combinations) |
|
|
342
|
+
| Updates | Manual re-patching required | Automatic with package updates |
|
|
343
|
+
| Maintenance | User responsibility | Package maintainer responsibility |
|
|
344
|
+
| Risk | Patches might break on updates | Version-locked compatibility |
|
|
345
|
+
| Connection Management | Manual implementation | Built-in tracking and cleanup |
|
|
346
|
+
|
|
347
|
+
## Available Adapters
|
|
348
|
+
|
|
349
|
+
- ✅ **Node.js** (`zastro-websockets-node`) - Production ready
|
|
350
|
+
- ✅ **Cloudflare Workers** (`zastro-websockets-cloudflare`) - Production ready
|
|
351
|
+
|
|
352
|
+
> **Note**: The Deno adapter is no longer supported as it has been moved to the Deno organization. Please use the [official Deno adapter](https://github.com/denoland/deno-astro-adapter) for Deno deployments.
|
|
353
|
+
|
|
354
|
+
## Troubleshooting
|
|
355
|
+
|
|
356
|
+
### WebSocket connection fails
|
|
357
|
+
- Ensure you're using the correct adapter package (`zastro-websockets-node` or `zastro-websockets-cloudflare`)
|
|
358
|
+
- Check that your hosting environment supports WebSockets
|
|
359
|
+
- Verify the WebSocket URL matches your deployment
|
|
360
|
+
|
|
361
|
+
### TypeScript errors
|
|
362
|
+
- Make sure to import the adapter from the correct package name
|
|
363
|
+
- Update your `tsconfig.json` to include the package types
|
|
364
|
+
- Restart your TypeScript server after installation
|
|
365
|
+
|
|
366
|
+
### Development vs Production
|
|
367
|
+
- The adapters work in both development and production
|
|
368
|
+
- Additional logging is available in development mode
|
|
369
|
+
- Make sure to set `output: "server"` in your Astro config
|
|
370
|
+
|
|
371
|
+
### Package Issues
|
|
372
|
+
- If you're upgrading from the old single package, uninstall `zastro-websockets` first
|
|
373
|
+
- Install the specific adapter package you need (`zastro-websockets-node` or `zastro-websockets-cloudflare`)
|
|
374
|
+
- Update your import statements to use the new package names
|
|
375
|
+
|
|
376
|
+
## Advanced Configuration
|
|
377
|
+
|
|
378
|
+
### Runtime-Specific Features
|
|
379
|
+
|
|
380
|
+
#### Cloudflare Workers
|
|
381
|
+
|
|
382
|
+
When using the Cloudflare adapter, you get access to the runtime context:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
export const GET: APIRoute = (ctx) => {
|
|
386
|
+
if (ctx.locals.isUpgradeRequest) {
|
|
387
|
+
const { response, socket } = ctx.locals.upgradeWebSocket()
|
|
388
|
+
|
|
389
|
+
// Access Cloudflare runtime features
|
|
390
|
+
const env = ctx.locals.runtime?.env
|
|
391
|
+
const cf = ctx.locals.runtime?.cf
|
|
392
|
+
|
|
393
|
+
// Use Cloudflare-specific features
|
|
394
|
+
const kv = env.MY_KV_NAMESPACE
|
|
395
|
+
const country = cf.country
|
|
396
|
+
|
|
397
|
+
return response
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return new Response("Upgrade required", { status: 426 })
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### Node.js
|
|
405
|
+
|
|
406
|
+
The Node.js adapter provides standard WebSocket functionality with full Node.js compatibility.
|
|
407
|
+
|
|
408
|
+
### Advanced Connection Management (Node.js)
|
|
409
|
+
|
|
410
|
+
The Node.js adapter includes a powerful ConnectionManager for production-grade WebSocket applications:
|
|
411
|
+
|
|
412
|
+
#### Basic Usage
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import { ConnectionManagerAPI } from 'zastro-websockets-node/connection-manager';
|
|
416
|
+
|
|
417
|
+
// Get connection statistics
|
|
418
|
+
const stats = ConnectionManagerAPI.getStats();
|
|
419
|
+
console.log(`Active connections: ${stats.totalManagedConnections}`);
|
|
420
|
+
|
|
421
|
+
// Perform health checks
|
|
422
|
+
const healthResults = await ConnectionManagerAPI.healthCheck();
|
|
423
|
+
|
|
424
|
+
// Close connections by criteria
|
|
425
|
+
const closedCount = ConnectionManagerAPI.closeConnections({
|
|
426
|
+
idleMoreThan: 300000, // Close connections idle for more than 5 minutes
|
|
427
|
+
tags: ['temporary'] // Close connections tagged as temporary
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### Advanced Configuration
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
import { getConnectionManager } from 'zastro-websockets-node/connection-manager';
|
|
435
|
+
|
|
436
|
+
const manager = getConnectionManager({
|
|
437
|
+
maxConnections: 1000, // Global connection limit
|
|
438
|
+
maxConnectionsPerIP: 10, // Per-IP connection limit
|
|
439
|
+
idleTimeout: 300000, // 5 minutes idle timeout
|
|
440
|
+
rateLimitWindow: 60000, // 1 minute rate limit window
|
|
441
|
+
rateLimitMaxConnections: 5, // Max 5 connections per IP per minute
|
|
442
|
+
enableHealthMonitoring: true, // Enable automatic health checks
|
|
443
|
+
customCleanupPolicy: (connection) => {
|
|
444
|
+
// Custom logic for connection cleanup
|
|
445
|
+
return connection.tags.has('temporary') && connection.age > 300000;
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Event listeners for monitoring
|
|
450
|
+
manager.on('connection:added', (connection) => {
|
|
451
|
+
console.log(`New connection: ${connection.id}`);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
manager.on('pool:full', (rejected) => {
|
|
455
|
+
console.warn(`Connection pool full, rejected: ${rejected.ip}`);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
manager.on('ratelimit:exceeded', (ip, attempts) => {
|
|
459
|
+
console.warn(`Rate limit exceeded for ${ip}`);
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### Connection Tagging and Metadata
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
// In your WebSocket route
|
|
467
|
+
socket.addEventListener('open', () => {
|
|
468
|
+
const connectionId = getConnectionId(socket);
|
|
469
|
+
if (connectionId) {
|
|
470
|
+
// Add tags for grouping
|
|
471
|
+
manager.addConnectionTag(connectionId, 'user-session');
|
|
472
|
+
manager.addConnectionTag(connectionId, 'real-time-updates');
|
|
473
|
+
|
|
474
|
+
// Store custom metadata
|
|
475
|
+
manager.setConnectionData(connectionId, 'userId', user.id);
|
|
476
|
+
manager.setConnectionData(connectionId, 'sessionStart', Date.now());
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Later, find connections by criteria
|
|
481
|
+
const userSessions = manager.getConnectionsByTag('user-session');
|
|
482
|
+
const temporaryConnections = manager.getConnectionsByTag('temporary');
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
#### Health Monitoring
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
// Manual health check
|
|
489
|
+
const result = await manager.performHealthCheck(connectionId);
|
|
490
|
+
if (!result.healthy) {
|
|
491
|
+
console.warn(`Connection ${connectionId} is unhealthy: ${result.error}`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Automatic background health monitoring
|
|
495
|
+
manager.on('connection:health', (connectionId, result) => {
|
|
496
|
+
if (!result.healthy) {
|
|
497
|
+
console.warn(`Health check failed for ${connectionId}: ${result.error}`);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### Graceful Shutdown
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
// Graceful shutdown with timeout
|
|
506
|
+
await ConnectionManagerAPI.shutdown({
|
|
507
|
+
timeout: 10000, // 10 second timeout
|
|
508
|
+
closeCode: 1001, // WebSocket close code
|
|
509
|
+
closeReason: 'Server shutting down'
|
|
510
|
+
});
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Examples
|
|
514
|
+
|
|
515
|
+
Check out the test projects for complete working examples:
|
|
516
|
+
- `/tests/projects/node` - Node.js WebSocket implementation
|
|
517
|
+
- `/tests/projects/cloudflare` - Cloudflare Workers WebSocket implementation
|
|
518
|
+
- `/example-usage.md` - Detailed usage guide with examples
|
|
519
|
+
- `/example-websocket.ts` - Basic WebSocket server example
|
|
520
|
+
- `/example-cloudflare-websocket.ts` - Cloudflare-specific example
|
|
521
|
+
- `/example-connection-management.ts` - Basic connection tracking and cleanup
|
|
522
|
+
- `/example-advanced-connection-management.ts` - Advanced connection management with pooling, rate limiting, and health monitoring
|
|
523
|
+
|
|
524
|
+
## Contributing
|
|
525
|
+
|
|
526
|
+
Contributions are welcome! Please:
|
|
527
|
+
1. Fork the repository
|
|
528
|
+
2. Create your feature branch
|
|
529
|
+
3. Add tests for your changes
|
|
530
|
+
4. Update documentation
|
|
531
|
+
5. Submit a pull request
|
|
532
|
+
|
|
533
|
+
### Development Setup
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
git clone https://github.com/zachhandley/zastro-websockets.git
|
|
537
|
+
cd zastro-websockets
|
|
538
|
+
git submodule update --init --recursive
|
|
539
|
+
pnpm install
|
|
540
|
+
pnpm run build
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## Development Guide
|
|
544
|
+
|
|
545
|
+
### How the Build System Works
|
|
546
|
+
|
|
547
|
+
This project uses a **dynamic build system** that automatically applies WebSocket patches to upstream Astro adapters. Here's how it works:
|
|
548
|
+
|
|
549
|
+
#### 1. Project Structure
|
|
550
|
+
```
|
|
551
|
+
zastro-websockets/
|
|
552
|
+
├── astro-upstream/ # Git submodule of official Astro repo
|
|
553
|
+
├── packages/
|
|
554
|
+
│ ├── node/ # Built Node.js adapter (generated)
|
|
555
|
+
│ │ ├── dist/ # Built TypeScript files
|
|
556
|
+
│ │ ├── src/ # Copied & modified upstream source
|
|
557
|
+
│ │ └── package.json # Modified package.json
|
|
558
|
+
│ └── cloudflare/ # Built Cloudflare adapter (generated)
|
|
559
|
+
│ ├── dist/ # Built TypeScript files
|
|
560
|
+
│ ├── src/ # Copied & modified upstream source
|
|
561
|
+
│ └── package.json # Modified package.json
|
|
562
|
+
├── patches/ # Legacy patch files (reference only)
|
|
563
|
+
├── scripts/ # Build automation scripts
|
|
564
|
+
│ ├── dynamic-build.ts # Main build orchestrator
|
|
565
|
+
│ ├── dynamic-build-node.ts # Node.js adapter build script
|
|
566
|
+
│ ├── dynamic-build-cloudflare.ts # Cloudflare adapter build script
|
|
567
|
+
│ └── reset-submodule.ts # Submodule management
|
|
568
|
+
└── tests/projects/ # Test projects for both adapters
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
#### 2. Build Process Overview
|
|
572
|
+
|
|
573
|
+
The build system uses a **6-step process** for each adapter:
|
|
574
|
+
|
|
575
|
+
1. **Copy Upstream Source** → Copy source files from `astro-upstream/packages/integrations/[adapter]` to `packages/[adapter]/`
|
|
576
|
+
2. **Apply Code Transformations** → Apply all WebSocket modifications as live code transformations
|
|
577
|
+
3. **Update Package.json** → Change package name, add WebSocket dependencies, resolve workspace dependencies
|
|
578
|
+
4. **Install Dependencies** → Install dependencies in upstream workspace (with `--no-frozen-lockfile`)
|
|
579
|
+
5. **Build Upstream** → Compile TypeScript in upstream workspace
|
|
580
|
+
6. **Copy Built Files** → Copy compiled `dist/` and modified `package.json` to final packages + README.md
|
|
581
|
+
|
|
582
|
+
#### 3. Key Build Scripts
|
|
583
|
+
|
|
584
|
+
**Main Build Script: `scripts/dynamic-build.ts`**
|
|
585
|
+
- Orchestrates the entire build process
|
|
586
|
+
- Cleans upstream and installs dependencies
|
|
587
|
+
- Runs both Node.js and Cloudflare builds sequentially
|
|
588
|
+
|
|
589
|
+
**Node.js Build: `scripts/dynamic-build-node.ts`**
|
|
590
|
+
- Copies upstream Node.js adapter source
|
|
591
|
+
- Applies all WebSocket modifications as code transformations
|
|
592
|
+
- Creates WebSocket files: `websocket/`, `middleware/`
|
|
593
|
+
- Adds `ws` dependency and WebSocket exports (`./websocket`, `./stats`)
|
|
594
|
+
- Resolves workspace dependencies to actual versions
|
|
595
|
+
- Builds in upstream workspace, then copies to final package
|
|
596
|
+
|
|
597
|
+
**Cloudflare Build: `scripts/dynamic-build-cloudflare.ts`**
|
|
598
|
+
- Copies upstream Cloudflare adapter source
|
|
599
|
+
- Applies WebSocket modifications for Cloudflare Workers
|
|
600
|
+
- Creates WebSocket files and entrypoint modifications
|
|
601
|
+
- Adds WebSocket export to package.json
|
|
602
|
+
- Resolves workspace dependencies to actual versions
|
|
603
|
+
- Builds in upstream workspace, then copies to final package
|
|
604
|
+
|
|
605
|
+
#### 4. Available Scripts
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
# Full build process (recommended)
|
|
609
|
+
pnpm run build
|
|
610
|
+
|
|
611
|
+
# Test the Node.js adapter
|
|
612
|
+
pnpm run test:node
|
|
613
|
+
|
|
614
|
+
# Test the Cloudflare adapter
|
|
615
|
+
pnpm run test:cloudflare
|
|
616
|
+
|
|
617
|
+
# Test both adapters
|
|
618
|
+
pnpm run test
|
|
619
|
+
|
|
620
|
+
# Reset the astro-upstream submodule to latest Astro tag
|
|
621
|
+
pnpm run reset
|
|
622
|
+
|
|
623
|
+
# Publish packages to npm (CI only)
|
|
624
|
+
pnpm run publish:packages
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### 5. Automated CI/CD Pipeline
|
|
628
|
+
|
|
629
|
+
The project uses **GitHub Actions** for automated building and publishing:
|
|
630
|
+
|
|
631
|
+
**Workflow: `.github/workflows/auto-publish.yml`**
|
|
632
|
+
- **Triggers**: Push to main, daily at 2 AM UTC, or manual dispatch
|
|
633
|
+
- **Process**:
|
|
634
|
+
1. Checks out latest Astro tag from submodule
|
|
635
|
+
2. Installs dependencies with workspace filtering
|
|
636
|
+
3. Builds both adapters with dynamic build system
|
|
637
|
+
4. Tests both adapters
|
|
638
|
+
5. Updates package versions to `{adapter-version}-{commit-hash}`
|
|
639
|
+
6. Publishes to npm with automation tokens
|
|
640
|
+
7. Commits built packages to repo
|
|
641
|
+
8. Creates GitHub release with version tag
|
|
642
|
+
|
|
643
|
+
**Version Strategy**:
|
|
644
|
+
- Uses **adapter-specific versions** (not Astro core version)
|
|
645
|
+
- Format: `8.3.4-abc1234` (Node adapter v8.3.4 + commit abc1234)
|
|
646
|
+
- Allows patch releases for fixes without waiting for Astro updates
|
|
647
|
+
|
|
648
|
+
#### 6. Key Features of Build System
|
|
649
|
+
|
|
650
|
+
**Workspace Dependency Resolution**:
|
|
651
|
+
- Automatically converts `@astrojs/internal-helpers@workspace:*` to actual versions
|
|
652
|
+
- Maps packages to correct workspace locations
|
|
653
|
+
- Handles complex monorepo dependency resolution
|
|
654
|
+
|
|
655
|
+
**README.md Distribution**:
|
|
656
|
+
- Automatically copies root README.md to each package
|
|
657
|
+
- Ensures npm packages have proper documentation
|
|
658
|
+
|
|
659
|
+
**Authentication & Publishing**:
|
|
660
|
+
- Uses npm automation tokens for 2FA bypass
|
|
661
|
+
- Creates `.npmrc` files in package directories
|
|
662
|
+
- Publishes to npm with proper access controls
|
|
663
|
+
|
|
664
|
+
#### 7. Development Workflow
|
|
665
|
+
|
|
666
|
+
1. **Make Changes to Build Scripts**
|
|
667
|
+
- Edit `scripts/dynamic-build-node.ts` for Node.js changes
|
|
668
|
+
- Edit `scripts/dynamic-build-cloudflare.ts` for Cloudflare changes
|
|
669
|
+
|
|
670
|
+
2. **Test Your Changes**
|
|
671
|
+
```bash
|
|
672
|
+
pnpm run build
|
|
673
|
+
pnpm run test
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
3. **Debug Build Issues**
|
|
677
|
+
- Check `packages/node/` and `packages/cloudflare/` for generated files
|
|
678
|
+
- Review TypeScript errors in build output
|
|
679
|
+
- Verify WebSocket files are created correctly
|
|
680
|
+
- Check workspace dependency resolution
|
|
681
|
+
|
|
682
|
+
4. **Update Tests**
|
|
683
|
+
- Test projects are in `tests/projects/`
|
|
684
|
+
- Update test projects to match API changes
|
|
685
|
+
|
|
686
|
+
#### 8. Key Differences from Patch-Based Approach
|
|
687
|
+
|
|
688
|
+
| Aspect | Old Patch System | New Dynamic System |
|
|
689
|
+
|--------|------------------|-------------------|
|
|
690
|
+
| **Modification Method** | Git patches | Code transformations |
|
|
691
|
+
| **Build Location** | Upstream directory | Upstream → Local packages |
|
|
692
|
+
| **Dependency Management** | Workspace conflicts | Automated workspace resolution |
|
|
693
|
+
| **TypeScript Compilation** | Upstream tsconfig | Upstream build → Copy dist |
|
|
694
|
+
| **Maintenance** | Manual patch updates | Automated code generation |
|
|
695
|
+
| **Debugging** | Patch application errors | Clear TypeScript errors |
|
|
696
|
+
| **CI/CD** | Manual publishing | Automated GitHub Actions |
|
|
697
|
+
|
|
698
|
+
#### 9. Adding New Features
|
|
699
|
+
|
|
700
|
+
To add new WebSocket features:
|
|
701
|
+
|
|
702
|
+
1. **Update the build scripts** to include your new files/modifications
|
|
703
|
+
2. **Add the files to the appropriate `createWebSocketFiles()` function**
|
|
704
|
+
3. **Update package.json exports** if needed in `updateUpstreamPackageJson()`
|
|
705
|
+
4. **Test the build process** with `pnpm run build`
|
|
706
|
+
5. **Update the test projects** to demonstrate the new features
|
|
707
|
+
|
|
708
|
+
#### 10. Troubleshooting Development Issues
|
|
709
|
+
|
|
710
|
+
**Build Fails with Workspace Dependency Errors:**
|
|
711
|
+
- Check that workspace dependency mapping is correct
|
|
712
|
+
- Verify that upstream packages exist at expected paths
|
|
713
|
+
- Ensure `--no-frozen-lockfile` is used in CI
|
|
714
|
+
|
|
715
|
+
**Missing WebSocket Files:**
|
|
716
|
+
- Check that `createWebSocketFiles()` creates all necessary files
|
|
717
|
+
- Verify file paths and exports are correct
|
|
718
|
+
- Ensure TypeScript compilation includes all source files
|
|
719
|
+
|
|
720
|
+
**Publishing Fails with ENEEDAUTH:**
|
|
721
|
+
- Verify npm automation token is stored as repository secret
|
|
722
|
+
- Check that `.npmrc` files are created in package directories
|
|
723
|
+
- Ensure registry-url is set correctly in GitHub Actions
|
|
724
|
+
|
|
725
|
+
**Version Mismatch Issues:**
|
|
726
|
+
- Verify that adapter versions are read from correct package.json paths
|
|
727
|
+
- Check that version update logic handles different adapter versions
|
|
728
|
+
- Ensure commit hash is properly extracted
|
|
729
|
+
|
|
730
|
+
This dynamic build system eliminates the need for manual patch management while providing automated CI/CD and reliable publishing to npm.
|
|
731
|
+
|
|
732
|
+
## License
|
|
733
|
+
|
|
734
|
+
MIT - see LICENSE file for details.
|
|
735
|
+
|
|
736
|
+
## Credits
|
|
737
|
+
|
|
738
|
+
This package is inspired by:
|
|
739
|
+
- [gratelets](https://github.com/gratelets/gratelets) - For the pre-patched adapter approach
|
|
740
|
+
- [astro-node-websocket](https://github.com/lilnasy/gratelets/tree/main/packages/node-websocket) - Original WebSocket integration concept
|
|
741
|
+
- The Astro team for the excellent framework and adapter architecture
|
|
742
|
+
|
|
743
|
+
## Changelog
|
|
744
|
+
|
|
745
|
+
See [CHANGELOG.md](./CHANGELOG.md) for version history and updates.
|
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": "0.0.0-
|
|
4
|
+
"version": "0.0.0-e6992d0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"author": "Zach Handley <zach@zachhandley.com>",
|