@zero-server/auth 0.9.6 → 0.9.7
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/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/auth/authorize.js +11 -11
- package/lib/auth/enrollment.js +5 -5
- package/lib/auth/jwt.js +9 -9
- package/lib/auth/oauth.js +1 -1
- package/lib/auth/session.js +5 -5
- package/lib/auth/trustedDevice.js +2 -2
- package/lib/auth/twoFactor.js +11 -11
- package/lib/auth/webauthn.js +6 -6
- package/lib/debug.js +10 -10
- package/package.json +3 -3
- package/types/body.d.ts +1 -1
- package/types/cli.d.ts +1 -1
- package/types/index.d.ts +16 -4
- package/types/middleware.d.ts +1 -1
- package/types/orm.d.ts +3 -3
- package/types/request.d.ts +3 -3
- package/types/webrtc.d.ts +501 -0
package/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// AUTO-GENERATED by .tools/generate-package-stubs.js
|
|
1
|
+
// AUTO-GENERATED by .tools/generate-package-stubs.js - edit .tools/scope-manifest.js and re-run `npm run packages:generate`.
|
|
2
2
|
export * from './types/auth';
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// AUTO-GENERATED by .tools/generate-package-stubs.js
|
|
1
|
+
// AUTO-GENERATED by .tools/generate-package-stubs.js - edit .tools/scope-manifest.js and re-run `npm run packages:generate`.
|
|
2
2
|
'use strict';
|
|
3
3
|
const lib = require("./lib/auth");
|
|
4
4
|
|
package/lib/auth/authorize.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module auth/authorize
|
|
3
|
-
* @description Authorization helpers
|
|
3
|
+
* @description Authorization helpers - role-based access control (RBAC),
|
|
4
4
|
* permission-based access, and policy classes.
|
|
5
5
|
*
|
|
6
6
|
* Works with any authentication middleware that sets `req.user`.
|
|
@@ -13,22 +13,22 @@
|
|
|
13
13
|
* app.use(jwt({ secret: process.env.JWT_SECRET }));
|
|
14
14
|
* app.use(attachUserHelpers());
|
|
15
15
|
*
|
|
16
|
-
* // Role-based
|
|
16
|
+
* // Role-based - only admins and editors can modify posts
|
|
17
17
|
* app.put('/posts/:id', authorize('admin', 'editor'), (req, res) => {
|
|
18
18
|
* res.json({ updated: true });
|
|
19
19
|
* });
|
|
20
20
|
*
|
|
21
|
-
* // Permission-based
|
|
21
|
+
* // Permission-based - require ALL listed permissions
|
|
22
22
|
* app.delete('/users/:id', can('users:read', 'users:delete'), (req, res) => {
|
|
23
23
|
* res.json({ deleted: true });
|
|
24
24
|
* });
|
|
25
25
|
*
|
|
26
|
-
* // ANY permission
|
|
26
|
+
* // ANY permission - useful for overlapping access
|
|
27
27
|
* app.get('/reports', canAny('reports:read', 'admin:read'), (req, res) => {
|
|
28
28
|
* res.json({ reports: [] });
|
|
29
29
|
* });
|
|
30
30
|
*
|
|
31
|
-
* // Policy class
|
|
31
|
+
* // Policy class - resource-level authorization
|
|
32
32
|
* class PostPolicy extends Policy {
|
|
33
33
|
* before(user) { if (user.role === 'superadmin') return true; }
|
|
34
34
|
* update(user, post) { return user.id === post.authorId || user.role === 'admin'; }
|
|
@@ -114,9 +114,9 @@ function authorize(...roles)
|
|
|
114
114
|
* Checks `req.user.permissions` (array or Set) for the required permission(s).
|
|
115
115
|
*
|
|
116
116
|
* Permission strings follow a `resource:action` convention:
|
|
117
|
-
* - `'posts:write'`
|
|
118
|
-
* - `'users:delete'`
|
|
119
|
-
* - `'*'`
|
|
117
|
+
* - `'posts:write'` - write access to posts
|
|
118
|
+
* - `'users:delete'` - delete users
|
|
119
|
+
* - `'*'` - superuser wildcard
|
|
120
120
|
*
|
|
121
121
|
* @param {...string} permissions - Required permissions (ALL must be present unless `opts.any` is true).
|
|
122
122
|
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
@@ -293,7 +293,7 @@ function gate(policy, action, getResource)
|
|
|
293
293
|
});
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
// Attach resource if loaded
|
|
296
|
+
// Attach resource if loaded - saves a redundant DB query in the handler
|
|
297
297
|
if (resource && !req.resource) req.resource = resource;
|
|
298
298
|
next();
|
|
299
299
|
};
|
|
@@ -306,8 +306,8 @@ function gate(policy, action, getResource)
|
|
|
306
306
|
* Call this middleware after JWT/session middleware.
|
|
307
307
|
*
|
|
308
308
|
* Adds:
|
|
309
|
-
* - `req.user.is(...roles)`
|
|
310
|
-
* - `req.user.can(...perms)`
|
|
309
|
+
* - `req.user.is(...roles)` - check roles
|
|
310
|
+
* - `req.user.can(...perms)` - check permissions
|
|
311
311
|
*
|
|
312
312
|
* @returns {Function} Middleware.
|
|
313
313
|
*
|
package/lib/auth/enrollment.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* for TOTP-based two-factor authentication.
|
|
6
6
|
*
|
|
7
7
|
* Steps:
|
|
8
|
-
* 1. `start()`
|
|
9
|
-
* 2. `verify()`
|
|
10
|
-
* 3. `complete()`
|
|
11
|
-
* 4. `disable()`
|
|
8
|
+
* 1. `start()` - Generate secret + backup codes, store in session
|
|
9
|
+
* 2. `verify()` - Confirm user can produce a valid TOTP code
|
|
10
|
+
* 3. `complete()` - Persist the verified secret to the database
|
|
11
|
+
* 4. `disable()` - Remove 2FA from the account
|
|
12
12
|
*
|
|
13
13
|
* @example | Full enrollment flow
|
|
14
14
|
* const { enrollment } = require('@zero-server/sdk');
|
|
@@ -356,7 +356,7 @@ function enrollment(opts = {})
|
|
|
356
356
|
|
|
357
357
|
function _defaultGetAccount(req)
|
|
358
358
|
{
|
|
359
|
-
if (!req.user) throw new Error('No user on request
|
|
359
|
+
if (!req.user) throw new Error('No user on request - authentication middleware required');
|
|
360
360
|
return req.user.email || req.user.id || req.user.sub;
|
|
361
361
|
}
|
|
362
362
|
|
package/lib/auth/jwt.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* const app = createApp();
|
|
14
14
|
* const SECRET = process.env.JWT_SECRET;
|
|
15
15
|
*
|
|
16
|
-
* // Public
|
|
16
|
+
* // Public - issue tokens
|
|
17
17
|
* app.post('/login', json(), async (req, res) => {
|
|
18
18
|
* const { email, password } = req.body;
|
|
19
19
|
* const user = await db.users.findOne({ email });
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* res.json({ token });
|
|
25
25
|
* });
|
|
26
26
|
*
|
|
27
|
-
* // Protected
|
|
27
|
+
* // Protected - everything under /api requires a valid token
|
|
28
28
|
* const api = Router();
|
|
29
29
|
* api.use(jwt({ secret: SECRET }));
|
|
30
30
|
* api.get('/me', (req, res) => res.json({ id: req.user.sub, role: req.user.role }));
|
|
@@ -79,7 +79,7 @@ function _base64urlDecode(str)
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Decode a JWT without verifying the signature.
|
|
82
|
-
* Returns `null` for malformed tokens
|
|
82
|
+
* Returns `null` for malformed tokens - never throws.
|
|
83
83
|
*
|
|
84
84
|
* @param {string} token - Raw JWT string.
|
|
85
85
|
* @returns {{ header: object, payload: object, signature: string }|null}
|
|
@@ -268,7 +268,7 @@ function verify(token, secretOrKey, opts = {})
|
|
|
268
268
|
* @param {Function} [opts.fetcher] - Custom fetch function (default: built-in fetch).
|
|
269
269
|
* @param {number} [opts.cacheTtl=600000] - Cache TTL in ms (default 10 minutes).
|
|
270
270
|
* @param {number} [opts.requestTimeout=5000] - Request timeout in ms.
|
|
271
|
-
* @returns {Function} `async (header) => publicKey`
|
|
271
|
+
* @returns {Function} `async (header) => publicKey` - resolves the signing key for a JWT header.
|
|
272
272
|
*
|
|
273
273
|
* @example
|
|
274
274
|
* const getKey = jwks('https://auth.example.com/.well-known/jwks.json');
|
|
@@ -330,7 +330,7 @@ function jwks(jwksUri, opts = {})
|
|
|
330
330
|
return pem;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
// No kid
|
|
333
|
+
// No kid - return the first RSA key
|
|
334
334
|
const first = keys.values().next().value;
|
|
335
335
|
if (!first) throw _jwtError('No suitable key in JWKS', 'JWKS_NO_KEY');
|
|
336
336
|
return first;
|
|
@@ -347,9 +347,9 @@ function jwks(jwksUri, opts = {})
|
|
|
347
347
|
* Create JWT authentication middleware.
|
|
348
348
|
*
|
|
349
349
|
* On success, populates:
|
|
350
|
-
* - `req.user`
|
|
351
|
-
* - `req.auth`
|
|
352
|
-
* - `req.token`
|
|
350
|
+
* - `req.user` - decoded payload
|
|
351
|
+
* - `req.auth` - `{ header, payload, token }` full decode info
|
|
352
|
+
* - `req.token` - raw JWT string
|
|
353
353
|
*
|
|
354
354
|
* @param {object} opts - Configuration.
|
|
355
355
|
* @param {string|Buffer} [opts.secret] - HMAC secret for HS* algorithms.
|
|
@@ -367,7 +367,7 @@ function jwks(jwksUri, opts = {})
|
|
|
367
367
|
* @param {number} [opts.clockTolerance=0] - Clock skew tolerance in seconds.
|
|
368
368
|
* @param {number} [opts.maxAge] - Maximum token age in seconds.
|
|
369
369
|
* @param {boolean} [opts.credentialsRequired=true] - Return 401 if no token found (false = optional auth).
|
|
370
|
-
* @param {Function} [opts.isRevoked] - `async (payload) => boolean`
|
|
370
|
+
* @param {Function} [opts.isRevoked] - `async (payload) => boolean` - check token revocation.
|
|
371
371
|
* @param {Function} [opts.onError] - Custom error handler `(err, req, res) => void`.
|
|
372
372
|
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
373
373
|
*
|
package/lib/auth/oauth.js
CHANGED
|
@@ -224,7 +224,7 @@ function oauth(opts = {})
|
|
|
224
224
|
// Validate state (CSRF prevention)
|
|
225
225
|
if (verify.state && query.state !== verify.state)
|
|
226
226
|
{
|
|
227
|
-
throw _oauthError('State mismatch
|
|
227
|
+
throw _oauthError('State mismatch - possible CSRF attack', 'OAUTH_STATE_MISMATCH');
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
const body = {
|
package/lib/auth/session.js
CHANGED
|
@@ -263,7 +263,7 @@ class Session
|
|
|
263
263
|
return { ...this._flash };
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
/** @private
|
|
266
|
+
/** @private - serialize to JSON for cookie/store */
|
|
267
267
|
_serialize()
|
|
268
268
|
{
|
|
269
269
|
const obj = { d: this._data };
|
|
@@ -271,7 +271,7 @@ class Session
|
|
|
271
271
|
return JSON.stringify(obj);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
/** @private
|
|
274
|
+
/** @private - deserialize from JSON */
|
|
275
275
|
static _deserialize(json, id)
|
|
276
276
|
{
|
|
277
277
|
try
|
|
@@ -472,7 +472,7 @@ function session(opts = {})
|
|
|
472
472
|
req.session = sess;
|
|
473
473
|
|
|
474
474
|
// Intercept response to persist session
|
|
475
|
-
// Hook into res.raw.end (Node ServerResponse)
|
|
475
|
+
// Hook into res.raw.end (Node ServerResponse) - the Response wrapper
|
|
476
476
|
// has no .end() method; its .send()/.json() helpers call raw.end().
|
|
477
477
|
const raw = res.raw;
|
|
478
478
|
const origEnd = raw.end.bind(raw);
|
|
@@ -481,7 +481,7 @@ function session(opts = {})
|
|
|
481
481
|
try
|
|
482
482
|
{
|
|
483
483
|
// _saveSession calls res.cookie() / res.clearCookie() which set
|
|
484
|
-
// Set-Cookie headers on raw via raw.setHeader
|
|
484
|
+
// Set-Cookie headers on raw via raw.setHeader - safe because
|
|
485
485
|
// headers aren't flushed until the original end() runs.
|
|
486
486
|
// NOTE: store-based sessions are sync-compatible because
|
|
487
487
|
// MemoryStore.set/get return resolved promises synchronously
|
|
@@ -574,7 +574,7 @@ function _saveSession(req, res, sess, ctx)
|
|
|
574
574
|
const encrypted = _encrypt(payload, ctx.keys[0]);
|
|
575
575
|
if (encrypted.length > MAX_COOKIE_SIZE)
|
|
576
576
|
{
|
|
577
|
-
log.warn('session cookie exceeds %d bytes
|
|
577
|
+
log.warn('session cookie exceeds %d bytes - consider using a store', MAX_COOKIE_SIZE);
|
|
578
578
|
}
|
|
579
579
|
res.cookie(ctx.cookieName, encrypted, cOpts);
|
|
580
580
|
log.debug('cookie session saved');
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Subsequent requests skip the 2FA prompt if the trust token is valid.
|
|
8
8
|
* Supports secret rotation, IP binding, and revocation.
|
|
9
9
|
*
|
|
10
|
-
* Uses AES-256-GCM encryption
|
|
10
|
+
* Uses AES-256-GCM encryption - tokens are encrypted, not just signed,
|
|
11
11
|
* preventing information leakage.
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
@@ -370,7 +370,7 @@ function _defaultFingerprint(req)
|
|
|
370
370
|
*/
|
|
371
371
|
function _defaultGetUserId(req)
|
|
372
372
|
{
|
|
373
|
-
if (!req.user) throw new Error('No user on request
|
|
373
|
+
if (!req.user) throw new Error('No user on request - authentication middleware required');
|
|
374
374
|
return req.user.id || req.user.sub || req.user._id;
|
|
375
375
|
}
|
|
376
376
|
|
package/lib/auth/twoFactor.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements TOTP (RFC 6238 / RFC 4226), backup codes,
|
|
5
5
|
* and composable middleware for step-up verification.
|
|
6
6
|
*
|
|
7
|
-
* Uses only Node.js built-in `crypto`
|
|
7
|
+
* Uses only Node.js built-in `crypto` - no external packages.
|
|
8
8
|
*
|
|
9
9
|
* @example | Setup 2FA for a user
|
|
10
10
|
* const { twoFactor } = require('@zero-server/sdk');
|
|
@@ -228,7 +228,7 @@ function _base32Decode(str)
|
|
|
228
228
|
|
|
229
229
|
/**
|
|
230
230
|
* Generate an HOTP code for a given counter value.
|
|
231
|
-
* Implements RFC 4226 §5
|
|
231
|
+
* Implements RFC 4226 §5 - HMAC-based One-Time Password.
|
|
232
232
|
*
|
|
233
233
|
* @param {Buffer} secret - Shared secret key.
|
|
234
234
|
* @param {number} counter - 8-byte counter value.
|
|
@@ -498,13 +498,13 @@ function verifyBackupCode(code, hashes)
|
|
|
498
498
|
|
|
499
499
|
/**
|
|
500
500
|
* Middleware that requires completed 2FA verification on the session.
|
|
501
|
-
* Checks `req.session.get('twoFactorVerified')`
|
|
501
|
+
* Checks `req.session.get('twoFactorVerified')` - returns 403 if not set.
|
|
502
502
|
*
|
|
503
503
|
* Designed to compose with `jwt()` or `session()` middleware:
|
|
504
504
|
*
|
|
505
505
|
* app.use(jwt({ secret }));
|
|
506
506
|
* app.use(require2FA());
|
|
507
|
-
* //
|
|
507
|
+
* // - or -
|
|
508
508
|
* app.use(session({ secret }));
|
|
509
509
|
* app.use(require2FA());
|
|
510
510
|
*
|
|
@@ -517,7 +517,7 @@ function verifyBackupCode(code, hashes)
|
|
|
517
517
|
* Defaults to always requiring 2FA.
|
|
518
518
|
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
519
519
|
*
|
|
520
|
-
* @example | Basic
|
|
520
|
+
* @example | Basic - all authenticated users must complete 2FA
|
|
521
521
|
* app.use(require2FA());
|
|
522
522
|
*
|
|
523
523
|
* @example | Only enforce for users who have enrolled
|
|
@@ -548,7 +548,7 @@ function require2FA(opts = {})
|
|
|
548
548
|
const enabled = await isEnabled(req);
|
|
549
549
|
if (!enabled)
|
|
550
550
|
{
|
|
551
|
-
log('2FA not enabled for user
|
|
551
|
+
log('2FA not enabled for user - skipping');
|
|
552
552
|
return next();
|
|
553
553
|
}
|
|
554
554
|
}
|
|
@@ -568,7 +568,7 @@ function require2FA(opts = {})
|
|
|
568
568
|
const session = req.session;
|
|
569
569
|
if (!session || typeof session.get !== 'function')
|
|
570
570
|
{
|
|
571
|
-
log.warn('require2FA: no session found
|
|
571
|
+
log.warn('require2FA: no session found - is session() middleware active?');
|
|
572
572
|
const raw = res.raw || res;
|
|
573
573
|
if (raw.headersSent) return;
|
|
574
574
|
raw.statusCode = 500;
|
|
@@ -583,7 +583,7 @@ function require2FA(opts = {})
|
|
|
583
583
|
return next();
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
-
log.warn('2FA not completed
|
|
586
|
+
log.warn('2FA not completed - blocking request');
|
|
587
587
|
const raw = res.raw || res;
|
|
588
588
|
if (raw.headersSent) return;
|
|
589
589
|
raw.statusCode = statusCode;
|
|
@@ -690,7 +690,7 @@ function verifyTOTPMiddleware(opts = {})
|
|
|
690
690
|
raw.end(JSON.stringify({ error: 'Too many attempts. Try again later.', retryAfter }));
|
|
691
691
|
return;
|
|
692
692
|
}
|
|
693
|
-
// Lockout expired
|
|
693
|
+
// Lockout expired - reset
|
|
694
694
|
attempts.delete(ip);
|
|
695
695
|
}
|
|
696
696
|
|
|
@@ -735,7 +735,7 @@ function verifyTOTPMiddleware(opts = {})
|
|
|
735
735
|
|
|
736
736
|
if (result.valid)
|
|
737
737
|
{
|
|
738
|
-
// Replay prevention (RFC 6238 §5.2)
|
|
738
|
+
// Replay prevention (RFC 6238 §5.2) - check AFTER signature
|
|
739
739
|
// verification to avoid leaking timing information about whether
|
|
740
740
|
// a code was previously used.
|
|
741
741
|
if (opts.replayStore)
|
|
@@ -775,7 +775,7 @@ function verifyTOTPMiddleware(opts = {})
|
|
|
775
775
|
catch (err)
|
|
776
776
|
{
|
|
777
777
|
log.error('replay store error: %s', err.message);
|
|
778
|
-
// Fail open for store errors
|
|
778
|
+
// Fail open for store errors - don't block legitimate users
|
|
779
779
|
}
|
|
780
780
|
}
|
|
781
781
|
|
package/lib/auth/webauthn.js
CHANGED
|
@@ -145,7 +145,7 @@ const cbor = {
|
|
|
145
145
|
if (additionalInfo === 23) return undefined;
|
|
146
146
|
if (additionalInfo === 25)
|
|
147
147
|
{
|
|
148
|
-
// float16
|
|
148
|
+
// float16 - not commonly used in WebAuthn but handle it
|
|
149
149
|
offset -= 0; // already read
|
|
150
150
|
return readArgument(additionalInfo);
|
|
151
151
|
}
|
|
@@ -593,7 +593,7 @@ function verifyRegistration(opts)
|
|
|
593
593
|
if (clientData.type !== 'webauthn.create')
|
|
594
594
|
return { verified: false, credential: null, error: `Invalid type: ${clientData.type}` };
|
|
595
595
|
|
|
596
|
-
// Strict origin validation
|
|
596
|
+
// Strict origin validation - exact match, no regex
|
|
597
597
|
if (clientData.origin !== expectedOrigin)
|
|
598
598
|
return { verified: false, credential: null, error: `Origin mismatch: ${clientData.origin}` };
|
|
599
599
|
|
|
@@ -627,7 +627,7 @@ function verifyRegistration(opts)
|
|
|
627
627
|
|
|
628
628
|
if (fmt === 'none')
|
|
629
629
|
{
|
|
630
|
-
// No attestation
|
|
630
|
+
// No attestation - acceptable for 'none' conveyance
|
|
631
631
|
log.debug('registration with "none" attestation format');
|
|
632
632
|
}
|
|
633
633
|
else if (fmt === 'packed')
|
|
@@ -644,7 +644,7 @@ function verifyRegistration(opts)
|
|
|
644
644
|
}
|
|
645
645
|
else
|
|
646
646
|
{
|
|
647
|
-
log.warn('unknown attestation format: %s
|
|
647
|
+
log.warn('unknown attestation format: %s - treating as none', fmt);
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
// 8. Extract public key from COSE format
|
|
@@ -703,7 +703,7 @@ function _verifyPackedAttestation(attStmt, parsedAuthData, rawAuthData, clientDa
|
|
|
703
703
|
}
|
|
704
704
|
else
|
|
705
705
|
{
|
|
706
|
-
// Self-attestation
|
|
706
|
+
// Self-attestation - verify with the credential public key
|
|
707
707
|
const { key } = _coseToPublicKey(parsedAuthData.credentialPublicKey);
|
|
708
708
|
const algName = _coseAlgToNodeAlg(alg);
|
|
709
709
|
return crypto.verify(algName, signedData, key, sig);
|
|
@@ -881,7 +881,7 @@ function verifyAuthentication(opts)
|
|
|
881
881
|
if (!authData.userPresent)
|
|
882
882
|
return { verified: false, newCounter: null, error: 'User not present' };
|
|
883
883
|
|
|
884
|
-
// 5. Counter validation
|
|
884
|
+
// 5. Counter validation - detect cloned authenticators
|
|
885
885
|
if (authData.signCount > 0 || credential.counter > 0)
|
|
886
886
|
{
|
|
887
887
|
if (authData.signCount <= credential.counter)
|
package/lib/debug.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* log.error('failed to connect', err);
|
|
17
17
|
* log('shorthand for debug level');
|
|
18
18
|
*
|
|
19
|
-
* // Set minimum level
|
|
19
|
+
* // Set minimum level - anything below is silenced
|
|
20
20
|
* debug.level('warn'); // only warn, error, fatal
|
|
21
21
|
* debug.level('silent'); // suppress all output
|
|
22
22
|
* debug.level('trace'); // show everything
|
|
@@ -81,7 +81,7 @@ _enabledPatterns = _parsePatterns();
|
|
|
81
81
|
*/
|
|
82
82
|
function _isEnabled(ns)
|
|
83
83
|
{
|
|
84
|
-
if (!_enabledPatterns) return true; // No DEBUG set
|
|
84
|
+
if (!_enabledPatterns) return true; // No DEBUG set - enable all
|
|
85
85
|
let enabled = false;
|
|
86
86
|
for (const { neg, re } of _enabledPatterns)
|
|
87
87
|
{
|
|
@@ -116,7 +116,7 @@ function _ts()
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Format arguments (like console.log
|
|
119
|
+
* Format arguments (like console.log - supports %s, %d, %j, %o).
|
|
120
120
|
* @private
|
|
121
121
|
*/
|
|
122
122
|
function _format(args)
|
|
@@ -278,13 +278,13 @@ function debug(namespace)
|
|
|
278
278
|
* Messages below this level are silenced.
|
|
279
279
|
*
|
|
280
280
|
* @param {string|number} level - Level name or number.
|
|
281
|
-
* `'trace'` (0)
|
|
282
|
-
* `'debug'` (1)
|
|
283
|
-
* `'info'` (2)
|
|
284
|
-
* `'warn'` (3)
|
|
285
|
-
* `'error'` (4)
|
|
286
|
-
* `'fatal'` (5)
|
|
287
|
-
* `'silent'` (6)
|
|
281
|
+
* `'trace'` (0) - all output
|
|
282
|
+
* `'debug'` (1) - debug and above
|
|
283
|
+
* `'info'` (2) - info and above
|
|
284
|
+
* `'warn'` (3) - warn and above
|
|
285
|
+
* `'error'` (4) - error and fatal only
|
|
286
|
+
* `'fatal'` (5) - fatal only
|
|
287
|
+
* `'silent'` (6) - nothing
|
|
288
288
|
*/
|
|
289
289
|
debug.level = function(level)
|
|
290
290
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zero-server/auth",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "JWT, sessions, OAuth, authorize, MFA stack.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zero-server",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
},
|
|
46
46
|
"sideEffects": false,
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@zero-server/fetch": "0.9.
|
|
48
|
+
"@zero-server/fetch": "0.9.7"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@zero-server/sdk": ">=0.9.
|
|
51
|
+
"@zero-server/sdk": ">=0.9.7"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@zero-server/sdk": {
|
package/types/body.d.ts
CHANGED
package/types/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// Re-exports for @zero-server/cli
|
|
1
|
+
// Re-exports for @zero-server/cli - CLI runner types
|
|
2
2
|
export { CLI, runCLI } from './orm';
|
package/types/index.d.ts
CHANGED
|
@@ -11,6 +11,18 @@ export { RouterInstance, RouteChain, RouteEntry, RouteInfo, RouteOptions, RouteH
|
|
|
11
11
|
export { Request, RangeResult } from './request';
|
|
12
12
|
export { Response, SendFileOptions, CookieOptions, PushOptions } from './response';
|
|
13
13
|
export { SSEOptions, SSEStream } from './sse';
|
|
14
|
+
export {
|
|
15
|
+
createWebRTC, SignalingHub, Room as WebRTCRoom, Peer as WebRTCPeer,
|
|
16
|
+
parseSdp, stringifySdp, parseCandidate, stringifyCandidate,
|
|
17
|
+
stunBinding, encodeBindingRequest, decodeMessage,
|
|
18
|
+
encodeXorMappedAddress, decodeXorMappedAddress,
|
|
19
|
+
STUN_MAGIC_COOKIE, STUN_METHOD, STUN_CLASS, STUN_ATTR,
|
|
20
|
+
issueTurnCredentials, TurnServer, SfuAdapter,
|
|
21
|
+
signJoinToken, verifyJoinToken,
|
|
22
|
+
WebRTCOptions, RoomOptions as WebRTCRoomOptions, PeerInfo, SignalingMessage,
|
|
23
|
+
IceServerConfig, TurnCredentials, IssueTurnCredentialsOptions, ClusterAdapter as WebRTCClusterAdapter,
|
|
24
|
+
WebRTCError, SignalingError, IceError, TurnError, SdpError,
|
|
25
|
+
} from './webrtc';
|
|
14
26
|
export { LifecycleManager, LifecycleState, LIFECYCLE_STATE } from './lifecycle';
|
|
15
27
|
export { ClusterManager, ClusterOptions, cluster } from './cluster';
|
|
16
28
|
export {
|
|
@@ -294,10 +306,10 @@ declare const zeroServer: {
|
|
|
294
306
|
LIFECYCLE_STATE: typeof LIFECYCLE_STATE;
|
|
295
307
|
ClusterManager: typeof ClusterManager;
|
|
296
308
|
cluster: typeof clusterize;
|
|
297
|
-
// Observability
|
|
309
|
+
// Observability - Structured Logging
|
|
298
310
|
Logger: typeof Logger;
|
|
299
311
|
structuredLogger: typeof structuredLogger;
|
|
300
|
-
// Observability
|
|
312
|
+
// Observability - Metrics
|
|
301
313
|
Counter: typeof Counter;
|
|
302
314
|
Gauge: typeof Gauge;
|
|
303
315
|
Histogram: typeof Histogram;
|
|
@@ -306,14 +318,14 @@ declare const zeroServer: {
|
|
|
306
318
|
createDefaultMetrics: typeof createDefaultMetrics;
|
|
307
319
|
metricsMiddleware: typeof metricsMiddleware;
|
|
308
320
|
metricsEndpoint: typeof metricsEndpointHandler;
|
|
309
|
-
// Observability
|
|
321
|
+
// Observability - Tracing
|
|
310
322
|
Span: typeof Span;
|
|
311
323
|
Tracer: typeof Tracer;
|
|
312
324
|
parseTraceparent: typeof parseTraceparent;
|
|
313
325
|
formatTraceparent: typeof formatTraceparent;
|
|
314
326
|
tracingMiddleware: typeof tracingMiddleware;
|
|
315
327
|
instrumentFetch: typeof instrumentFetch;
|
|
316
|
-
// Observability
|
|
328
|
+
// Observability - Health Checks
|
|
317
329
|
healthCheck: typeof healthCheck;
|
|
318
330
|
createHealthHandlers: typeof createHealthHandlers;
|
|
319
331
|
memoryCheck: typeof memoryCheck;
|
package/types/middleware.d.ts
CHANGED
|
@@ -136,7 +136,7 @@ export interface CompressOptions {
|
|
|
136
136
|
level?: number;
|
|
137
137
|
/** Force specific encoding(s). */
|
|
138
138
|
encoding?: string | string[];
|
|
139
|
-
/** Filter function
|
|
139
|
+
/** Filter function - return false to skip compression. */
|
|
140
140
|
filter?: (req: Request, res: Response) => boolean;
|
|
141
141
|
}
|
|
142
142
|
|
package/types/orm.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface SchemaColumnDef {
|
|
|
29
29
|
enum?: string[];
|
|
30
30
|
/** Allowed values (set type). */
|
|
31
31
|
values?: string[];
|
|
32
|
-
/** Mass-assignment protection
|
|
32
|
+
/** Mass-assignment protection - exclude from bulk writes. */
|
|
33
33
|
guarded?: boolean;
|
|
34
34
|
/** Precision for decimal types. */
|
|
35
35
|
precision?: number;
|
|
@@ -229,7 +229,7 @@ export class Query {
|
|
|
229
229
|
take(n: number): Query;
|
|
230
230
|
/** Alias for offset (LINQ naming). */
|
|
231
231
|
skip(n: number): Query;
|
|
232
|
-
/** Alias for exec
|
|
232
|
+
/** Alias for exec - explicitly convert to array. */
|
|
233
233
|
toArray(): Promise<Model[]>;
|
|
234
234
|
/** Shorthand for orderBy(field, 'desc'). */
|
|
235
235
|
orderByDesc(field: string): Query;
|
|
@@ -260,7 +260,7 @@ export class Query {
|
|
|
260
260
|
/** Inject a raw WHERE clause for SQL adapters (ignored by memory/mongo). */
|
|
261
261
|
whereRaw(sql: string, ...params: any[]): Query;
|
|
262
262
|
|
|
263
|
-
/** Thenable support
|
|
263
|
+
/** Thenable support - `await query`. */
|
|
264
264
|
then<TResult1 = Model[], TResult2 = never>(
|
|
265
265
|
onfulfilled?: ((value: Model[]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
266
266
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
package/types/request.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface Request {
|
|
|
31
31
|
readonly ips: string[];
|
|
32
32
|
/** `true` when the connection is over TLS (trust-proxy-aware). */
|
|
33
33
|
readonly secure: boolean;
|
|
34
|
-
/** Protocol string
|
|
34
|
+
/** Protocol string - `'https'` or `'http'` (trust-proxy-aware). */
|
|
35
35
|
readonly protocol: 'http' | 'https';
|
|
36
36
|
/** HTTP version string (e.g. '1.1', '2.0'). */
|
|
37
37
|
httpVersion: string;
|
|
@@ -49,7 +49,7 @@ export interface Request {
|
|
|
49
49
|
id?: string;
|
|
50
50
|
/** Whether the request timed out (populated by timeout middleware). */
|
|
51
51
|
timedOut?: boolean;
|
|
52
|
-
/** The original URL as received
|
|
52
|
+
/** The original URL as received - never rewritten by middleware. */
|
|
53
53
|
originalUrl: string;
|
|
54
54
|
/** The URL path on which the current router was mounted. */
|
|
55
55
|
baseUrl: string;
|
|
@@ -83,7 +83,7 @@ export interface Request {
|
|
|
83
83
|
subdomains(offset?: number): string[];
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
* Content negotiation
|
|
86
|
+
* Content negotiation - check which types the client accepts.
|
|
87
87
|
*/
|
|
88
88
|
accepts(...types: string[]): string | false;
|
|
89
89
|
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript surface for @zero-server/webrtc.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the runtime surface exported from `lib/webrtc/index.js` and
|
|
5
|
+
* re-exported from the top-level SDK in `index.js`. Every entry listed
|
|
6
|
+
* in `.tools/scope-manifest.js` under the `webrtc` scope MUST have a
|
|
7
|
+
* matching declaration here - this is enforced by
|
|
8
|
+
* `test/packages/webrtc-types.test.js`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { EventEmitter } from 'node:events';
|
|
12
|
+
import type { KeyObject } from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Shared option / config types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface IceServerConfig {
|
|
19
|
+
urls: string | string[];
|
|
20
|
+
username?: string;
|
|
21
|
+
credential?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TurnCredentials {
|
|
25
|
+
urls: string[];
|
|
26
|
+
username: string;
|
|
27
|
+
credential: string;
|
|
28
|
+
ttl: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IssueTurnCredentialsOptions {
|
|
32
|
+
secret: string;
|
|
33
|
+
userId: string;
|
|
34
|
+
ttl?: string | number;
|
|
35
|
+
servers: string[];
|
|
36
|
+
realm?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SignalingHubOptions {
|
|
40
|
+
maxSdpSize?: number;
|
|
41
|
+
maxCandidatesPerOffer?: number;
|
|
42
|
+
peerMessageRate?: number;
|
|
43
|
+
maxProtocolErrors?: number;
|
|
44
|
+
ipAttachRate?: number;
|
|
45
|
+
originAllowlist?: string[];
|
|
46
|
+
joinTokenSecret?: string | Buffer;
|
|
47
|
+
autoCreateRooms?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface WebRTCOptions extends SignalingHubOptions {
|
|
51
|
+
path?: string;
|
|
52
|
+
iceServers?: IceServerConfig[] | 'auto';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PeerAttachInfo {
|
|
56
|
+
user?: unknown;
|
|
57
|
+
ip?: string;
|
|
58
|
+
origin?: string;
|
|
59
|
+
[extra: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PeerTransport {
|
|
63
|
+
send(data: string): void;
|
|
64
|
+
on(event: 'message' | 'close', cb: (...args: unknown[]) => void): void;
|
|
65
|
+
close(code?: number, reason?: string): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// SDP / ICE helpers
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export interface ParsedSdpMedia {
|
|
73
|
+
type: string;
|
|
74
|
+
port: number;
|
|
75
|
+
proto: string;
|
|
76
|
+
formats: string[];
|
|
77
|
+
iceUfrag?: string;
|
|
78
|
+
icePwd?: string;
|
|
79
|
+
fingerprint?: { algorithm: string; hash: string };
|
|
80
|
+
candidates?: ParsedIceCandidate[];
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ParsedSdp {
|
|
85
|
+
version: number;
|
|
86
|
+
origin: Record<string, unknown>;
|
|
87
|
+
sessionName: string;
|
|
88
|
+
media: ParsedSdpMedia[];
|
|
89
|
+
[key: string]: unknown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ParsedIceCandidate {
|
|
93
|
+
foundation: string;
|
|
94
|
+
component: number;
|
|
95
|
+
transport: string;
|
|
96
|
+
priority: number;
|
|
97
|
+
address: string;
|
|
98
|
+
port: number;
|
|
99
|
+
type: string;
|
|
100
|
+
relatedAddress?: string;
|
|
101
|
+
relatedPort?: number;
|
|
102
|
+
tcpType?: string;
|
|
103
|
+
[key: string]: unknown;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export declare function parseSdp(sdp: string, opts?: { maxBytes?: number }): ParsedSdp;
|
|
107
|
+
export declare function stringifySdp(parsed: ParsedSdp): string;
|
|
108
|
+
|
|
109
|
+
export declare function parseCandidate(line: string): ParsedIceCandidate;
|
|
110
|
+
export declare function stringifyCandidate(parsed: ParsedIceCandidate): string;
|
|
111
|
+
export declare function filterCandidates(
|
|
112
|
+
candidates: ParsedIceCandidate[],
|
|
113
|
+
opts?: { allowPrivate?: boolean; allowLoopback?: boolean; allowLinkLocal?: boolean; allowMdns?: boolean }
|
|
114
|
+
): ParsedIceCandidate[];
|
|
115
|
+
|
|
116
|
+
export declare function isPrivateIp(addr: string): boolean;
|
|
117
|
+
export declare function isLoopbackIp(addr: string): boolean;
|
|
118
|
+
export declare function isLinkLocalIp(addr: string): boolean;
|
|
119
|
+
export declare function isMdnsHostname(addr: string): boolean;
|
|
120
|
+
|
|
121
|
+
export declare const CANDIDATE_TYPES: Readonly<{ HOST: string; SRFLX: string; PRFLX: string; RELAY: string }>;
|
|
122
|
+
export declare const TCP_TYPES: Readonly<{ ACTIVE: string; PASSIVE: string; SO: string }>;
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// STUN / TURN
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
export declare function stunBinding(opts: {
|
|
129
|
+
host: string;
|
|
130
|
+
port?: number;
|
|
131
|
+
timeoutMs?: number;
|
|
132
|
+
retries?: number;
|
|
133
|
+
socketType?: 'udp4' | 'udp6';
|
|
134
|
+
}): Promise<{ family: 4 | 6; address: string; port: number }>;
|
|
135
|
+
|
|
136
|
+
export declare function encodeBindingRequest(transactionId?: Buffer): { buffer: Buffer; transactionId: Buffer };
|
|
137
|
+
export declare function decodeMessage(buf: Buffer): {
|
|
138
|
+
method: number;
|
|
139
|
+
class: number;
|
|
140
|
+
transactionId: Buffer;
|
|
141
|
+
attributes: Array<{ type: number; value: Buffer }>;
|
|
142
|
+
};
|
|
143
|
+
export declare function encodeXorMappedAddress(address: string, port: number, transactionId: Buffer): Buffer;
|
|
144
|
+
export declare function decodeXorMappedAddress(value: Buffer, transactionId: Buffer): { family: 4 | 6; address: string; port: number };
|
|
145
|
+
|
|
146
|
+
export declare const STUN_MAGIC_COOKIE: number;
|
|
147
|
+
export declare const STUN_METHOD: Readonly<{ BINDING: number }>;
|
|
148
|
+
export declare const STUN_CLASS: Readonly<{ REQUEST: number; INDICATION: number; SUCCESS: number; ERROR: number }>;
|
|
149
|
+
export declare const STUN_ATTR: Readonly<{ MAPPED_ADDRESS: number; XOR_MAPPED_ADDRESS: number; ERROR_CODE: number; SOFTWARE: number }>;
|
|
150
|
+
|
|
151
|
+
export declare function issueTurnCredentials(opts: IssueTurnCredentialsOptions): TurnCredentials;
|
|
152
|
+
|
|
153
|
+
export declare class TurnServer {
|
|
154
|
+
constructor(opts: {
|
|
155
|
+
secret: string;
|
|
156
|
+
realm?: string;
|
|
157
|
+
listeners: Array<{ proto: 'udp' | 'tcp' | 'tls'; port: number; host?: string; tls?: { cert: Buffer; key: Buffer } }>;
|
|
158
|
+
quotas?: { maxAllocationsPerUser?: number; maxBytesPerMinute?: number };
|
|
159
|
+
defaultLifetime?: number;
|
|
160
|
+
maxLifetime?: number;
|
|
161
|
+
relayHost?: string;
|
|
162
|
+
});
|
|
163
|
+
readonly realm: string;
|
|
164
|
+
start(): Promise<void>;
|
|
165
|
+
stop(): Promise<void>;
|
|
166
|
+
address(): { address: string; port: number } | null;
|
|
167
|
+
on(event: 'allocation', listener: (ev: { userId: string; relay: { address: string; port: number }; client: { address: string; port: number } }) => void): this;
|
|
168
|
+
on(event: 'deallocation', listener: (ev: { userId: string; client: { address: string; port: number }; reason?: string }) => void): this;
|
|
169
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
170
|
+
on(event: string, listener: (...args: unknown[]) => void): this;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Signaling core
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
export type PeerState = 'stable' | 'have-local-offer' | 'have-remote-offer';
|
|
178
|
+
|
|
179
|
+
export declare const PEER_STATE: Readonly<{
|
|
180
|
+
STABLE: 'stable';
|
|
181
|
+
HAVE_LOCAL_OFFER: 'have-local-offer';
|
|
182
|
+
HAVE_REMOTE_OFFER: 'have-remote-offer';
|
|
183
|
+
}>;
|
|
184
|
+
|
|
185
|
+
export declare class Peer {
|
|
186
|
+
readonly id: string;
|
|
187
|
+
readonly user: unknown;
|
|
188
|
+
readonly ip: string | null;
|
|
189
|
+
readonly transport: PeerTransport;
|
|
190
|
+
state: PeerState;
|
|
191
|
+
room: Room | null;
|
|
192
|
+
errors: number;
|
|
193
|
+
readonly connectedAt: number;
|
|
194
|
+
closed: boolean;
|
|
195
|
+
e2ee?: E2eeChannel;
|
|
196
|
+
constructor(transport: PeerTransport, info?: PeerAttachInfo);
|
|
197
|
+
send(type: string, payload?: object): void;
|
|
198
|
+
sendError(code: string, message: string): void;
|
|
199
|
+
close(code?: number, reason?: string): void;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export declare class Room {
|
|
203
|
+
readonly name: string;
|
|
204
|
+
readonly hub: SignalingHub | null;
|
|
205
|
+
isOpen: boolean;
|
|
206
|
+
constructor(name: string, opts?: { hub?: SignalingHub });
|
|
207
|
+
open(): this;
|
|
208
|
+
require(fn: (peer: Peer) => boolean | Promise<boolean>): this;
|
|
209
|
+
canPublish(fn: (peer: Peer) => boolean): this;
|
|
210
|
+
canSubscribe(fn: (peer: Peer) => boolean): this;
|
|
211
|
+
readonly size: number;
|
|
212
|
+
peers(): Peer[];
|
|
213
|
+
canJoin(peer: Peer): boolean | Promise<boolean>;
|
|
214
|
+
broadcast(type: string, payload?: object, exceptPeerId?: string): void;
|
|
215
|
+
close(reason?: string): void;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface SignalingHubEvents {
|
|
219
|
+
join: (ev: { peer: Peer; room: Room }) => void;
|
|
220
|
+
leave: (ev: { peer: Peer; room: Room }) => void;
|
|
221
|
+
offer: (ev: { peer: Peer; target: Peer | null; room: Room; sdp: string }) => void;
|
|
222
|
+
answer: (ev: { peer: Peer; target: Peer | null; room: Room; sdp: string }) => void;
|
|
223
|
+
signal: (ev: { peer: Peer; type: string }) => void;
|
|
224
|
+
joinFailed: (ev: { peer: Peer; reason: string; room?: string }) => void;
|
|
225
|
+
publishFailed: (ev: { peer: Peer; reason: string; room: string }) => void;
|
|
226
|
+
subscribeFailed: (ev: { peer: Peer; reason: string; room: string }) => void;
|
|
227
|
+
wireError: (ev: { peer: Peer; code: string }) => void;
|
|
228
|
+
e2eeKey: (ev: { peer: Peer; room: Room; epoch: number; key: string }) => void;
|
|
229
|
+
clusterError: (err: Error) => void;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export declare class SignalingHub extends EventEmitter {
|
|
233
|
+
constructor(opts?: SignalingHubOptions);
|
|
234
|
+
readonly size: number;
|
|
235
|
+
room(name: string): Room;
|
|
236
|
+
rooms(): Room[];
|
|
237
|
+
attach(transport: PeerTransport, info?: PeerAttachInfo): Peer;
|
|
238
|
+
close(): void;
|
|
239
|
+
on<E extends keyof SignalingHubEvents>(event: E, listener: SignalingHubEvents[E]): this;
|
|
240
|
+
on(event: string, listener: (...args: unknown[]) => void): this;
|
|
241
|
+
off<E extends keyof SignalingHubEvents>(event: E, listener: SignalingHubEvents[E]): this;
|
|
242
|
+
off(event: string, listener: (...args: unknown[]) => void): this;
|
|
243
|
+
emit<E extends keyof SignalingHubEvents>(event: E, ...args: Parameters<SignalingHubEvents[E]>): boolean;
|
|
244
|
+
emit(event: string, ...args: unknown[]): boolean;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export declare function createWebRTC(app: unknown, opts?: WebRTCOptions): SignalingHub;
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Join tokens
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
export interface SignJoinTokenOptions {
|
|
254
|
+
secret: string | Buffer;
|
|
255
|
+
user: string | { id?: string; userId?: string; sub?: string; [k: string]: unknown };
|
|
256
|
+
room: string;
|
|
257
|
+
ttl?: number;
|
|
258
|
+
claims?: Record<string, unknown>;
|
|
259
|
+
algorithm?: string;
|
|
260
|
+
audience?: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface VerifyJoinTokenOptions {
|
|
264
|
+
secret: string | Buffer;
|
|
265
|
+
room?: string;
|
|
266
|
+
audience?: string | string[];
|
|
267
|
+
algorithms?: string | string[];
|
|
268
|
+
clockTolerance?: number;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export declare function signJoinToken(opts: SignJoinTokenOptions): string;
|
|
272
|
+
|
|
273
|
+
export declare function verifyJoinToken(
|
|
274
|
+
token: string,
|
|
275
|
+
opts: VerifyJoinTokenOptions
|
|
276
|
+
): { room: string; user: unknown; sub?: string; aud?: string; [k: string]: unknown };
|
|
277
|
+
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Observability
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
export interface ObservabilityBindOptions {
|
|
283
|
+
metrics?: unknown;
|
|
284
|
+
tracer?: unknown;
|
|
285
|
+
prefix?: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export declare function bindObservability(
|
|
289
|
+
hub: SignalingHub,
|
|
290
|
+
opts?: ObservabilityBindOptions
|
|
291
|
+
): () => void;
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// E2EE key relay
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
export interface E2eeKeyEvent {
|
|
298
|
+
from: string;
|
|
299
|
+
epoch: number;
|
|
300
|
+
key: Buffer;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export declare class E2eeChannel {
|
|
304
|
+
readonly peer: Peer;
|
|
305
|
+
readonly hub: SignalingHub;
|
|
306
|
+
epoch: number;
|
|
307
|
+
constructor(peer: Peer, hub: SignalingHub);
|
|
308
|
+
publish(epoch: number | null, key: Buffer | Uint8Array | string): number;
|
|
309
|
+
subscribe(fn: (ev: E2eeKeyEvent) => void): () => void;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export declare function attachE2ee(peer: Peer, hub: SignalingHub): E2eeChannel;
|
|
313
|
+
export declare function generateE2eeKeyPair(): { publicKey: KeyObject; privateKey: KeyObject };
|
|
314
|
+
export declare function sealKey(plaintext: Buffer | Uint8Array, recipientPubKey: KeyObject | Buffer): Buffer;
|
|
315
|
+
export declare function openSealedKey(sealed: Buffer | Uint8Array, recipientPrivKey: KeyObject | Buffer): Buffer;
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Cluster adapter
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
export interface ClusterAdapter {
|
|
322
|
+
publish(channel: string, message: unknown): void | Promise<void>;
|
|
323
|
+
subscribe(channel: string, handler: (message: unknown) => void): (() => void) | void;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface UseClusterOptions {
|
|
327
|
+
nodeId?: string;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export declare class ClusterCoordinator {
|
|
331
|
+
readonly hub: SignalingHub;
|
|
332
|
+
readonly adapter: ClusterAdapter;
|
|
333
|
+
readonly nodeId: string;
|
|
334
|
+
constructor(hub: SignalingHub, adapter: ClusterAdapter, opts?: UseClusterOptions);
|
|
335
|
+
locate(peerId: string): { nodeId: string; room: string } | null;
|
|
336
|
+
routeDirect(toPeerId: string, type: string, payload: object): boolean;
|
|
337
|
+
fanoutRoom(roomName: string, type: string, payload: object, excludeId?: string): void;
|
|
338
|
+
close(): void;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export declare function useCluster(
|
|
342
|
+
hub: SignalingHub,
|
|
343
|
+
adapter: ClusterAdapter,
|
|
344
|
+
opts?: UseClusterOptions
|
|
345
|
+
): ClusterCoordinator;
|
|
346
|
+
|
|
347
|
+
export declare class MemoryClusterAdapter implements ClusterAdapter {
|
|
348
|
+
publish(channel: string, message: unknown): void;
|
|
349
|
+
subscribe(channel: string, handler: (message: unknown) => void): () => void;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// CLI
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
export interface WebRTCCommandDeps {
|
|
357
|
+
out?: (line: string) => void;
|
|
358
|
+
err?: (line: string) => void;
|
|
359
|
+
setExit?: (code: number) => void;
|
|
360
|
+
stunBinding?: typeof stunBinding;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export declare function runWebRTCCommand(
|
|
364
|
+
subcmd: 'stun' | 'turn-creds' | 'join-token' | 'verify-token' | 'help' | string,
|
|
365
|
+
flags?: Map<string, string>,
|
|
366
|
+
deps?: WebRTCCommandDeps
|
|
367
|
+
): Promise<number>;
|
|
368
|
+
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
// SFU adapter (interface only - real implementations land in later PRs)
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
export interface SfuPeerInfo {
|
|
374
|
+
id: string;
|
|
375
|
+
user?: unknown;
|
|
376
|
+
room: string;
|
|
377
|
+
joinedAt: number;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface SfuRouter { id: string; routerId: string; }
|
|
381
|
+
export interface SfuTransport {
|
|
382
|
+
id: string;
|
|
383
|
+
transportId: string;
|
|
384
|
+
routerId: string;
|
|
385
|
+
peer: SfuPeerInfo | null;
|
|
386
|
+
iceParameters: unknown;
|
|
387
|
+
dtlsParameters: unknown;
|
|
388
|
+
}
|
|
389
|
+
export interface SfuProducer {
|
|
390
|
+
id: string;
|
|
391
|
+
producerId: string;
|
|
392
|
+
transportId: string;
|
|
393
|
+
kind: 'audio' | 'video';
|
|
394
|
+
rtpParams: unknown;
|
|
395
|
+
paused: boolean;
|
|
396
|
+
}
|
|
397
|
+
export interface SfuConsumer {
|
|
398
|
+
id: string;
|
|
399
|
+
consumerId: string;
|
|
400
|
+
transportId: string;
|
|
401
|
+
producerId: string;
|
|
402
|
+
kind: 'audio' | 'video';
|
|
403
|
+
rtpParams: unknown;
|
|
404
|
+
rtpCaps: unknown;
|
|
405
|
+
}
|
|
406
|
+
export interface SfuStats {
|
|
407
|
+
kind: 'global' | 'router' | 'transport';
|
|
408
|
+
[key: string]: unknown;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export type SfuEventHandler = (event: string, payload: unknown) => void;
|
|
412
|
+
|
|
413
|
+
export declare class SfuAdapter {
|
|
414
|
+
constructor();
|
|
415
|
+
createRouter(opts?: unknown): Promise<SfuRouter>;
|
|
416
|
+
createTransport(router: SfuRouter, peer: SfuPeerInfo): Promise<SfuTransport>;
|
|
417
|
+
produce(transport: SfuTransport, kind: 'audio' | 'video', rtpParams: unknown): Promise<SfuProducer>;
|
|
418
|
+
consume(transport: SfuTransport, producerId: string, rtpCaps: unknown): Promise<SfuConsumer>;
|
|
419
|
+
pauseProducer(producerId: string): Promise<void>;
|
|
420
|
+
resumeProducer(producerId: string): Promise<void>;
|
|
421
|
+
closeRouter(routerId: string): Promise<void>;
|
|
422
|
+
stats(scope?: string): Promise<SfuStats>;
|
|
423
|
+
onEvent(handler: SfuEventHandler): () => void;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export declare class MemorySfuAdapter extends SfuAdapter {
|
|
427
|
+
constructor(opts?: Record<string, unknown>);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export interface MediasoupAdapterOptions {
|
|
431
|
+
mediasoup?: unknown;
|
|
432
|
+
worker?: unknown;
|
|
433
|
+
workerSettings?: Record<string, unknown>;
|
|
434
|
+
mediaCodecs?: Array<Record<string, unknown>>;
|
|
435
|
+
webRtcTransportOptions?: Record<string, unknown>;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export declare class MediasoupSfuAdapter extends SfuAdapter {
|
|
439
|
+
constructor(opts?: MediasoupAdapterOptions);
|
|
440
|
+
close(): Promise<void>;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export interface LiveKitAdapterOptions {
|
|
444
|
+
url: string;
|
|
445
|
+
apiKey: string;
|
|
446
|
+
apiSecret: string;
|
|
447
|
+
livekit?: unknown;
|
|
448
|
+
client?: unknown;
|
|
449
|
+
defaultRoomOpts?: Record<string, unknown>;
|
|
450
|
+
defaultGrants?: Record<string, unknown>;
|
|
451
|
+
tokenTtl?: string | number;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export declare class LiveKitSfuAdapter extends SfuAdapter {
|
|
455
|
+
constructor(opts: LiveKitAdapterOptions);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export declare function loadSfuAdapter(
|
|
459
|
+
spec: SfuAdapter | 'memory' | 'mediasoup' | 'livekit' | string,
|
|
460
|
+
opts?: Record<string, unknown>,
|
|
461
|
+
): SfuAdapter;
|
|
462
|
+
|
|
463
|
+
// ---------------------------------------------------------------------------
|
|
464
|
+
// Server-side bot peer (wrtc)
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
|
|
467
|
+
export interface BotPeerOptions {
|
|
468
|
+
hub: SignalingHub;
|
|
469
|
+
room: string;
|
|
470
|
+
user?: unknown;
|
|
471
|
+
ip?: string;
|
|
472
|
+
joinToken?: string;
|
|
473
|
+
iceServers?: Array<Record<string, unknown>>;
|
|
474
|
+
rtcConfig?: Record<string, unknown>;
|
|
475
|
+
wrtc?: unknown;
|
|
476
|
+
onTrack?: (track: unknown, streams: unknown[], fromPeerId: string) => void;
|
|
477
|
+
onDataChannel?: (channel: unknown, fromPeerId: string) => void;
|
|
478
|
+
onPeerJoin?: (remotePeerId: string) => void;
|
|
479
|
+
onPeerLeave?: (remotePeerId: string) => void;
|
|
480
|
+
onError?: (err: Error) => void;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export interface BotPeerHandle {
|
|
484
|
+
peer: Peer;
|
|
485
|
+
peerConnections: Map<string, unknown>;
|
|
486
|
+
getPeerConnection: (remotePeerId: string) => unknown | undefined;
|
|
487
|
+
ready: Promise<{ peerId: string }>;
|
|
488
|
+
close: () => void;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export declare function spawnBotPeer(opts: BotPeerOptions): BotPeerHandle;
|
|
492
|
+
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
// Errors
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
|
|
497
|
+
export declare class WebRTCError extends Error { readonly code: string; }
|
|
498
|
+
export declare class SignalingError extends WebRTCError {}
|
|
499
|
+
export declare class IceError extends WebRTCError {}
|
|
500
|
+
export declare class TurnError extends WebRTCError {}
|
|
501
|
+
export declare class SdpError extends WebRTCError {}
|