zero-query 1.2.0 → 1.2.3

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.
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "{{NAME}}",
3
+ "private": true,
4
+ "type": "commonjs",
5
+ "scripts": {
6
+ "start": "node server/index.js",
7
+ "dev": "node server/index.js"
8
+ },
9
+ "dependencies": {
10
+ "zero-query": "latest"
11
+ },
12
+ "devDependencies": {
13
+ "@zero-server/sdk": "^0.9.8",
14
+ "@zero-server/webrtc": "^0.9.8"
15
+ }
16
+ }
@@ -0,0 +1,198 @@
1
+ // server/index.js - WebRTC signaling + TURN backend
2
+ //
3
+ // Hosts the zQuery webrtc demo over real WebSocket signaling using
4
+ // @zero-server/webrtc's SignalingHub. Browsers connect with
5
+ // `$.webrtc.join('ws://host:PORT/rtc', { room })` and exchange offers /
6
+ // answers / ICE through the hub - so two real machines (not just two
7
+ // tabs in the same browser) can call each other.
8
+ //
9
+ // Usage:
10
+ // node server/index.js # listens on PORT (default 3000)
11
+ // PORT=8080 node server/index.js
12
+ //
13
+ // Optional environment variables:
14
+ // WEBRTC_JWT_SECRET - if set, joins must include a signed join token.
15
+ // GET /rtc/token/:room returns a short-TTL token.
16
+ // TURN_SECRET - if set, GET /rtc/turn returns ephemeral
17
+ // RFC 7635 TURN credentials. Otherwise it returns
18
+ // a plain STUN-only iceServers list.
19
+ // TURN_URLS - comma-separated TURN/STUN URLs to hand out
20
+ // (e.g. 'turn:turn.example.com:3478?transport=udp').
21
+ //
22
+ // @zero-server/sdk and @zero-server/webrtc are devDependencies declared in
23
+ // package.json. If they aren't installed yet, this script prompts to
24
+ // install them once on first run (same pattern as the dev server does
25
+ // for zero-http).
26
+
27
+ 'use strict';
28
+
29
+ const path = require('node:path');
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Install prompt
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Prompt the user to install a missing devDependency. Resolves true on yes.
37
+ */
38
+ function promptInstall(label) {
39
+ const rl = require('node:readline').createInterface({
40
+ input: process.stdin,
41
+ output: process.stdout,
42
+ });
43
+ return new Promise((resolve) => {
44
+ rl.question(
45
+ '\n This server needs ' + label + ', which is not installed.\n' +
46
+ ' These packages are only used by the demo server and are not\n' +
47
+ ' required for the client bundle, building, or production.\n' +
48
+ ' Install them now? (y/n): ',
49
+ (answer) => {
50
+ rl.close();
51
+ resolve(answer.trim().toLowerCase() === 'y');
52
+ }
53
+ );
54
+ });
55
+ }
56
+
57
+ /**
58
+ * `require` a package; on failure, prompt the user to npm-install it as a
59
+ * devDependency, then re-require. Exits the process if the user declines.
60
+ */
61
+ async function requireOrInstall(pkgs) {
62
+ try {
63
+ return pkgs.map((p) => require(p));
64
+ } catch (err) {
65
+ if (err.code !== 'MODULE_NOT_FOUND') throw err;
66
+ const ok = await promptInstall(pkgs.join(' + '));
67
+ if (!ok) {
68
+ console.error('\n ✖ Cannot start the WebRTC server without these packages.\n');
69
+ process.exit(1);
70
+ }
71
+ const { execSync } = require('node:child_process');
72
+ const args = pkgs.join(' ');
73
+ console.log('\n Installing ' + args + '...\n');
74
+ execSync('npm install --save-dev ' + args, { stdio: 'inherit' });
75
+ return pkgs.map((p) => require(p));
76
+ }
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Bootstrap
81
+ // ---------------------------------------------------------------------------
82
+
83
+ async function main() {
84
+ const [sdk, webrtc] = await requireOrInstall([
85
+ '@zero-server/sdk',
86
+ '@zero-server/webrtc',
87
+ ]);
88
+
89
+ const {
90
+ createApp,
91
+ helmet,
92
+ cors,
93
+ compress,
94
+ static: serveStatic,
95
+ } = sdk;
96
+ const {
97
+ SignalingHub,
98
+ signJoinToken,
99
+ issueTurnCredentials,
100
+ } = webrtc;
101
+
102
+ const PORT = parseInt(process.env.PORT || '3000', 10);
103
+ const ROOT = path.resolve(__dirname, '..');
104
+ const JWT = process.env.WEBRTC_JWT_SECRET || null;
105
+ const TURN_SEC = process.env.TURN_SECRET || null;
106
+ const TURN_URLS = (process.env.TURN_URLS || '').split(',').map((s) => s.trim()).filter(Boolean);
107
+ const STUN_FALLBACK = ['stun:stun.l.google.com:19302'];
108
+
109
+ const app = createApp();
110
+
111
+ // ---- Security & static assets ----
112
+ app.use(helmet({
113
+ contentSecurityPolicy: {
114
+ directives: {
115
+ defaultSrc: ["'self'"],
116
+ scriptSrc: ["'self'", "'unsafe-inline'"],
117
+ styleSrc: ["'self'", "'unsafe-inline'"],
118
+ imgSrc: ["'self'", 'data:', 'blob:'],
119
+ mediaSrc: ["'self'", 'blob:'],
120
+ connectSrc: ["'self'", 'ws:', 'wss:'],
121
+ fontSrc: ["'self'", 'data:'],
122
+ },
123
+ },
124
+ hsts: false,
125
+ }));
126
+ app.use(cors());
127
+ app.use(compress({ threshold: 1024 }));
128
+ app.use(serveStatic(ROOT, { index: 'index.html' }));
129
+
130
+ // ---- Signaling hub ----
131
+ const hubOpts = {};
132
+ if (JWT) hubOpts.joinTokenSecret = JWT;
133
+ const hub = new SignalingHub(hubOpts);
134
+
135
+ app.ws('/rtc', (ws, req) => {
136
+ hub.attach(ws, {
137
+ ip: req.ip,
138
+ origin: req.headers && req.headers.origin,
139
+ });
140
+ });
141
+
142
+ hub.on('join', ({ peer, room }) => console.log(' + peer', peer.id, 'joined', room.name));
143
+ hub.on('leave', ({ peer, room }) => console.log(' - peer', peer.id, 'left', room.name));
144
+
145
+ // ---- Join token endpoint (only when WEBRTC_JWT_SECRET is set) ----
146
+ app.get('/rtc/token/:room', (req, res) => {
147
+ if (!JWT) return res.json({ wsUrl: _wsUrl(req), token: null });
148
+ const token = signJoinToken({
149
+ secret: JWT,
150
+ user: { id: 'anon-' + Math.random().toString(36).slice(2, 10) },
151
+ room: req.params.room,
152
+ ttl: 300,
153
+ });
154
+ res.json({ wsUrl: _wsUrl(req), token });
155
+ });
156
+
157
+ // ---- TURN/STUN credential endpoint ----
158
+ app.get('/rtc/turn', (req, res) => {
159
+ if (TURN_SEC && TURN_URLS.length) {
160
+ const creds = issueTurnCredentials({
161
+ secret: TURN_SEC,
162
+ userId: 'anon-' + Math.random().toString(36).slice(2, 10),
163
+ ttl: '20m',
164
+ servers: TURN_URLS,
165
+ });
166
+ return res.json({ iceServers: [creds] });
167
+ }
168
+ // Dev fallback - public STUN only. Works for two machines on the
169
+ // same network but cannot traverse symmetric NATs without TURN.
170
+ const urls = TURN_URLS.length ? TURN_URLS : STUN_FALLBACK;
171
+ res.json({ iceServers: [{ urls }] });
172
+ });
173
+
174
+ // ---- Listen ----
175
+ app.listen(PORT, () => {
176
+ console.log('\n ⚡ WebRTC server → http://localhost:' + PORT);
177
+ console.log(' • signaling ws://localhost:' + PORT + '/rtc');
178
+ console.log(' • turn creds http://localhost:' + PORT + '/rtc/turn');
179
+ if (JWT) {
180
+ console.log(' • join tokens http://localhost:' + PORT + '/rtc/token/<room>');
181
+ } else {
182
+ console.log(' • join tokens disabled (set WEBRTC_JWT_SECRET to enable)');
183
+ }
184
+ console.log('');
185
+ });
186
+ }
187
+
188
+ function _wsUrl(req) {
189
+ const host = (req.headers && req.headers.host) || 'localhost:3000';
190
+ const proto = (req.headers && req.headers['x-forwarded-proto'] === 'https') ? 'wss' : 'ws';
191
+ return proto + '://' + host + '/rtc';
192
+ }
193
+
194
+ main().catch((err) => {
195
+ console.error('\n ✖ WebRTC server failed to start:\n');
196
+ console.error(err);
197
+ process.exit(1);
198
+ });
package/dist/API.md CHANGED
@@ -6555,7 +6555,7 @@ The WebRTC surface is layered — pick the level you need:
6555
6555
  | **Hardening** | `SFrameContext`, `attachE2ee`, TURN refresher | You need E2EE and rotating TURN credentials. |
6556
6556
 
6557
6557
 
6558
- > **Tip:** Want a working starting point? Run `npx zero-query create my-app --webrtc-demo` (alias `-w`) to scaffold a one-page video room with local + remote tiles, mic/cam controls, and a status overlay.
6558
+ > **Tip:** Want a working starting point? Run `npx zero-query create my-app --webrtc-demo` (alias `-w`) to scaffold a one-page video room with mic / camera / screen-share toggles, a reactive roster, and a chat data channel. The scaffold installs [zero-server](https://github.com/tonywied17/zero-server), launches the signaling + static server on `http://localhost:3000`, and opens your browser - one command, no extra setup. Camera and microphone stay off until the user opts in.
6559
6559
 
6560
6560
 
6561
6561
  ### Surface Status
Binary file