freertc 0.1.0

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 ADDED
@@ -0,0 +1,246 @@
1
+ # freertc Cloudflare Worker (WebSocket + D1)
2
+
3
+ This project provides a Cloudflare Worker signaling relay for WebRTC peers using the [Peer Signaling Protocol (PSP)](https://github.com/draeder/Peer-Signaling-Protocol-Specification) envelope shape.
4
+
5
+ ## Install from npm
6
+
7
+ Local project install:
8
+
9
+ ```bash
10
+ npm install freertc
11
+ ```
12
+
13
+ Global install:
14
+
15
+ ```bash
16
+ npm install -g freertc
17
+ ```
18
+
19
+ When you run the CLI from your project directory, `freertc` copies the required worker files into that directory on first run:
20
+
21
+ - `src/index.js`
22
+ - `public/index.html`
23
+ - `public/app.js`
24
+ - `scripts/d1-schema.sql`
25
+ - `wrangler.template.jsonc`
26
+ - `wrangler.workers-dev.jsonc`
27
+
28
+ ## What this worker does
29
+
30
+ - Accepts WebSocket client connections at `/ws`.
31
+ - Validates [PSP](https://github.com/draeder/Peer-Signaling-Protocol-Specification) message envelopes (`psp_version`, `type`, `network`, `from`, `message_id`, `timestamp`).
32
+ - Supports discovery, negotiation, control, and extension message types.
33
+ - Stores peer announcements in Cloudflare D1 (`psp_announcements`).
34
+ - Stores directed signaling messages in Cloudflare D1 (`psp_relay`).
35
+ - Exposes a simple relay registry at `/api/v1/relays` when D1 is configured.
36
+ - Delivers queued relay messages when peers reconnect.
37
+ - Serves the browser demo from `public/`.
38
+
39
+ ## Runtime scope
40
+
41
+ - The checked-in Cloudflare Worker runtime is `src/index.js` with Cloudflare D1 (`DB` binding).
42
+ - The Rust/WASM worker lives in `src/lib.rs` and is optional; the default template now uses the JS worker path.
43
+ - The built-in browser demo served by the Worker is `public/index.html` + `public/app.js`.
44
+ - `src/kv.js` and `demo/src/*` are legacy/experimental code paths and are not used by `wrangler dev` or `wrangler deploy` in the current setup.
45
+
46
+ ## Supported message types
47
+
48
+ - Discovery: `announce`, `withdraw`, `discover`, `peer_list`, `redirect`
49
+ - Negotiation: `connect_request`, `connect_accept`, `connect_reject`, `offer`, `answer`, `ice_candidate`, `ice_end`, `renegotiate`
50
+ - Control: `ping`, `pong`, `bye`, `error`, `ack`
51
+ - Extension: `ext`
52
+
53
+ ## Wrangler install wizard (recommended)
54
+
55
+ Use the interactive wizard from the project directory where you want the worker files and Wrangler config to live:
56
+
57
+ ```bash
58
+ npx freertc wizard
59
+ ```
60
+
61
+ The default command runs full setup mode (`both`):
62
+
63
+ ```bash
64
+ npx freertc
65
+ ```
66
+
67
+ You can also preselect full setup mode explicitly:
68
+
69
+ ```bash
70
+ npx freertc setup
71
+ ```
72
+
73
+ Global install flow:
74
+
75
+ ```bash
76
+ freertc wizard
77
+ freertc
78
+ ```
79
+
80
+ After install, freertc prints a quick-start reminder with the exact next command.
81
+
82
+ The wizard can:
83
+
84
+ - Copy the worker runtime files into your current project when they are missing.
85
+ - Verify Wrangler CLI.
86
+ - Create `wrangler.jsonc` from `wrangler.template.jsonc` if needed.
87
+ - Set Worker name automatically to `freertc-<your-domain>` when a domain is provided.
88
+ - Initialize local D1 schema for `wrangler dev`.
89
+ - Initialize remote D1 schema for deploy.
90
+ - Detect Rust build configs and install `worker-build`/WASM target automatically when required.
91
+ - Check existing Wrangler auth and only run `wrangler login` when needed.
92
+ - Optionally run `npm run dev:cf` and `npm run deploy`.
93
+
94
+ ## Manual setup
95
+
96
+ ### 1. Install dependencies
97
+
98
+ ```bash
99
+ npm install
100
+ ```
101
+
102
+ If you installed the npm package instead of cloning the repo, use `npx freertc wizard` instead of the repository scripts below.
103
+
104
+ ### 2. Configure Wrangler
105
+
106
+ - If needed, copy `wrangler.template.jsonc` to `wrangler.jsonc`.
107
+ - Set your Worker name, route(s), and D1 database values.
108
+ - Ensure `d1_databases[0].binding` is `DB`.
109
+
110
+ ### 3. Initialize D1 schema
111
+
112
+ Local (for `wrangler dev`):
113
+
114
+ ```bash
115
+ npm run d1:init:local
116
+ ```
117
+
118
+ Remote (for production):
119
+
120
+ ```bash
121
+ npm run d1:init:remote
122
+ ```
123
+
124
+ If your database name is not `freertc-signal`, use Wrangler directly:
125
+
126
+ ```bash
127
+ wrangler d1 execute <your-db-name> --local --file scripts/d1-schema.sql
128
+ wrangler d1 execute <your-db-name> --remote --file scripts/d1-schema.sql
129
+ ```
130
+
131
+ ## Local development
132
+
133
+ ```bash
134
+ npm run dev
135
+ ```
136
+
137
+ `npm run dev` now runs the non-Cloudflare local runtime (plain Node.js + WebSocket).
138
+
139
+ Cloudflare/Wrangler runtime:
140
+
141
+ ```bash
142
+ npm run dev:cf
143
+ ```
144
+
145
+ Shortcut alias (same behavior):
146
+
147
+ ```bash
148
+ npm run dev:local
149
+ ```
150
+
151
+ You can also choose host/port:
152
+
153
+ ```bash
154
+ HOST=127.0.0.1 PORT=8788 npm run dev:node
155
+ ```
156
+
157
+ `npm run dev:cf` checks local Rust Worker prerequisites automatically:
158
+
159
+ - Uses `wrangler.workers-dev.jsonc` automatically when `wrangler.jsonc` is not present.
160
+ - Only installs `worker-build` and the WebAssembly Rust target when the selected Wrangler config uses a `worker-build` command.
161
+ - The checked-in `wrangler.workers-dev.jsonc` now points to `src/index.js`, so standard demo runs do not require Rust/WASM setup.
162
+
163
+ Endpoints:
164
+
165
+ - WebSocket: `ws://127.0.0.1:8788/ws` (`npm run dev`)
166
+ - Health: `http://127.0.0.1:8788/health` (`npm run dev`)
167
+ - Demo UI: `http://127.0.0.1:8788/` (`npm run dev`)
168
+
169
+ Cloudflare/Wrangler endpoints (default):
170
+
171
+ - WebSocket: `ws://127.0.0.1:8787/ws` (`npm run dev:cf`)
172
+ - Health: `http://127.0.0.1:8787/health` (`npm run dev:cf`)
173
+ - Demo UI: `http://127.0.0.1:8787/` (`npm run dev:cf`)
174
+ - Relay registry: `http://127.0.0.1:8787/api/v1/relays` (`GET`/`POST`, when D1 is configured)
175
+
176
+ ## Deploy
177
+
178
+ ```bash
179
+ npx freertc deploy
180
+ ```
181
+
182
+ If you installed freertc globally:
183
+
184
+ ```bash
185
+ freertc deploy
186
+ ```
187
+
188
+ Repository-only scripts:
189
+
190
+ - `npm run build` builds the Rust/WASM worker via `worker-build --release`.
191
+ - `npm run deploy:raw` deploys without `--env production`.
192
+ - `npm run check` runs `cargo check --target wasm32-unknown-unknown` for the Rust worker path.
193
+ - `npm run dev` runs the standalone local relay (non-Cloudflare).
194
+ - `npm run dev:cf` runs Wrangler/Cloudflare local dev.
195
+ - `npm run dev:node` runs a standalone local relay without Cloudflare/Wrangler.
196
+ - `npm run dev:local` is a no-env shortcut for the standalone local relay.
197
+
198
+ ## Troubleshooting custom domain deploys
199
+
200
+ If your custom domain returns:
201
+
202
+ ```json
203
+ {"error":"API key is missing"}
204
+ ```
205
+
206
+ that error is usually from Cloudflare routing/auth config, not from this Worker runtime.
207
+
208
+ Quick checks:
209
+
210
+ 1. Compare `https://<workers-subdomain>/health` and `https://<custom-domain>/health`.
211
+ 2. Confirm your route/custom domain points to this Worker.
212
+ 3. Review Cloudflare Access/API Shield/WAF rules on the custom hostname.
213
+ 4. If deploying with `--env production`, verify that environment is the one bound to the route.
214
+
215
+ Expected `/health` response includes JSON like:
216
+
217
+ ```json
218
+ {"ok":true,"version":"1.0","peers":0}
219
+ ```
220
+
221
+ ## Auto WebRTC two-tab test
222
+
223
+ The demo defaults to Auto WebRTC and performs real offer/answer + ICE exchange over this Worker.
224
+
225
+ 1. Open `http://127.0.0.1:8787/` in two tabs.
226
+ 2. Set both tabs to the same `network` and `session_id`.
227
+ 3. Set opposite peer IDs using random hex strings:
228
+ - Tab A `from`: `fc2142e44ec5c76f1bd46ccbb1eb2ed48f66f64260a5299c871f37ac742fa0c9`
229
+ - Tab B `from`: `3e45d44c4ce4f9304a53f42b978fd13d23f85df4d97a88f8eb33ec13a2f8f7b1`
230
+ 4. Set each tab `to` to the other tab `from`, or leave `to` empty for auto-discovery.
231
+ 5. Connect both sockets and click Start Auto Handshake in both tabs.
232
+ 6. When DataChannel is open, send chat messages.
233
+
234
+ ## Minimal client expectations
235
+
236
+ - Send `announce` first to bind socket identity (`from`, `network`).
237
+ - Include `session_id` for negotiation messages.
238
+ - Include `to` for directed messages.
239
+ - Use periodic `announce` to refresh presence TTL and receive queued relay messages.
240
+ - Use `ping` for liveness (`pong`) and keepalive.
241
+
242
+ ## Notes
243
+
244
+ - TTL is enforced using `timestamp + ttl_ms` (default 30 seconds, max 120 seconds).
245
+ - Malformed envelopes produce [PSP](https://github.com/draeder/Peer-Signaling-Protocol-Specification) `error` responses.
246
+
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawnSync } from 'node:child_process';
7
+ import { ensureProjectFiles, resolveProjectRoot, resolveWranglerCommand } from '../scripts/project-bootstrap.mjs';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const PACKAGE_ROOT = path.resolve(path.dirname(__filename), '..');
11
+ const PROJECT_ROOT = resolveProjectRoot(process.cwd());
12
+
13
+ function printHelp() {
14
+ console.log(`freertc CLI
15
+
16
+ Usage:
17
+ freertc
18
+ freertc wizard
19
+ freertc setup
20
+ freertc init
21
+ freertc install
22
+ freertc deploy [-- <deploy-args>]
23
+ freertc dev [-- <dev-args>]
24
+ freertc dev:cf [-- <dev-args>]
25
+
26
+ Examples:
27
+ npx freertc
28
+ npx freertc wizard
29
+ npx freertc setup
30
+ freertc
31
+ npx freertc deploy
32
+ `);
33
+ }
34
+
35
+ function runInProject(command, args, { bootstrap = false } = {}) {
36
+ if (bootstrap) {
37
+ ensureProjectFiles(PROJECT_ROOT);
38
+ }
39
+
40
+ const result = spawnSync(command, args, {
41
+ cwd: PROJECT_ROOT,
42
+ stdio: 'inherit',
43
+ env: {
44
+ ...process.env,
45
+ FREERTC_PACKAGE_ROOT: PACKAGE_ROOT
46
+ }
47
+ });
48
+
49
+ if (typeof result.status === 'number') {
50
+ process.exit(result.status);
51
+ }
52
+
53
+ process.exit(1);
54
+ }
55
+
56
+ function requireWranglerConfig() {
57
+ const configPath = path.join(PROJECT_ROOT, 'wrangler.jsonc');
58
+ if (fs.existsSync(configPath)) {
59
+ return;
60
+ }
61
+
62
+ console.error(`Missing ${configPath}. Run "npx freertc" or "npx freertc wizard" from this project directory first.`);
63
+ process.exit(1);
64
+ }
65
+
66
+ const [, , subcommand, ...rest] = process.argv;
67
+
68
+ if (!subcommand) {
69
+ runInProject(process.execPath, [path.join(PACKAGE_ROOT, 'scripts', 'wrangler-install-wizard.mjs'), '--mode', 'both'], { bootstrap: true });
70
+ }
71
+
72
+ if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
73
+ printHelp();
74
+ process.exit(0);
75
+ }
76
+
77
+ if (subcommand === 'wizard') {
78
+ runInProject(process.execPath, [path.join(PACKAGE_ROOT, 'scripts', 'wrangler-install-wizard.mjs'), ...rest], { bootstrap: true });
79
+ }
80
+
81
+ if (subcommand === 'setup') {
82
+ runInProject(process.execPath, [path.join(PACKAGE_ROOT, 'scripts', 'wrangler-install-wizard.mjs'), '--mode', 'both', ...rest], { bootstrap: true });
83
+ }
84
+
85
+ if (subcommand === 'init' || subcommand === 'install') {
86
+ runInProject(process.execPath, [path.join(PACKAGE_ROOT, 'scripts', 'wrangler-install-wizard.mjs'), '--mode', 'both', ...rest], { bootstrap: true });
87
+ }
88
+
89
+ if (subcommand === 'deploy') {
90
+ ensureProjectFiles(PROJECT_ROOT);
91
+ requireWranglerConfig();
92
+ const wrangler = resolveWranglerCommand(PROJECT_ROOT);
93
+ runInProject(wrangler.command, [...wrangler.baseArgs, 'deploy', '--env', 'production', ...rest]);
94
+ }
95
+
96
+ if (subcommand === 'dev') {
97
+ runInProject(process.execPath, [path.join(PACKAGE_ROOT, 'scripts', 'non-cloudflare-server.mjs'), ...rest], { bootstrap: true });
98
+ }
99
+
100
+ if (subcommand === 'dev:cf' || subcommand === 'dev-cf') {
101
+ runInProject(process.execPath, [path.join(PACKAGE_ROOT, 'scripts', 'dev-server.mjs'), ...rest], { bootstrap: true });
102
+ }
103
+
104
+ console.error(`Unknown command: ${subcommand}\n`);
105
+ printHelp();
106
+ process.exit(1);
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "freertc",
3
+ "version": "0.1.0",
4
+ "description": "Cloudflare Worker signaling relay for WebRTC peers with D1 storage.",
5
+ "keywords": [
6
+ "webrtc",
7
+ "signaling",
8
+ "cloudflare-workers",
9
+ "cloudflare-d1",
10
+ "websocket"
11
+ ],
12
+ "homepage": "https://github.com/draeder/freertc#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/draeder/freertc/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/draeder/freertc.git"
19
+ },
20
+ "bin": {
21
+ "freertc": "bin/freertc.mjs"
22
+ },
23
+ "type": "module",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "files": [
29
+ "README.md",
30
+ "bin/",
31
+ "public/",
32
+ "scripts/d1-schema.sql",
33
+ "scripts/dev-server.mjs",
34
+ "scripts/non-cloudflare-server.mjs",
35
+ "scripts/postinstall-message.mjs",
36
+ "scripts/project-bootstrap.mjs",
37
+ "scripts/wrangler-install-wizard.mjs",
38
+ "src/index.js",
39
+ "wrangler.template.jsonc",
40
+ "wrangler.workers-dev.jsonc"
41
+ ],
42
+ "scripts": {
43
+ "build": "worker-build --release",
44
+ "dev": "node scripts/non-cloudflare-server.mjs",
45
+ "dev:cf": "node scripts/dev-server.mjs",
46
+ "dev:node": "node scripts/non-cloudflare-server.mjs",
47
+ "dev:local": "npm run dev:node",
48
+ "deploy": "wrangler deploy --env production",
49
+ "deploy:raw": "wrangler deploy",
50
+ "check": "cargo check --target wasm32-unknown-unknown",
51
+ "d1:init:local": "wrangler d1 execute freertc-signal --local --file scripts/d1-schema.sql",
52
+ "d1:init:remote": "wrangler d1 execute freertc-signal --remote --file scripts/d1-schema.sql",
53
+ "postinstall": "node scripts/postinstall-message.mjs",
54
+ "wizard": "node scripts/wrangler-install-wizard.mjs",
55
+ "config:validate": "node scripts/validate-config.mjs",
56
+ "config:gen": "node scripts/generate-prod-config.mjs",
57
+ "dev:prod": "npm run config:validate && npm run config:gen && node scripts/prod-server.mjs",
58
+ "deploy:local-prod": "npm run config:validate && npm run config:gen && docker-compose -f docker-compose.prod.yml up -d",
59
+ "deploy:local-prod:logs": "docker-compose -f docker-compose.prod.yml logs -f",
60
+ "deploy:local-prod:stop": "docker-compose -f docker-compose.prod.yml down"
61
+ },
62
+ "dependencies": {
63
+ "ws": "^8.18.0"
64
+ },
65
+ "devDependencies": {
66
+ "wrangler": "^3.114.0"
67
+ }
68
+ }