@origonai/web-sdk 0.1.1 → 0.1.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.
@@ -82,7 +82,7 @@ Document a min-supported-browser policy with the product team.
82
82
  |---|---|---|
83
83
  | Bidi QUIC stream | `wt.createBidirectionalStream()` (client-opened) | ControlMessages — Connect, NegotiateMedia, Hold, Mute, SendDtmf, Resume + their `*Ok` + server-push |
84
84
  | QUIC datagrams | `wt.datagrams` (readable + writable streams) | Audio frames (one frame per datagram), uplink + downlink |
85
- | QUIC uni stream (server→client) | `wt.incomingUnidirectionalStreams` | DTMF events when the peer pressed a digit |
85
+ | QUIC uni stream (server→client) | `wt.incomingUnidirectionalStreams` | Drained as unsupported protocol input; no public receive lane |
86
86
  | QUIC graceful close | `wt.close({ closeCode, reason })` | Hangup |
87
87
 
88
88
  Everything in [`client-integration.md`](client-integration.md) §4–§12 applies
@@ -154,8 +154,8 @@ After `ConnectOk` the bidi multiplexes:
154
154
  - Your verbs out (`Hold`, `Mute`, `SendDtmf`, `Resume`, `NegotiateMedia`).
155
155
  - Server `*Ok` responses (correlate by `request_id`).
156
156
  - Server-push events (`PeerAttached`, `PeerDetached`, `EndpointHeld`,
157
- `EndpointMuted`, `PeerStateChanged`, `DtmfReceived`, `EndpointHangup`,
158
- `MoqError`) — these carry `request_id = 0`.
157
+ `EndpointMuted`, `PeerStateChanged`, `EndpointHangup`, `MoqError`) — these
158
+ carry `request_id = 0`.
159
159
 
160
160
  A single read loop on `ctrl.readable.getReader()` is sufficient. Dispatch
161
161
  by `msg_type`.
@@ -232,30 +232,18 @@ manual PLC). Server never retransmits.
232
232
 
233
233
  ---
234
234
 
235
- ## 6. DTMF inbound (unidirectional sub-stream)
235
+ ## 6. DTMF flow input
236
236
 
237
- DTMF *from a peer* arrives on a fresh server-initiated uni stream, one
238
- per digit:
237
+ `SessionManager.sendDtmf(sessionId, digit)` sends exactly one symbol from
238
+ `0-9*#A-D` as `SendDtmf` on the bidi control stream. It resolves only after
239
+ the matching `SendDtmfOk`, which acknowledges Media publication to the active
240
+ CX flow. Request-scoped Media errors reject only the matching call.
239
241
 
240
- ```js
241
- const uniReader = wt.incomingUnidirectionalStreams.getReader()
242
- while (true) {
243
- const { value: stream, done } = await uniReader.read()
244
- if (done) break
245
- // stream is a ReadableStream<Uint8Array>
246
- handleDtmfUniStream(stream) // decodes SubgroupHeader + DtmfEvent + EndOfGroup
247
- }
248
- ```
249
-
250
- The framing on this stream is documented in
251
- [`docs/wire-protocol.md`](wire-protocol.md) §"Stream paths (non-audio)".
252
- The payload is a single `DtmfEvent { digit, duration_ms }` protobuf
253
- message.
254
-
255
- DTMF *to the server* (you pressing a digit) is the `SendDtmf` verb on
256
- the bidi control stream — not a uni stream. The browser cannot open
257
- WT uni sub-streams at all in some specs/browsers, so we never required
258
- it for the outbound direction.
242
+ The request belongs to the WebTransport on which it was sent. A close rejects
243
+ it; reconnect/Resume never queues or replays it, and a stale ACK is ignored.
244
+ The SDK does not receive peer-generated DTMF, expose a DTMF callback, synthesize
245
+ a tone, or imply PSTN/carrier delivery. Incoming uni streams are drained only
246
+ to keep the transport healthy.
259
247
 
260
248
  ---
261
249
 
@@ -268,13 +256,12 @@ Same shape as native. See [`client-integration.md`](client-integration.md)
268
256
  |---|---|---|
269
257
  | `Hold { held: bool }` | Client → Server | `session.hold` |
270
258
  | `Mute { scope: Uplink \| Downlink \| Both \| None }` | Client → Server | `session.mute_self` |
