@zero-server/sdk 0.9.9 → 0.9.10

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 CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  <p align="center">
14
14
  <a href="https://github.com/tonywied17/zero-server/actions"><img src="https://img.shields.io/github/actions/workflow/status/tonywied17/zero-server/ci.yml?branch=main&style=flat-square&logo=githubactions&logoColor=white&label=CI" alt="CI"></a>
15
- <a href="https://github.com/tonywied17/zero-server/actions"><img src="https://img.shields.io/badge/tests-7780%20passing-brightgreen?style=flat-square&logo=vitest&logoColor=white" alt="tests"></a>
15
+ <a href="https://github.com/tonywied17/zero-server/actions"><img src="https://img.shields.io/badge/tests-7781%20passing-brightgreen?style=flat-square&logo=vitest&logoColor=white" alt="tests"></a>
16
16
  <a href="https://github.com/tonywied17/zero-server"><img src="https://img.shields.io/badge/coverage-95.85%25-brightgreen?style=flat-square&logo=vitest&logoColor=white" alt="coverage"></a>
17
17
  <a href="https://z-server.dev"><img src="https://img.shields.io/badge/docs-z--server.dev-00d8e0?style=flat-square&logo=readthedocs&logoColor=white" alt="docs"></a>
18
18
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-00d8e0?style=flat-square&logo=opensourceinitiative&logoColor=white" alt="MIT"></a>
@@ -5,6 +5,12 @@
5
5
  * offer / answer / ICE traffic. Transport-agnostic — bind to `app.ws()`
6
6
  * in production, an `EventEmitter` shim in tests.
7
7
  *
8
+ * SDP validation is cross-browser by design: session-level
9
+ * `a=fingerprint` / `a=ice-ufrag` / `a=ice-pwd` lines are accepted as a
10
+ * fallback for media sections that omit their own copy (RFC 8839 §5.4,
11
+ * RFC 8122 §5). This is required for Firefox interop — Firefox emits
12
+ * `a=fingerprint` only at session level.
13
+ *
8
14
  * @example | Bind a hub to an `app.ws()` route with all production knobs
9
15
  * const app = createApp();
10
16
  * const hub = new SignalingHub({
@@ -88,6 +94,11 @@ function _countCandidatesInSdp(sdp)
88
94
  * sections (RFC 8841 m=application ... UDP/DTLS/SCTP). When BUNDLE is in use
89
95
  * (every browser since ~2018), only the first m-section is required to carry
90
96
  * iceUfrag/icePwd; other bundled sections inherit them via a=group:BUNDLE.
97
+ *
98
+ * Session-level `a=fingerprint`, `a=ice-ufrag`, and `a=ice-pwd` lines are
99
+ * honoured: per RFC 8839 §5.4 and RFC 8122 §5 they apply to every media
100
+ * section that omits its own copy. Firefox emits `a=fingerprint` only at
101
+ * session level, so this fallback is required for cross-browser interop.
91
102
  */
92
103
  function _validateSdpStructure(sdp)
93
104
  {
@@ -102,13 +113,22 @@ function _validateSdpStructure(sdp)
102
113
 
103
114
  // BUNDLE: collect bundled mids so per-section ice credentials are optional
104
115
  // on every section except the first (the BUNDLE owner).
116
+ // Also collect session-level fingerprint / ice-ufrag / ice-pwd, which per
117
+ // RFC 8839 §5.4 and RFC 8122 §5 apply to every media section that omits
118
+ // its own copy (Firefox emits fingerprint only at session level).
105
119
  const bundleMids = new Set();
120
+ let sessFingerprint = null, sessIceUfrag = null, sessIcePwd = null;
106
121
  for (const a of desc.attributes || [])
107
122
  {
108
- if (a.key !== 'group') continue;
109
- const parts = String(a.value || '').split(/\s+/);
110
- if (parts[0] !== 'BUNDLE') continue;
111
- for (let i = 1; i < parts.length; i++) bundleMids.add(parts[i]);
123
+ if (a.key === 'group')
124
+ {
125
+ const parts = String(a.value || '').split(/\s+/);
126
+ if (parts[0] === 'BUNDLE')
127
+ for (let i = 1; i < parts.length; i++) bundleMids.add(parts[i]);
128
+ }
129
+ else if (a.key === 'fingerprint' && a.value) sessFingerprint = a.value;
130
+ else if (a.key === 'ice-ufrag' && a.value) sessIceUfrag = a.value;
131
+ else if (a.key === 'ice-pwd' && a.value) sessIcePwd = a.value;
112
132
  }
113
133
 
114
134
  let firstBundleMid = null;
@@ -121,14 +141,18 @@ function _validateSdpStructure(sdp)
121
141
  const isSctp = /^(UDP|TCP)\/DTLS\/SCTP$/i.test(m.proto);
122
142
  if (!isRtp && !isSctp) return 'INVALID_SDP';
123
143
 
124
- if (!m.fingerprint) return 'INVALID_SDP';
144
+ const fp = m.fingerprint || sessFingerprint;
145
+ const ufrag = m.iceUfrag || sessIceUfrag;
146
+ const pwd = m.icePwd || sessIcePwd;
147
+
148
+ if (!fp) return 'INVALID_SDP';
125
149
 
126
150
  // ice-ufrag / ice-pwd: required on the BUNDLE owner; optional on
127
151
  // every other bundled section (inherited per RFC 8843 §9.2).
128
152
  const bundled = m.mid && bundleMids.has(m.mid);
129
153
  if (bundled && firstBundleMid === null) firstBundleMid = m.mid;
130
154
  const isBundleOwner = !bundled || m.mid === firstBundleMid;
131
- if (isBundleOwner && (!m.iceUfrag || !m.icePwd)) return 'INVALID_SDP';
155
+ if (isBundleOwner && (!ufrag || !pwd)) return 'INVALID_SDP';
132
156
  }
133
157
  return null;
134
158
  }
@@ -139,6 +163,10 @@ function _validateSdpStructure(sdp)
139
163
  * Central WebRTC signaling broker. Owns rooms, attaches peers, validates
140
164
  * JSEP traffic, and emits `join` / `leave` / `error` lifecycle events.
141
165
  *
166
+ * Interop: SDP validation accepts session-level `a=fingerprint`,
167
+ * `a=ice-ufrag`, and `a=ice-pwd` as a fallback when a media section omits
168
+ * them (RFC 8839 §5.4 / RFC 8122 §5). Required for Firefox.
169
+ *
142
170
  * @class
143
171
  * @section Signaling
144
172
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zero-server/sdk",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Zero-dependency backend framework for Node.js - routing, ORM, auth, WebSocket, SSE, gRPC, observability, and 20+ middleware. Distributed as a single SDK and as scoped @zero-server/* packages.",
5
5
  "main": "index.js",
6
6
  "bin": {