@urun-sh/core 0.2.60 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ - **BREAKING (types): `SessionPhaseName` widens 9 → 11 members** — adds
6
+ `'waiting_for_capacity'` and `'unknown'`. An exhaustive `switch` over
7
+ `phase.name` with a `never` default, or a `Record<SessionPhaseName, …>` label
8
+ map, stops compiling until the two new members are handled. The terminal
9
+ server words RELEASED/REAPED/EXPIRED/SUPERSEDED still map onto the *existing*
10
+ `'ended'`/`'expired'` names — no new terminal strings. (Breaking change rides
11
+ the 0.x minor per semver.)
12
+ - **Server status honesty (#351).** `STATUS_TO_PHASE` maps the full server
13
+ vocabulary; an unrecognized status is no longer silently rendered as
14
+ `'provisioning'` — it becomes a loud `'unknown'` phase + `console.error` +
15
+ `unknown-server-status` diagnostic carrying the literal word. The lookup is
16
+ own-property-only, so `Object.prototype` words (`"constructor"`, `"toString"`,
17
+ `"__proto__"`) are treated as unknown rather than resolving inherited members.
18
+ `SessionPhase.reason` now propagates on non-terminal AND terminal paths, and
19
+ the session-refresh stash is cleared on every server-declared terminal frame
20
+ (fixes the eternal "Provisioning…" re-attach, urun-infra#1856). New exports:
21
+ `isServerTerminalPhase`, `SIGNALING_PROTOCOL_VERSION` (`'v1'`, sent on the
22
+ `start` frame).
23
+ - **WT audio lane (#353).** Opus-over-QUIC-datagram downlink: datagram header
24
+ byte 11 is the lane discriminant (0 = video, byte-for-byte the historical
25
+ layout; 1 = audio) → `AudioFrameAssembler` → WebCodecs `AudioDecoder` →
26
+ scheduled WebAudio playout → bridge `MediaStreamTrack`, with a 40ms standing
27
+ buffer. A decode-error budget kills only the audio lane (reverts to WebRTC;
28
+ video keeps flowing). Negotiated per leg, so a pre-audio client never receives
29
+ a lane-1 datagram. The assembler bounds its in-flight set
30
+ (`WT_AUDIO_MAX_PENDING_FRAMES`) under sustained loss, rejects a non-finite
31
+ `jitterBufferTargetMs`, and closes the `AudioContext` if decoder setup throws.
32
+
33
+ - **BREAKING (exported type `SignalCarrier`).** Implementers of the exported
34
+ `SignalCarrier` interface must update two members:
35
+ `sendSignal` now returns `boolean` (was `void`), and a new REQUIRED
36
+ `emitDiagnostic(diagnostic: SessionDiagnostic): void` reports through the
37
+ session's diagnostic channel. `TransportSession` — the only implementation
38
+ in this repo, and the one every SDK consumer actually passes — already
39
+ satisfies both; this affects only a host that hand-rolled its own carrier.
40
+
41
+ Deliberately NOT widened to `boolean | void` for source compatibility: a
42
+ legacy `void` implementation that FAILED to send returns `undefined`, and
43
+ treating `undefined` as success is precisely the silent-drop this release
44
+ exists to remove — it would preserve the phantom-timeout bug for exactly the
45
+ carriers that still have it. A compile error that names the seam is the
46
+ correct outcome (`CLAUDE.md`: one canonical path, fail hard, never a silent
47
+ alternate path). Pre-1.0, so a minor bump carries it.
48
+
49
+ - **FIX: a signaling request that never left the process no longer fails as a
50
+ phantom timeout.** `SignalCarrier.sendSignal` returned `void` and was
51
+ documented as "dropped while disconnected", so a `transport.offer` issued
52
+ across a signaling blip vanished silently and `request()` then sat out its
53
+ full 10s deadline before rejecting with `signaling request timed out:
54
+ transport.offer` — a message that blames a timeout for a condition known
55
+ locally and instantly, and points the reader at a server that never received
56
+ a byte. `sendSignal`/`_send` now report whether the message went out on an
57
+ OPEN socket, `request()` rejects IMMEDIATELY with `signaling request not
58
+ sent: <method> — the signaling socket is not open` (tearing down its deadline
59
+ timer so nothing fires behind the rejection), and `notify()` — fire-and-forget
60
+ by contract, so nothing to reject — warns instead of pretending delivery. The
61
+ genuine no-reply case still uses the deadline lane, unchanged. The
62
+ undelivered-notify report rides the session's diagnostic channel
63
+ (`diagnosticSink` when configured, the `'diagnostic'` event always,
64
+ `kind: 'signaling-notify-undelivered'`) rather than a bare `console.warn`
65
+ that would bypass a host sink.
66
+
3
67
  ## 0.2.57
4
68
 
5
69
  - **FIX: `jitterBufferTarget` range guard (0–4000ms).** The W3C
@@ -95,6 +159,28 @@
95
159
  past the `<= 0` check and throw on assignment to the WebIDL double, aborting
96
160
  the consume before `resumeConsumer`.
97
161
 
162
+ - **`SessionGatewayResponse.region_apex` is renamed `session_api_base`** (type
163
+ surface change: a consumer reading the raw allocated payload off
164
+ `AllocatedSession.response` must rename the field). The control plane owns
165
+ this name — urun-infra #1497 emits `session_api_base`, and its value is
166
+ already a full origin the SDK accepts unchanged. The field stays OPTIONAL:
167
+ #1497 is not merged, so nothing emits it yet and an ABSENT field remains the
168
+ normal path, keeping the create-final origin as the bootstrap pin.
169
+ - **A present-but-unusable `session_api_base` now THROWS** where the previous
170
+ `region_apex` handling silently kept the bootstrap origin. On a managed
171
+ `urun.sh` https gateway a malformed / non-https / path-bearing / off-trust
172
+ value raises a gateway contract error, because silently keeping the bootstrap
173
+ origin sent the session's polls, release beacons and auth to a region that
174
+ does not own it and surfaced as a confident-looking 401/404 elsewhere.
175
+ Non-managed bases (kind's in-cluster `http://` service base, localhost,
176
+ self-hosted) ignore the field for that stated reason and never throw.
177
+ - **An HTTP 3xx / `opaqueredirect` from session create OR poll is now a loud
178
+ terminal error** instead of a swallowed `HTTP 0`. The cross-region contract is
179
+ the JSON envelope (`200 {status:'redirect', location}`); an HTTP redirect is a
180
+ contract violation. The poll request also moves to `redirect: 'manual'`, so a
181
+ consumer that relied on the browser transparently following a poll redirect
182
+ will now see the error instead.
183
+
98
184
  - **`SessionDiagnostic.level` gains `'debug'`** (union widened: an exhaustive
99
185
  `switch`/`Record` over the level needs a new arm). `debug` diagnostics mirror
100
186
  to `console.debug` — hidden at the browser console's default verbosity —
@@ -148,6 +234,25 @@
148
234
  fails the build on ANY wall-clock media-gap branch that drives
149
235
  `_renegotiate()`/`_attemptReconnect()` (inbound counter or decode counter),
150
236
  while allowing the bounded first-byte-anchored join probe.
237
+ - **A dispose racing an in-flight WebTransport negotiation aborts quietly and leaks
238
+ nothing.** zimage.urun.sh prod (2026-07-30): React lifecycle churn (the FE's bounded
239
+ terminal-frame auto-mint remounts its session engine; the unmount cleanup calls
240
+ `session.disconnect()` synchronously inside the React commit) disposed the dial-
241
+ negotiation handle while the QUIC probe was in flight. `WebTransportSession.stop()`
242
+ rejects the pending `firstMedia` with "webtransport session stopped", and the sidecar's
243
+ connect catch dressed that DELIBERATE teardown in the loud error-shaped
244
+ `transport negotiation unavailable, staying on webrtc` line — three identical times per
245
+ page load. A dispose is not a fallback (no session survives to fall back): the abort now
246
+ logs at debug and schedules nothing. The loud warn stays byte-for-byte for genuine
247
+ negotiation failures on a live handle (the WT-QUIC-first LOUD-fallback mandate is
248
+ unchanged). Additionally, a dispose racing `connect()`/`retryWebTransport()` used to
249
+ leak the just-established WT session (armed failover and all) past the handle's
250
+ lifecycle — the late arrival is now stopped.
251
+ - **The `selected ICE candidate pair` diagnostic emits once per ACTUAL route.** It was
252
+ latched once-per-session-handle: repeated identical lines still spammed the console
253
+ across session churn, while a genuine route migration (an ICE restart landing on a
254
+ different pair, e.g. UDP → TCP relay) stayed silent forever after the first emit. The
255
+ diagnostic now dedupes on the pair itself — first route, then only on change.
151
256
 
152
257
  ## 0.2.44
153
258