271
- | `SendDtmf { digit, duration_ms }` | Client → Server | `dtmf_send` |
259
+ | `SendDtmf { request_id, digit }` | Client → Server | `dtmf_send` |
272
260
  | `Resume { call_token, last_event_seq }` | Client → Server | (token only) |
273
261
  | `NegotiateMedia { offers, frame_size_ms }` | Client → Server | (token only; additive) |
274
262
 
275
- The corresponding `*Ok` responses carry the echoed `request_id`. Server
276
- pushes the matching state-change event (`EndpointHeld`, `EndpointMuted`,
277
- `PeerStateChanged`, `DtmfReceived`) for other peers' UI consistency.
263
+ The corresponding `*Ok` responses carry the echoed `request_id`. DTMF has no
264
+ peer receive event; `SendDtmfOk` acknowledges Media publication only.
278
265
 
279
266
  ---
280
267
 
@@ -446,7 +433,7 @@ async function startCall({ url, jwt, certHash /* optional */ }) {
446
433
  startBidiReadLoop(reader, dispatchEvent) // server-push + verb Oks
447
434
  startDatagramSender(wt, audioAlias, serverEpochUnix) // mic → Opus → datagram
448
435
  startDatagramReceiver(wt) // datagrams → decoders
449
- startUniReceiver(wt) // DTMF uni streams
436
+ startUniStreamDrain(wt) // unsupported server uni input
450
437
 
451
438
  // 5. Wait for session end
452
439
  wt.closed.then(info => onClosed(info), err => onError(err))
package/docs/contract.md CHANGED
@@ -34,25 +34,47 @@ reciprocal cross-repository contract ledger.
34
34
 
35
35
  ## Release gate
36
36
 
37
- Run typecheck, unit tests, and the production build, then rebuild the linked
38
- `workspace/web/chat` consumer before publication. Publishing to npm remains an
39
- owner action and is never part of an implementation spin.
40
-
41
- ## Live-chat monitoring LCM-2 hardcut candidate
42
-
43
- - The unpublished candidate is generated from workspace wave base
44
- `a7a731d62cc938f8221d3749cbf7b05474f0f9b4` plus the coordinated dirty
45
- LCM-2 proto/golden inputs by
46
- `web/kit/scripts/ship-orpc.sh`; `src/orpc-gen/PROVENANCE` pins the candidate
47
- proto and both golden-fixture hashes. It remains unpublished and is consumed
48
- only through the exact local links recorded in the workspace artifact ledger;
49
- monitoring endpoint/lifecycle activation remains LCM-3.
50
- - `Message.metadata` and `TypingEvent.metadata` are required server outputs,
51
- with audience exactly `internal|all`; missing or unknown output metadata fails
52
- closed. Current ordinary calls inject `all`. CX additionally accepts an
53
- omitted `MessageRequest.metadata` or `TypingRequest.metadata` only from an
54
- authenticated participant-purpose legacy client and normalizes it to `all`;
55
- monitor-purpose omission remains invalid.
37
+ Run typecheck, unit tests, and the production build, then rebuild both linked
38
+ `workspace/web/chat` and `workspace/web/cx` consumers. Inspect the packed
39
+ declarations and artifact bytes before publication. Publishing to npm remains
40
+ an owner action and is never part of an implementation spin.
41
+
42
+ ## Internal voice DTMF wire codec
43
+
44
+ - The browser codec encodes `SEND_DTMF=0x58` as
45
+ `requestId (QUIC varint) | digit (exactly one ASCII byte)` and decodes
46
+ `SEND_DTMF_OK=0x59` as the echoed request id only. The accepted alphabet is
47
+ exactly `0-9*#A-D`; empty, multi-byte, lowercase, non-ASCII, and trailing
48
+ bytes fail closed.
49
+ - Request-scoped Media refusals remain the existing `MoqError` path:
50
+ `CapabilityMissing`, `IllegalState`, or `ResourceExhausted`. An ACK means
51
+ input acceptance for the owning CX flow, not peer delivery, tone playback,
52
+ or carrier forwarding.
53
+ - `src/voice/wire/testdata/send_dtmf_golden.txt` is a byte-identical copy of
54
+ Media's producer-owned canonical fixture at
55
+ `core/media/modules/moq-endpoint/testdata/send_dtmf_golden.txt`.
56
+ - `VoiceSession.sendDtmf(digit)` and
57
+ `SessionManager.sendDtmf(sessionId, digit)` expose the send-only API. Calls
58
+ reject during Resume, pending requests reject on transport close, and no
59
+ request is replayed on a replacement transport. There is no peer-receive
60
+ callback or tone playback. Media error-code changes and canonical fixture
61
+ changes therefore require a web-sdk consumer review.
62
+
63
+ ## Live-chat monitoring and optional message metadata
64
+
65
+ - Version 0.1.2 exposes optional `Message.metadata` and optional
66
+ `MessageMetadata.audience`. ORPC messages plus REST directory/history rows
67
+ normalize missing/null containers to `undefined`, while a present object with
68
+ a missing/null/empty audience remains `{}`. Lowercase `internal|all` is
69
+ preserved and every other non-empty value fails without trimming. Received
70
+ `TypingEvent.metadata` remains required and known.
71
+ - Ordinary participant message and typing calls omit request metadata. CX
72
+ resolves participant-purpose omission to `all`; monitor-purpose omission is
73
+ invalid. Monitor consumers therefore validate and send an explicit
74
+ `internal|all` audience for every message and typing operation.
75
+ - CX remains the authorization and projection boundary. Optional parsing never
76
+ authorizes an internal row for an external consumer, and newly authored CX
77
+ storage/events/replay/history/directory values remain explicit.
56
78
  - `onTyping` receives exactly `{participantId, role, userId, userName, state,
57
79
  metadata}`. Inbound watchdogs are participant-keyed; one participant's off
58
80
  or message cannot clear an unrelated participant.
package/docs/voice.md CHANGED
@@ -5,7 +5,7 @@
5
5
  | Layer | Tech |
6
6
  |--------------|----------------------------------------------------------------------|
7
7
  | Transport | WebTransport (`https://<media-server>/web`), one bidi control sub-stream + datagrams |
8
- | Control | B2BUA verbs (Connect/ConnectOk/Mute/Resume/PeerAttached/…), `msg_type (vi) \| payload_len (vi) \| payload` |
8
+ | Control | B2BUA verbs (Connect/ConnectOk/Mute/Resume/SendDtmf/PeerAttached/…), `msg_type (vi) \| payload_len (vi) \| payload` |
9
9
  | Audio | Opus 48 kHz mono, 20 ms frames (50 per wall-second), `ObjectDatagram` per frame |
10
10
  | Group / object IDs | `group_id = floor(Date.now()/1000) - server_epoch_unix`, `object_id ∈ [0..49]`, `end_of_group` on object 49 |
11
11
  | Reconnect | Tier-3 `Resume { call_token, last_event_seq }` only — browsers expose no QUIC migration / 0-RTT |
@@ -13,6 +13,10 @@
13
13
 
14
14
  See [`backend-integration.md`](backend-integration.md) (canonical spec) for the full wire definitions.
15
15
 
16
+ The internal `SendDtmf` codec carries one `0-9*#A-D` symbol and awaits its
17
+ request-correlated ACK. It does not yet expose a public send method, receive
18
+ callback, tone generator, duration control, or carrier-forwarding behavior.
19
+
16
20
  ## Required browser features (the SDK-wide floor)
17
21
 
18
22
  | Feature | Needed by |
@@ -128,6 +132,7 @@ const { sessionId } = await client.joinSession({
128
132
  })
129
133
 
130
134
  await client.setMute(sessionId, 'uplink') // 'uplink' | 'downlink' | 'both' | 'none'
135
+ await client.sendDtmf(sessionId, '#') // exactly one of 0-9, *, #, A-D
131
136
 
132
137
  // For a provisioned receive-only join, enter and leave microphone capture
133
138
  // without exposing an unmuted permission/acquisition window.
@@ -147,6 +152,12 @@ await client.endSession(sessionId)
147
152
 
148
153
  ## Reconnect behaviour
149
154
 
155
+ DTMF requests are transport-bound. Calls made during reconnect reject locally;
156
+ an unacknowledged digit is rejected when its transport closes and is never
157
+ replayed on the replacement transport. `SendDtmfOk` means CX-flow publication
158
+ was accepted, not carrier delivery. There is no inbound DTMF callback or SDK
159
+ tone playback.
160
+
150
161
  When `wt.closed` fires unexpectedly (no `EndpointHangup` or terminal `MoqError` arrived first), the SDK runs:
151
162
 
152
163
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@origonai/web-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Origon Web SDK - chat and voice session client",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.5.1",