@zero-server/sdk 0.9.8 → 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-7777%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({
@@ -83,6 +89,16 @@ function _countCandidatesInSdp(sdp)
83
89
  * Validate that an SDP has the required RFC 8829 attributes on every media
84
90
  * section and uses DTLS-SRTP transport. Returns an error code string, or
85
91
  * `null` if the SDP is acceptable.
92
+ *
93
+ * Accepts both RTP/SAVPF audio/video sections (RFC 8829) and SCTP data-channel
94
+ * sections (RFC 8841 m=application ... UDP/DTLS/SCTP). When BUNDLE is in use
95
+ * (every browser since ~2018), only the first m-section is required to carry
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.
86
102
  */
87
103
  function _validateSdpStructure(sdp)
88
104
  {
@@ -94,12 +110,49 @@ function _validateSdpStructure(sdp)
94
110
  return 'INVALID_SDP';
95
111
  }
96
112
  if (!desc.media || desc.media.length === 0) return 'INVALID_SDP';
113
+
114
+ // BUNDLE: collect bundled mids so per-section ice credentials are optional
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).
119
+ const bundleMids = new Set();
120
+ let sessFingerprint = null, sessIceUfrag = null, sessIcePwd = null;
121
+ for (const a of desc.attributes || [])
122
+ {
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;
132
+ }
133
+
134
+ let firstBundleMid = null;
97
135
  for (const m of desc.media)
98
136
  {
99
- if (typeof m.proto !== 'string' || !/^UDP\/TLS\/RTP\/SAVPF?$/i.test(m.proto))
100
- return 'INVALID_SDP';
101
- if (!m.iceUfrag || !m.icePwd) return 'INVALID_SDP';
102
- if (!m.fingerprint) return 'INVALID_SDP';
137
+ if (typeof m.proto !== 'string') return 'INVALID_SDP';
138
+
139
+ // RTP audio/video (RFC 8829) OR SCTP data channels (RFC 8841).
140
+ const isRtp = /^UDP\/TLS\/RTP\/SAVPF?$/i.test(m.proto);
141
+ const isSctp = /^(UDP|TCP)\/DTLS\/SCTP$/i.test(m.proto);
142
+ if (!isRtp && !isSctp) return 'INVALID_SDP';
143
+
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';
149
+
150
+ // ice-ufrag / ice-pwd: required on the BUNDLE owner; optional on
151
+ // every other bundled section (inherited per RFC 8843 §9.2).
152
+ const bundled = m.mid && bundleMids.has(m.mid);
153
+ if (bundled && firstBundleMid === null) firstBundleMid = m.mid;
154
+ const isBundleOwner = !bundled || m.mid === firstBundleMid;
155
+ if (isBundleOwner && (!ufrag || !pwd)) return 'INVALID_SDP';
103
156
  }
104
157
  return null;
105
158
  }
@@ -110,6 +163,10 @@ function _validateSdpStructure(sdp)
110
163
  * Central WebRTC signaling broker. Owns rooms, attaches peers, validates
111
164
  * JSEP traffic, and emits `join` / `leave` / `error` lifecycle events.
112
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
+ *
113
170
  * @class
114
171
  * @section Signaling
115
172
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zero-server/sdk",
3
- "version": "0.9.8",
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": {