openrtc 0.1.3 → 0.2.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.
Files changed (41) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +73 -9
  3. package/dist/DelegatingRuntimeAdapter-BxOcVIx_.d.ts +201 -0
  4. package/dist/{IpcRuntimeAdapter-CT-W0Mtp.d.ts → IpcRuntimeAdapter-U5AEH0jn.d.ts} +6 -2
  5. package/dist/auth/index.js +1 -1
  6. package/dist/auth/internal.js +1 -1
  7. package/dist/chunk-7DN33VUQ.js +1 -0
  8. package/dist/chunk-BLOEZIFJ.js +1 -0
  9. package/dist/chunk-FQETZQPW.js +2 -0
  10. package/dist/chunk-IUHUYHNZ.js +1 -0
  11. package/dist/chunk-UBEE42VU.js +2 -0
  12. package/dist/chunk-UPO23UMZ.js +3 -0
  13. package/dist/chunk-WNJE3MJF.js +1 -0
  14. package/dist/{factories-0TxDPMeu.d.ts → framing-lxzHEuSr.d.ts} +479 -226
  15. package/dist/index.d.ts +201 -7
  16. package/dist/index.js +2 -1
  17. package/dist/openrtc_bg.wasm +0 -0
  18. package/dist/runtime/WasmRuntimeAdapter.d.ts +2 -1
  19. package/dist/runtime/WasmRuntimeAdapter.js +1 -1
  20. package/dist/runtime/index.d.ts +213 -392
  21. package/dist/runtime/index.js +1 -1
  22. package/dist/runtime/tauri.d.ts +5 -6
  23. package/dist/runtime/tauri.js +1 -1
  24. package/dist/transport/index.d.ts +11 -0
  25. package/dist/transport/index.js +1 -0
  26. package/dist/{DelegatingRuntimeAdapter-CW8d78dZ.d.ts → types-D0fRvUbN.d.ts} +222 -171
  27. package/env/index.d.ts +64 -0
  28. package/env/index.mjs +230 -0
  29. package/package.json +29 -18
  30. package/dist/chunk-26POO5PL.js +0 -4
  31. package/dist/chunk-4MH42GUI.js +0 -2
  32. package/dist/chunk-D54VGRRH.js +0 -1
  33. package/dist/chunk-DIQYFAGL.js +0 -2
  34. package/dist/chunk-PZ25OCVG.js +0 -2
  35. package/dist/chunk-UVASANPG.js +0 -1
  36. package/wasm/README.md +0 -64
  37. package/wasm/openrtc.d.ts +0 -320
  38. package/wasm/openrtc.js +0 -2290
  39. package/wasm/openrtc_bg.wasm +0 -0
  40. package/wasm/openrtc_bg.wasm.d.ts +0 -113
  41. package/wasm/package.json +0 -16
@@ -1,145 +1,8 @@
1
- import { l as ChannelDescriptor$1, i as ProtocolName, L as LocalDeviceInfo, I as IncomingStreamChannelMetadata, k as ClientOptions, m as Device, n as SignalingSession, o as RuntimeIdentity, p as DeviceStatusSnapshot, M as ManagedDeviceConnectOptions, q as PeerState, r as PeerHealth, J as JoinRoomOptions, s as RoomMember, t as PeerScope, B as BackendConnectionState, A as AuthContext, u as PeerLifecycleStatus, P as PeerSessionSnapshot, v as ConnectionOptions, G as GrantScope, w as ISignalingBackend, c as RuntimeCapabilities, a as ConnectDeviceHooks, b as ConnectDeviceOptions, C as ConnectDeviceResult, x as ManagedConnectionRecord, R as ResolvedPeerIdentity, d as AppLimits, N as NativeConnectResult, E as ExplicitFileDataSendParams } from './DelegatingRuntimeAdapter-CW8d78dZ.js';
2
- import { P as PlutoIpcBridge, I as IpcRuntimeAdapter } from './IpcRuntimeAdapter-CT-W0Mtp.js';
1
+ import { A as AuthContext, R as RuntimeCapabilities, a as RuntimeCapabilityProfile } from './DelegatingRuntimeAdapter-BxOcVIx_.js';
2
+ import { a2 as ConnectionOptions, q as ApplicationPayloadCrypto, v as ApplicationPayloadReadinessOptions, w as ApplicationPayloadRouteOptions, n as ProtocolName, x as ApplicationPayloadSendOptions, a0 as ChannelDescriptor$1, L as LocalDeviceInfo, T as IncomingStreamChannelMetadata, C as ClientOptions, a as Device, f as SignalingSession, R as RuntimeIdentity, b as DeviceStatusSnapshot, a3 as ManagedDeviceConnectOptions, Z as PeerState, $ as PeerHealth, O as JoinRoomOptions, Q as RoomMember, _ as PeerScope, j as BackendConnectionState, y as DevicePresenceStatus, a4 as PeerLifecycleStatus, P as PeerSessionSnapshot, G as GrantScope, I as ISignalingBackend, l as ConnectDeviceHooks, m as ConnectDeviceOptions, k as ConnectDeviceResult, M as ManagedConnectionRecord, c as ResolvedPeerIdentity, t as AppLimits, i as NativeConnectResult, E as ExplicitFileDataSendParams } from './types-D0fRvUbN.js';
3
+ import { P as PlutoIpcBridge, I as IpcRuntimeAdapter } from './IpcRuntimeAdapter-U5AEH0jn.js';
3
4
  import { WasmRuntimeAdapter } from './runtime/WasmRuntimeAdapter.js';
4
5
 
5
- /**
6
- * App-facing options intentionally hide transport node identifiers from callbacks.
7
- */
8
- type RuntimeClientOptions = Omit<ClientOptions, 'onDevices' | 'onAutoConnect' | 'onIncomingSession'> & {
9
- onDevices?: (devices: RuntimeDevice[]) => void;
10
- onAutoConnect?: (devices: RuntimeDevice[]) => void;
11
- onIncomingSession?: (session: RuntimeSession) => Promise<void>;
12
- };
13
- /**
14
- * App-facing device model. Runtime transport identifiers stay internal.
15
- */
16
- type RuntimeDevice = Omit<Device, 'nodeId'>;
17
- /**
18
- * App-facing session model. Node identifiers are runtime-internal metadata.
19
- */
20
- type RuntimeSession = Omit<SignalingSession, 'initiatorNodeId' | 'targetNodeId' | 'appTag'>;
21
- type RuntimeAuthIdentity = RuntimeIdentity;
22
- type RuntimeAuthContext = AuthContext;
23
- /**
24
- * App-facing peer state model. Runtime transport identifiers stay internal.
25
- */
26
- type RuntimePeerState = Omit<PeerState, 'nodeId'>;
27
- type RuntimePeerHealth = PeerHealth;
28
- type RuntimePeerLifecycleStatus = PeerLifecycleStatus;
29
- type RuntimePeerScope = PeerScope;
30
- /**
31
- * App-facing peer session snapshot model. Runtime transport identifiers stay internal.
32
- */
33
- type RuntimePeerSessionSnapshot = Omit<PeerSessionSnapshot, 'nodeId'>;
34
- type RuntimeLocalDeviceInfo = LocalDeviceInfo;
35
- type RuntimeRoomMember = RoomMember;
36
- type RuntimeDeviceConnectOptions = ManagedDeviceConnectOptions;
37
- type RuntimeJoinRoomOptions = JoinRoomOptions;
38
- type RuntimeChannelDescriptor = ChannelDescriptor$1;
39
- interface RuntimeConnectionTarget {
40
- ticket: string;
41
- timeoutMs?: number;
42
- }
43
- interface RuntimePeerConnectOptions {
44
- deviceId?: string;
45
- ticket?: string;
46
- scope?: RuntimePeerScope;
47
- channelId?: string;
48
- }
49
- interface RuntimePeerStreamOptions {
50
- ticket?: string;
51
- scope?: RuntimePeerScope;
52
- channelId?: string;
53
- timeoutMs?: number;
54
- }
55
- /**
56
- * App-facing device status snapshot model. Runtime transport identifiers stay internal.
57
- */
58
- type RuntimeDeviceStatusSnapshot = Omit<DeviceStatusSnapshot, 'nodeId'>;
59
- type RuntimeDeviceStatus = RuntimeDeviceStatusSnapshot;
60
- type RuntimeIncomingStream = {
61
- type: 'bi' | 'uni';
62
- stream: any;
63
- remoteNodeId: string;
64
- protocolHint?: 'control' | 'explicit' | 'native-main' | 'unknown';
65
- channel?: IncomingStreamChannelMetadata | null;
66
- };
67
- type RuntimeConnectionEvent = {
68
- type: 'connected' | 'disconnected';
69
- connectionId: string;
70
- localNodeId: string;
71
- remoteNodeId: string;
72
- transport: ProtocolName;
73
- };
74
- type RuntimeRawConnection = {
75
- connectionId: string;
76
- localNodeId: string;
77
- remoteNodeId: string;
78
- };
79
- type RuntimePresenceLoopOptions = {
80
- deviceName?: string;
81
- ticket?: string;
82
- metadata?: string;
83
- };
84
- type RuntimeManagedSessionPlatform = 'native' | 'web';
85
- type RuntimeManagedSessionKind = 'app-user-device' | 'share-anonymous' | 'drive-grant-guest' | 'scoped-guest' | (string & {});
86
- type RuntimeAutoConnectPolicy = 'enabled' | 'disabled' | 'scoped-only';
87
- type RuntimePresencePolicy = 'online' | 'disabled';
88
- type RuntimeConnectionStateSnapshot = BackendConnectionState;
89
- type RuntimeUiConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'failed';
90
- interface RuntimePeerReadinessSnapshot {
91
- requestedId: string;
92
- connected: boolean;
93
- settledReady: boolean;
94
- status: RuntimeUiConnectionStatus;
95
- health: RuntimePeerHealth | 'unknown';
96
- deviceId?: string | null;
97
- deviceIdHint?: string | null;
98
- activeConnectionId?: string | null;
99
- readinessState?: string;
100
- readinessReason?: string;
101
- scopes: string[];
102
- }
103
- interface RuntimeLocalDeviceProfile {
104
- deviceId: string | null;
105
- deviceName: string;
106
- platformType: string;
107
- userId: string | null;
108
- capabilities: NonNullable<RuntimeLocalDeviceInfo['capabilities']>;
109
- lastSeenAt: string;
110
- localNodeId?: string | null;
111
- }
112
- interface RuntimeManagedSessionOptions {
113
- platform: RuntimeManagedSessionPlatform;
114
- sessionKind?: RuntimeManagedSessionKind;
115
- scope?: string | null;
116
- autoConnectPolicy?: RuntimeAutoConnectPolicy;
117
- presencePolicy?: RuntimePresencePolicy;
118
- userId?: string | null;
119
- deviceName?: string;
120
- localDeviceId?: string | null;
121
- metadata?: string;
122
- autoConnect?: boolean;
123
- presence?: boolean;
124
- }
125
- interface RuntimeManagedSession {
126
- mode: RuntimeManagedSessionPlatform;
127
- device: RuntimeLocalDeviceProfile;
128
- stop: () => Promise<void>;
129
- }
130
- type ConnectionTarget = RuntimeConnectionTarget;
131
- type PeerConnectOptions = RuntimePeerConnectOptions;
132
- type PeerStreamOptions = RuntimePeerStreamOptions;
133
- type IncomingStream = RuntimeIncomingStream;
134
- type ConnectionEvent = RuntimeConnectionEvent;
135
- type RawConnection = RuntimeRawConnection;
136
- type PresenceLoopOptions = RuntimePresenceLoopOptions;
137
- type PeerReadiness = RuntimePeerReadinessSnapshot;
138
- type DeviceProfile = RuntimeLocalDeviceProfile;
139
- type SessionOptions$1 = RuntimeManagedSessionOptions;
140
- type Session$1 = RuntimeManagedSession;
141
- type ChannelDescriptor = RuntimeChannelDescriptor;
142
-
143
6
  interface ITransport {
144
7
  readonly name: string;
145
8
  readonly isReady: boolean;
@@ -163,16 +26,21 @@ declare class Connection {
163
26
  private static readonly textEncoder;
164
27
  private static readonly UPGRADE_RETRY_DELAY_MS;
165
28
  private static readonly MAX_UPGRADE_RETRIES;
29
+ private static readonly MAX_UPGRADE_REDRIVES;
30
+ private static readonly UPGRADE_REDRIVE_DELAY_MS;
166
31
  private static readonly WEBRTC_DISCONNECTED_GRACE_MS;
167
32
  private static readonly MAX_PENDING_MESSAGES;
33
+ private static readonly DEFAULT_APPLICATION_CRYPTO_WAIT_MS;
168
34
  /**
169
35
  * Must exceed `WEBRTC_ICE_CONNECT_TIMEOUT_MS` in `WebRTCTransport`: the transport
170
36
  * waits that long for ICE + DataChannel before `failed`. A shorter watchdog here
171
37
  * tears the PC down early, burns `MAX_UPGRADE_RETRIES`, and stretches upgrades to ~60s+ under load.
172
38
  */
173
39
  private static readonly WEBRTC_UPGRADE_ATTEMPT_TIMEOUT_MS;
40
+ private static readonly MAX_PENDING_ENCRYPTED_APPLICATION_PAYLOADS;
174
41
  private listeners;
175
42
  private pendingMessages;
43
+ private pendingEncryptedApplicationPayloads;
176
44
  private webrtcBinaryHandlers;
177
45
  private closeListeners;
178
46
  private upgradeStateListeners;
@@ -188,34 +56,61 @@ declare class Connection {
188
56
  readonly localNodeId: string;
189
57
  private options;
190
58
  private writeMutex;
59
+ private applicationCryptoWaiters;
191
60
  private nativeSignalSender;
192
61
  private webrtcTransport;
62
+ private webRtcApplicationRouteReadyTransport;
63
+ private webRtcApplicationRouteReadyNegotiationId;
193
64
  private upgradeState;
194
65
  private upgradeRequested;
66
+ private negotiationSwitchCount;
195
67
  private upgradeAttemptActive;
196
68
  private upgradeRetryCount;
69
+ private upgradeReDriveCount;
70
+ private upgradeReDriveTimer;
197
71
  private upgradeRetryTimer;
198
72
  private upgradeAttemptTimeoutTimer;
199
73
  private lastReportedTransportStatus;
74
+ private applicationRouteMissingNotified;
200
75
  private upgradeTerminalFailure;
201
76
  private pendingSignals;
202
77
  private activeNegotiationId;
203
78
  private webrtcHeartbeat;
204
79
  private webRtcHeartbeatProbeInFlight;
80
+ private webRtcApplicationRouteProbeInFlight;
205
81
  private webRtcHeartbeatConsecutiveProbeFailures;
206
82
  private irohHealthListeners;
207
83
  private webrtcHealthListeners;
208
84
  private lastWebRtcHeartbeatOkAt;
209
85
  private lastWebRtcDataChannelOpenAt;
86
+ private static readonly WEBRTC_RENEGOTIATE_LIVENESS_WINDOW_MS;
87
+ private static readonly MAX_NEGOTIATION_SWITCHES;
210
88
  private lastForegroundAt;
211
89
  private webRtcDisconnectedGraceTimer;
212
90
  private preferredWebRtcRole;
213
91
  constructor(id: string, localNodeId: string, remoteNodeId: string, writer: WritableStreamDefaultWriter<Uint8Array>, reader: ReadableStreamDefaultReader<Uint8Array>, options?: ConnectionOptions);
92
+ setApplicationCrypto(applicationCrypto: ApplicationPayloadCrypto | undefined): void;
93
+ requireApplicationCrypto(): void;
94
+ isApplicationCryptoRequired(): boolean;
95
+ isReadyForApplicationPayload(options?: ApplicationPayloadReadinessOptions): boolean;
96
+ getApplicationPayloadTransports(options?: ApplicationPayloadRouteOptions): ProtocolName[];
97
+ private isTransportAvailableForApplicationPayload;
98
+ private resolveApplicationCryptoWaiters;
99
+ private rejectApplicationCryptoWaiters;
100
+ private waitForRequiredApplicationCrypto;
214
101
  /**
215
102
  * Starts WebRTC upgrade only when peer capability negotiation allows it.
216
103
  * No-op when WebRTC is not configured or an upgrade already started.
217
104
  */
218
105
  requestWebRTCUpgrade(reason?: string): void;
106
+ /**
107
+ * Whether the current WebRTC route is provably alive: an open data channel
108
+ * plus either a recent heartbeat round trip or a freshly opened channel. An
109
+ * `open` readyState alone is not proof — it lingers for many seconds after the
110
+ * remote vanishes (e.g. a page reload), so a dead-but-`open` channel reports
111
+ * not-live and can be replaced rather than trusted.
112
+ */
113
+ private isWebRtcRouteHeartbeatLive;
219
114
  requestTransportRecovery(reason: string, options?: {
220
115
  forceRenegotiate?: boolean;
221
116
  replaceExisting?: boolean;
@@ -248,6 +143,8 @@ declare class Connection {
248
143
  candidateTypeSummary: string | null;
249
144
  } | null;
250
145
  }>;
146
+ private lastWebRtcLocalCandidateType;
147
+ private lastWebRtcRemoteCandidateType;
251
148
  get transportType(): ProtocolName;
252
149
  private _peerMoQReady;
253
150
  get peerMoQReady(): boolean;
@@ -260,7 +157,7 @@ declare class Connection {
260
157
  * Send a message to the other peer.
261
158
  * Messages can be strings or objects (JSON stringified).
262
159
  */
263
- send(message: string | object | ArrayBuffer | Uint8Array): Promise<void>;
160
+ send(message: string | object | ArrayBuffer | Uint8Array, options?: ApplicationPayloadSendOptions): Promise<void>;
264
161
  /**
265
162
  * Send media data (video/audio header + payload)
266
163
  * Internal use for now.
@@ -271,24 +168,41 @@ declare class Connection {
271
168
  * Throws when the requested transport is unavailable.
272
169
  */
273
170
  sendOnTransport(transport: ProtocolName, message: string | object | ArrayBuffer | Uint8Array): Promise<void>;
171
+ /**
172
+ * Send plaintext control traffic that is required to bootstrap application
173
+ * key agreement. This is intentionally separate from public app-data sends.
174
+ */
175
+ sendControl(message: string | object | ArrayBuffer | Uint8Array): Promise<void>;
274
176
  /**
275
177
  * Write an already-framed payload directly on the underlying iroh control stream.
276
178
  * Used by native-main signaling paths that must preserve protocol framing.
277
179
  */
278
180
  sendRawIrohFrame(frame: Uint8Array): Promise<void>;
279
181
  /**
280
- * Return currently usable transports for this connection.
182
+ * Compatibility/diagnostic view of currently usable transports.
183
+ *
184
+ * Lifecycle projection and route selection must use getTransportStatus().
185
+ * This legacy list intentionally remains for older callers and tests that
186
+ * need a human-readable inventory, but it is not an authority for active
187
+ * versus fallback transport state.
281
188
  */
282
189
  getAvailableTransports(): ProtocolName[];
283
190
  onWebRTCBinaryMessage(handler: (data: Uint8Array) => boolean): void;
191
+ private dispatchWebRtcBinaryHandlers;
284
192
  getWebRTCTransport(): ITransport | null;
285
193
  sendOnWebRTC(data: Uint8Array): Promise<void>;
286
194
  private encodePayload;
195
+ private protectApplicationPayload;
196
+ private openApplicationPayload;
197
+ private openUntypedApplicationPayload;
198
+ private queueEncryptedApplicationPayload;
199
+ private flushPendingEncryptedApplicationPayloads;
287
200
  private buildFrame;
288
201
  private sendOverIroh;
289
202
  private sendUsingTransport;
290
203
  private sendTypedOnTransport;
291
204
  private sendTyped;
205
+ private getSendTransportPriorities;
292
206
  /**
293
207
  * Disconnect the connection
294
208
  */
@@ -310,6 +224,7 @@ declare class Connection {
310
224
  private readLoop;
311
225
  injectData(chunk: Uint8Array): void;
312
226
  private handleData;
227
+ private tryHandlePlaintextNativeSignal;
313
228
  private static isUntypedDataFrame;
314
229
  private processDataPayload;
315
230
  private isLikelyTextPayload;
@@ -318,6 +233,8 @@ declare class Connection {
318
233
  receiveMessage(msg: any): void;
319
234
  receiveDataPayload(messageData: Uint8Array): void;
320
235
  private trackListeners;
236
+ private warnedSrtpApplicationCryptoBoundary;
237
+ private warnSrtpApplicationCryptoBoundary;
321
238
  addTrack(track: MediaStreamTrack): RTCRtpSender | null;
322
239
  addTransceiver(trackOrKind: MediaStreamTrack | string, init?: RTCRtpTransceiverInit): RTCRtpTransceiver | null;
323
240
  onTrack(callback: (event: RTCTrackEvent) => void): void;
@@ -325,10 +242,12 @@ declare class Connection {
325
242
  private isWebRtcDataChannelOpen;
326
243
  private clearWebRtcDisconnectedGraceTimer;
327
244
  private transportStillLikelyAlive;
328
- private handleDisconnectedTransportStateWithGrace;
245
+ private handleUnhealthyWebRtcTransportStateWithGrace;
329
246
  private attemptUpgrade;
330
247
  private setUpgradeState;
331
248
  private handleUpgradeFailure;
249
+ private scheduleUpgradeReDrive;
250
+ private clearUpgradeReDriveTimer;
332
251
  private shouldRetryUpgrade;
333
252
  private scheduleUpgradeRetry;
334
253
  private clearUpgradeRetryTimer;
@@ -337,14 +256,75 @@ declare class Connection {
337
256
  private teardownWebRTCTransport;
338
257
  private startWebRtcHeartbeat;
339
258
  private stopWebRtcHeartbeat;
259
+ private notifyWebRtcApplicationRouteMissing;
260
+ private startWebRtcApplicationRouteProbe;
340
261
  private sendIrohHeartbeatPong;
341
262
  /** Register a listener for WebRTC heartbeat health changes. */
342
263
  onWebRtcHeartbeatHealth(cb: (health: ConnectionHealth) => void): void;
343
264
  /** Register a listener for iroh heartbeat health changes (WASM path). */
344
265
  onIrohHeartbeatHealth(cb: (health: ConnectionHealth) => void): void;
345
266
  reportTransportStatusIfChanged(): void;
267
+ /**
268
+ * Single connection-owned transport snapshot for status reporting,
269
+ * lifecycle projection, and diagnostics. The active route is application
270
+ * ready; the parallel route is its usable fallback or candidate.
271
+ */
272
+ getTransportStatus(): {
273
+ activeTransport: ProtocolName;
274
+ parallelTransport: ProtocolName | null;
275
+ };
346
276
  private resolveParallelTransport;
347
277
  setNativeSignalSender(sender: (envelope: any) => Promise<void>): void;
278
+ /**
279
+ * Structured connection-lifecycle log. One event per meaningful transition
280
+ * (upgrade requested/started/failed, route promoted, app route ready, …) with
281
+ * the connection's current lifecycle snapshot, so the whole connection flow
282
+ * can be reconstructed from logs. Emits both a human line and a JSON line.
283
+ */
284
+ private traceLifecycle;
285
+ getControlFrameMode(): NonNullable<ConnectionOptions['controlFrameMode']>;
286
+ /**
287
+ * Send a control envelope over the native-main signal route. Returns false
288
+ * (without sending) unless this connection is in `native-main` control-frame
289
+ * mode and has a native signal sender, so a typed connection never
290
+ * accidentally routes control through the native sender.
291
+ */
292
+ sendNativeControlEnvelope(envelope: any): Promise<boolean>;
293
+ /**
294
+ * Promote a connection to the native-main control plane: switch its
295
+ * control-frame mode to `native-main` and attach the native signal sender.
296
+ * Used when native-main evidence arrives for a connection that bootstrapped
297
+ * on a typed stream, so subsequent control traffic flows over the durable
298
+ * native-main route even if the bootstrap stream later closes.
299
+ */
300
+ promoteToNativeMainControlRoute(sender: (envelope: any) => Promise<void>): void;
301
+ private hasRequiredApplicationCrypto;
302
+ private clearWebRtcApplicationRouteReady;
303
+ private isCurrentWebRtcTransportApplicationRouteReady;
304
+ private isWebRtcApplicationRouteReadyForTransport;
305
+ /**
306
+ * Mark the upgraded WebRTC data plane as carrying a proven application route
307
+ * (data channel open + required crypto present). Idempotent. Promotes the
308
+ * active transport projection to `webrtc` via reportTransportStatusIfChanged.
309
+ */
310
+ private markWebRtcApplicationRouteReady;
311
+ /**
312
+ * True only when the WebRTC transport is upgraded AND its application route
313
+ * is proven (crypto present). The active route must not advertise `webrtc`
314
+ * until this holds, so a half-open data channel never looks routable.
315
+ */
316
+ isWebRtcApplicationRouteReady(): boolean;
317
+ /**
318
+ * MoQ has a different data-plane shape than WebRTC: it is a shared relay
319
+ * session on the Client plus a peer capability/subscription bit on this
320
+ * Connection. Even so, it follows the same lifecycle invariant as every
321
+ * custom transport: do not advertise it as the active application route until
322
+ * the local transport is ready, the peer route is enabled, and the required
323
+ * application crypto is available. Future MoQ route probes should tighten
324
+ * this predicate, not bypass it.
325
+ */
326
+ isMoQApplicationRouteReady(): boolean;
327
+ private isMoQDataRouteReady;
348
328
  private sendInternalSignal;
349
329
  receiveInternalSignal(content: any): void;
350
330
  private handleInternalSignal;
@@ -353,6 +333,203 @@ declare class Connection {
353
333
  private normalizeNegotiationId;
354
334
  }
355
335
 
336
+ /**
337
+ * Coalesced peer reconciliation scheduler and full reconcile orchestration.
338
+ *
339
+ * Internal `Client` triggers schedule through `schedulePeerReconciliation()` so
340
+ * concurrent refresh requests share one in-flight `runPeerReconciliation` pass.
341
+ */
342
+
343
+ type PeerReconciliationStats = {
344
+ scheduled: number;
345
+ coalesced: number;
346
+ ran: number;
347
+ orphanTsConnection: number;
348
+ orphanRustRecord: number;
349
+ orphanTsConnectionHealed: number;
350
+ };
351
+
352
+ /**
353
+ * App-facing options intentionally hide transport node identifiers from callbacks.
354
+ */
355
+ type RuntimeClientOptions = Omit<ClientOptions, 'onDevices' | 'onAutoConnect' | 'onIncomingSession'> & {
356
+ onDevices?: (devices: RuntimeDevice[]) => void;
357
+ onAutoConnect?: (devices: RuntimeDevice[]) => void;
358
+ onIncomingSession?: (session: RuntimeSession) => Promise<void>;
359
+ };
360
+ /**
361
+ * App-facing device model. Runtime transport identifiers stay internal.
362
+ */
363
+ type RuntimeDevice = Omit<Device, 'nodeId'>;
364
+ /**
365
+ * App-facing session model. Node identifiers are runtime-internal metadata.
366
+ */
367
+ type RuntimeSession = Omit<SignalingSession, 'initiatorNodeId' | 'targetNodeId' | 'appTag'>;
368
+ type RuntimeAuthIdentity = RuntimeIdentity;
369
+ type RuntimeAuthContext = AuthContext;
370
+ /**
371
+ * App-facing peer state model. Runtime transport identifiers stay internal.
372
+ */
373
+ type RuntimePeerState = Omit<PeerState, 'nodeId'>;
374
+ type RuntimePeerHealth = PeerHealth;
375
+ type RuntimePeerLifecycleStatus = PeerLifecycleStatus;
376
+ type RuntimePeerScope = PeerScope;
377
+ /**
378
+ * App-facing peer session snapshot model. Runtime transport identifiers stay internal.
379
+ */
380
+ type RuntimePeerSessionSnapshot = Omit<PeerSessionSnapshot, 'nodeId'>;
381
+ type RuntimeLocalDeviceInfo = LocalDeviceInfo;
382
+ type RuntimeRoomMember = RoomMember;
383
+ type RuntimeDeviceConnectOptions = ManagedDeviceConnectOptions;
384
+ type RuntimeJoinRoomOptions = JoinRoomOptions;
385
+ type RuntimeChannelDescriptor = ChannelDescriptor$1;
386
+ interface RuntimeConnectionTarget {
387
+ ticket: string;
388
+ timeoutMs?: number;
389
+ }
390
+ interface RuntimePeerConnectOptions {
391
+ deviceId?: string;
392
+ ticket?: string;
393
+ scope?: RuntimePeerScope;
394
+ channelId?: string;
395
+ /**
396
+ * When true, automatic/observer dials fail fast if the peer has a sticky
397
+ * manual-disconnect projection for this app session. User-initiated manual
398
+ * reconnects should leave this unset and clear the exclusion explicitly.
399
+ */
400
+ respectManualDisconnect?: boolean;
401
+ }
402
+ interface RuntimePeerStreamOptions {
403
+ ticket?: string;
404
+ scope?: RuntimePeerScope;
405
+ channelId?: string;
406
+ timeoutMs?: number;
407
+ }
408
+ type RuntimeLogicalChannelFraming = 'raw' | 'u32be';
409
+ interface RuntimeScopedChannelOptions {
410
+ peerId?: string;
411
+ peerNodeId?: string;
412
+ deviceId?: string | null;
413
+ ticket?: string;
414
+ scope: RuntimePeerScope;
415
+ channelId: string;
416
+ timeoutMs?: number;
417
+ respectManualDisconnect?: boolean;
418
+ /**
419
+ * Native product protocols such as Plutonium drive-view expect a plaintext
420
+ * stream label before payload frames. OpenRTC owns that handshake here so
421
+ * apps do not need to choose raw transport APIs.
422
+ */
423
+ nativeLabel?: boolean;
424
+ framing?: RuntimeLogicalChannelFraming;
425
+ }
426
+ interface RuntimeScopedLogicalChannel {
427
+ channelId: string;
428
+ peerId: string;
429
+ connection: Connection;
430
+ send(payload: Uint8Array): Promise<void>;
431
+ onMessage(callback: (payload: Uint8Array) => void): () => void;
432
+ close(): Promise<void>;
433
+ diagnostics(): {
434
+ connectionId: string | null;
435
+ remoteNodeId: string | null;
436
+ transports: string[];
437
+ framing: RuntimeLogicalChannelFraming;
438
+ nativeLabel: boolean;
439
+ };
440
+ }
441
+ /**
442
+ * App-facing device status snapshot model. Runtime transport identifiers stay internal.
443
+ */
444
+ type RuntimeDeviceStatusSnapshot = Omit<DeviceStatusSnapshot, 'nodeId'>;
445
+ type RuntimeDeviceStatus = RuntimeDeviceStatusSnapshot;
446
+ type RuntimeDevicePresenceStatus$1 = DevicePresenceStatus;
447
+ type RuntimeIncomingStream = {
448
+ type: 'bi' | 'uni';
449
+ stream: any;
450
+ remoteNodeId: string;
451
+ protocolHint?: 'control' | 'explicit' | 'native-main' | 'unknown';
452
+ channel?: IncomingStreamChannelMetadata | null;
453
+ };
454
+ type RuntimeConnectionEvent = {
455
+ type: 'connected' | 'disconnected';
456
+ connectionId: string;
457
+ localNodeId: string;
458
+ remoteNodeId: string;
459
+ transport: ProtocolName;
460
+ };
461
+ type RuntimeRawConnection = {
462
+ connectionId: string;
463
+ localNodeId: string;
464
+ remoteNodeId: string;
465
+ };
466
+ type RuntimePresenceLoopOptions = {
467
+ deviceName?: string;
468
+ ticket?: string;
469
+ metadata?: string;
470
+ };
471
+ type RuntimeManagedSessionPlatform = 'native' | 'web';
472
+ type RuntimeManagedSessionKind = 'app-user-device' | 'share-anonymous' | 'drive-grant-guest' | 'scoped-guest' | (string & {});
473
+ type RuntimeAutoConnectPolicy = 'enabled' | 'disabled' | 'scoped-only';
474
+ type RuntimePresencePolicy = 'online' | 'disabled';
475
+ type RuntimeConnectionStateSnapshot = BackendConnectionState;
476
+ type RuntimeApplicationPayloadReadinessOptions = ApplicationPayloadReadinessOptions;
477
+ type RuntimeUiConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'failed';
478
+ interface RuntimePeerReadinessSnapshot {
479
+ requestedId: string;
480
+ connected: boolean;
481
+ settledReady: boolean;
482
+ status: RuntimeUiConnectionStatus;
483
+ health: RuntimePeerHealth | 'unknown';
484
+ deviceId?: string | null;
485
+ deviceIdHint?: string | null;
486
+ activeConnectionId?: string | null;
487
+ readinessState?: string;
488
+ readinessReason?: string;
489
+ scopes: string[];
490
+ }
491
+ interface RuntimeLocalDeviceProfile {
492
+ deviceId: string | null;
493
+ deviceName: string;
494
+ platformType: string;
495
+ userId: string | null;
496
+ capabilities: NonNullable<RuntimeLocalDeviceInfo['capabilities']>;
497
+ lastSeenAt: string;
498
+ localNodeId?: string | null;
499
+ }
500
+ interface RuntimeManagedSessionOptions {
501
+ platform: RuntimeManagedSessionPlatform;
502
+ sessionKind?: RuntimeManagedSessionKind;
503
+ scope?: string | null;
504
+ autoConnectPolicy?: RuntimeAutoConnectPolicy;
505
+ presencePolicy?: RuntimePresencePolicy;
506
+ userId?: string | null;
507
+ deviceName?: string;
508
+ localDeviceId?: string | null;
509
+ metadata?: string;
510
+ autoConnect?: boolean;
511
+ presence?: boolean;
512
+ }
513
+ interface RuntimeManagedSession {
514
+ mode: RuntimeManagedSessionPlatform;
515
+ device: RuntimeLocalDeviceProfile;
516
+ stop: () => Promise<void>;
517
+ }
518
+ type ConnectionTarget = RuntimeConnectionTarget;
519
+ type PeerConnectOptions = RuntimePeerConnectOptions;
520
+ type PeerStreamOptions = RuntimePeerStreamOptions;
521
+ type ScopedChannelOptions = RuntimeScopedChannelOptions;
522
+ type ScopedLogicalChannel = RuntimeScopedLogicalChannel;
523
+ type IncomingStream = RuntimeIncomingStream;
524
+ type ConnectionEvent = RuntimeConnectionEvent;
525
+ type RawConnection = RuntimeRawConnection;
526
+ type PresenceLoopOptions = RuntimePresenceLoopOptions;
527
+ type PeerReadiness = RuntimePeerReadinessSnapshot;
528
+ type DeviceProfile = RuntimeLocalDeviceProfile;
529
+ type SessionOptions$1 = RuntimeManagedSessionOptions;
530
+ type Session$1 = RuntimeManagedSession;
531
+ type ChannelDescriptor = RuntimeChannelDescriptor;
532
+
356
533
  interface SessionOptions {
357
534
  /**
358
535
  * Logical purpose of this session. Used as a peer-scope tag on all
@@ -430,6 +607,7 @@ declare class ScopedSession implements Session {
430
607
  disconnectPeer(peerId: string): Promise<void>;
431
608
  onIncomingStream(callback: (stream: RuntimeIncomingStream) => void): () => void;
432
609
  dispose(): void;
610
+ private releaseScopedPeersOnDispose;
433
611
  private assertNotDisposed;
434
612
  }
435
613
 
@@ -437,29 +615,29 @@ declare class ScopedSession implements Session {
437
615
  * Phase 4 of the OpenRTC auth + connection remediation plan
438
616
  * (`docs/plans/openrtc-auth-connection-remediation-plan.md`).
439
617
  *
440
- * `DriveGrantActorHandle` is the TS mirror of the Rust
441
- * `DriveGrantConnectionActor` (see `openrtc/crates/openrtc/src/client/drive_grant_actor.rs`).
442
- * It is the single observer-facing API for drive-grant guest connections:
443
- * `DriveViewClient`, `DriveShareRedeemPage`, and friends call this handle
444
- * instead of dialing/retrying directly. With one handle per
445
- * `(scope, peerNodeId)` key, multiple requesters that share a connection
446
- * coalesce their `ensureReady()` calls onto a single underlying dial
447
- * removing the lifecycle-replacement race documented in `logs-desktop-A.md`.
618
+ * `DriveGrantActorHandle` is the historical implementation behind the public
619
+ * `ScopedConnectionActorHandle` alias. It is a TypeScript observer-side dial
620
+ * coalescer for scoped protocol routes such as drive-grant guests and
621
+ * trusted-device drive-view. It mirrors the intended Rust actor shape but is
622
+ * not backed by the Rust actor today. With one handle per `(scope, peerNodeId)`
623
+ * key, participating callers coalesce `ensureReady()` onto one underlying
624
+ * `connectPeer` call. It is not a general peer lifecycle authority.
448
625
  *
449
626
  * ## Scope
450
627
  *
451
- * Phase 4 lands the handle + `RuntimeClient.getDriveGrantActor` accessor
452
- * + observer-side rewiring. The actor is **not yet** wired into the
453
- * underlying Rust dial path the legacy `connectPeer` codepath continues
454
- * to run (Phase 7 flips that). What changes today:
628
+ * The handle is not wired into the underlying Rust dial path; it delegates to
629
+ * the existing `connectPeer` path. It remains a transitional compatibility
630
+ * layer until the runtime chooses one actor implementation and wires it
631
+ * end-to-end. What it does today:
455
632
  *
456
- * - `DriveViewClient.ensureGrantConnected` calls
457
- * `runtimeClient.getDriveGrantActor(scope, peerNodeId).ensureReady()`,
458
- * then performs its existing `connectPeer` call inside the
459
- * handle-owned `dial` function. Multiple concurrent observers for the
460
- * same key thus coalesce to one `connectPeer` call.
461
- * - `DeviceContext` for `app-user-device` is bit-for-bit unchanged —
462
- * this file only loads when `sessionKind === 'drive-grant-guest'`.
633
+ * - `DriveGrantConnectionPipeline` and trusted-device drive-view acquisition call
634
+ * `runtimeClient.getScopedConnectionActor(scope, peerNodeId).ensureReady()`,
635
+ * then perform their existing `connectPeer` call inside the handle-owned
636
+ * `dial` function. Multiple concurrent observers for the same key thus
637
+ * coalesce to one `connectPeer` call.
638
+ * - `DeviceContext` for the managed `app-user-device` session remains
639
+ * separate. It owns app presence/auto-connect and must not call this
640
+ * observer coalescer directly.
463
641
  *
464
642
  * ## Coalescing semantics
465
643
  *
@@ -479,16 +657,22 @@ declare class ScopedSession implements Session {
479
657
  * | --(failure)--> `idle`
480
658
  * `<any>` --(shutdown)--> `closed`
481
659
  *
482
- * The Rust actor enforces the same shape; the two are intentionally kept
483
- * isomorphic so Phase 7 can collapse the TS handle into a thin observer
484
- * over the Rust actor without changing call sites.
660
+ * WebRTC upgrade/readiness is intentionally absent from this actor. The
661
+ * runtime `Connection`/peer lifecycle owns transport promotion. Actor callers
662
+ * may observe that lifecycle separately, but this handle must not become a
663
+ * second WebRTC-ready emitter.
664
+ *
665
+ * The Rust actor has a similar shape but is inactive scaffolding in the current
666
+ * workspace. Treat the two implementations as migration debt, not redundant
667
+ * production authorities.
485
668
  */
486
669
  type DriveGrantActorErrorKind = 'shutdown' | 'dial-failed' | 'not-ready' | 'logical-channel-not-implemented';
487
670
  declare class DriveGrantActorError extends Error {
488
671
  readonly kind: DriveGrantActorErrorKind;
489
672
  constructor(kind: DriveGrantActorErrorKind, message: string);
490
673
  }
491
- type DriveGrantWebRtcState = 'unknown' | 'disabled' | 'connecting' | 'connected' | 'failed';
674
+ type DriveGrantActorState = 'idle' | 'dialing' | 'base-ready' | 'protocol-ready' | 'degraded' | 'closed';
675
+ type DriveGrantPreferredTransport = 'webrtc' | 'base' | 'none';
492
676
  interface DriveGrantConnectionKey {
493
677
  scope: string;
494
678
  peerNodeId: string;
@@ -514,22 +698,23 @@ interface DriveGrantActorHandleOptions {
514
698
  interface DriveGrantLogicalChannelHandle {
515
699
  label: string;
516
700
  }
517
- type WebRtcListener = (state: DriveGrantWebRtcState) => void;
518
701
  declare class DriveGrantActorHandle {
519
702
  readonly key: DriveGrantConnectionKey;
520
703
  private readonly dial;
521
704
  private readonly close;
522
705
  private state;
523
706
  private inFlight;
524
- private webrtcState;
525
- private webrtcListeners;
526
707
  constructor(options: DriveGrantActorHandleOptions);
527
708
  /** Returns true if the actor has resolved a successful dial and is not shut down. */
528
709
  isReady(): boolean;
529
710
  /** Returns true after `shutdown()` has been called. */
530
711
  isClosed(): boolean;
712
+ getState(): DriveGrantActorState;
713
+ getPreferredTransport(): DriveGrantPreferredTransport;
714
+ markProtocolReady(): void;
715
+ markDegraded(): void;
531
716
  /**
532
- * Phase 7 correlation context. Always populated with
717
+ * Correlation context. Always populated with
533
718
  * `sessionKind=drive-grant-guest`, `scope`, `peerNodeId`, and
534
719
  * (when the scope encodes one) `grantId`.
535
720
  */
@@ -540,15 +725,6 @@ declare class DriveGrantActorHandle {
540
725
  * `shutdown()` flips state to `closed`.
541
726
  */
542
727
  ensureReady(): Promise<void>;
543
- /** Subscribe to WebRTC adoption state. Returns an unsubscribe fn. */
544
- observeWebRtc(listener: WebRtcListener): () => void;
545
- /** Current WebRTC state without subscribing. */
546
- getWebRtcState(): DriveGrantWebRtcState;
547
- /**
548
- * Test/Phase-7 hook: lifecycle code that learns of a WebRTC state
549
- * transition pushes it via this method so observers see the change.
550
- */
551
- setWebRtcState(state: DriveGrantWebRtcState): void;
552
728
  /**
553
729
  * Phase 6: returns a logical channel handle backed by the base iroh
554
730
  * transport. The host-side `DriveViewBaseIrohHandler` dispatches
@@ -565,8 +741,7 @@ declare class DriveGrantActorHandle {
565
741
  openLogicalChannelTestStub(label: string): Promise<DriveGrantLogicalChannelHandle>;
566
742
  /**
567
743
  * Idempotent shutdown. Subsequent `ensureReady()` rejects with
568
- * `shutdown`; subsequent `shutdown()` is a no-op. Observers receive
569
- * a final `'failed'` state so they can wire teardown side-effects.
744
+ * `shutdown`; subsequent `shutdown()` is a no-op.
570
745
  */
571
746
  shutdown(): Promise<void>;
572
747
  }
@@ -657,15 +832,18 @@ declare class RuntimeClient {
657
832
  private managedSessionTransition;
658
833
  private managedSessionStartInFlight;
659
834
  private readonly managedReconciliationTasks;
835
+ private managedReconciliationTimer;
836
+ private readonly managedReconciliationFollowupTimers;
837
+ private nativeIncomingMessageSubscriptionAttached;
660
838
  /**
661
- * Phase 4: per-`{scope, peerNodeId}` scoped-connection actor registry.
662
- * Lazily created; `null` until an observer first calls `getDriveGrantActor`
663
- * (legacy name) or `getScopedConnectionActor` (generic alias).
839
+ * The one per-`{scope, peerNodeId}` observer-side dial coalescer. It is
840
+ * intentionally separate from the peer lifecycle read model: it requests a
841
+ * scoped route through `connectPeer`, while `Client` owns connection state.
664
842
  *
665
- * Note: persistent same-account auto-connect (`app-user-device`) and
666
- * anonymous share flows (`share-anonymous`) do not use this coalescer.
843
+ * Persistent same-account auto-connect (`app-user-device`) and anonymous
844
+ * share flows (`share-anonymous`) do not use this grant-guest coalescer.
667
845
  */
668
- private driveGrantActorRegistry;
846
+ private scopedConnectionActorRegistry;
669
847
  private static describeTicketForLogs;
670
848
  private serializeManagedSessionOperation;
671
849
  constructor(options: RuntimeClientOptions, backend?: ISignalingBackend | null);
@@ -674,7 +852,6 @@ declare class RuntimeClient {
674
852
  private resolveManagedAutoConnectEnabled;
675
853
  private resolveManagedPresenceEnabled;
676
854
  private getEffectiveRuntimeUserId;
677
- private sleep;
678
855
  private fetchAndApplyAppLimits;
679
856
  private resolvePeerStreamTarget;
680
857
  private compatPeerConnectionFromTarget;
@@ -682,11 +859,14 @@ declare class RuntimeClient {
682
859
  private hasManagedSession;
683
860
  private usesNativeIpcBackend;
684
861
  private startManagedReconciliation;
862
+ private scheduleManagedReconciliationFollowups;
863
+ private stopManagedReconciliation;
685
864
  private notifyManagedReconciliation;
686
865
  reconcileNativeManagedSession(reason?: string): Promise<void>;
687
866
  private connectionStateSnapshotKey;
688
867
  private runNativeStartLoop;
689
868
  private isUnsupportedRuntimeAdapterError;
869
+ private isRecoverableNativeManagedSessionStartError;
690
870
  onIdentityChange(callback: (identity: RuntimeAuthIdentity | null) => void): () => void;
691
871
  signInAnonymously(): Promise<void>;
692
872
  signInWithPluto(): Promise<void>;
@@ -699,8 +879,13 @@ declare class RuntimeClient {
699
879
  localNodeId?: string;
700
880
  userId?: string;
701
881
  }>;
882
+ getPeerReconciliationStats(): Readonly<PeerReconciliationStats>;
702
883
  getRuntimePolicy(): RuntimePolicySnapshot;
703
884
  getRuntimeCapabilities(): RuntimeCapabilities;
885
+ getCapabilityProfile(): RuntimeCapabilityProfile;
886
+ private attachNativeIncomingMessageSubscription;
887
+ private attachNativeIncomingStreamSource;
888
+ private resolveNativeIncomingStreamSource;
704
889
  start(options?: {
705
890
  clearSessionTokens?: boolean;
706
891
  }): Promise<void>;
@@ -790,6 +975,7 @@ declare class RuntimeClient {
790
975
  */
791
976
  connectDevice(device: RuntimeDevice, options: RuntimeDeviceConnectOptions): Promise<ConnectDeviceResult>;
792
977
  watchPeerStates(callback: (peers: RuntimePeerState[]) => void): () => void;
978
+ watchPeerLifecycle(callback: (peers: RuntimePeerState[]) => void): () => void;
793
979
  getPeerState(id: string): RuntimePeerState | undefined;
794
980
  resolvePeerConnectionRecords(id: string): Promise<ManagedConnectionRecord[]>;
795
981
  getPeerSession(id: string): Promise<PeerSessionSnapshot | null>;
@@ -804,8 +990,9 @@ declare class RuntimeClient {
804
990
  getPeerScopes(id: string): string[];
805
991
  isSamePeer(a: string, b: string): boolean;
806
992
  refreshPeerSnapshot(): Promise<RuntimePeerState[]>;
807
- subscribeSessions(localDeviceId: string, callback: (session: RuntimeSession) => void): Promise<() => void>;
993
+ subscribeSessions(localDeviceId: string, callback: (session: RuntimeSession) => void | Promise<void>): Promise<() => void>;
808
994
  getAppLimits(): AppLimits;
995
+ private syncRoomRtdbPresenceFromBackend;
809
996
  createRoom(roomId?: string): Promise<string>;
810
997
  joinRoom(roomId: string, options?: RuntimeJoinRoomOptions): Promise<Connection[]>;
811
998
  leaveRoom(roomId: string): Promise<void>;
@@ -814,26 +1001,24 @@ declare class RuntimeClient {
814
1001
  /** @deprecated Use connect() (no-arg) for lifecycle, or connectTo() for peer connections */
815
1002
  connectByTicket(target: RuntimeConnectionTarget): Promise<Connection>;
816
1003
  connectPeer(options: RuntimePeerConnectOptions): Promise<Connection>;
1004
+ connectScopedChannel(options: RuntimeScopedChannelOptions): Promise<RuntimeScopedLogicalChannel>;
817
1005
  /**
818
- * Phase 4 of the OpenRTC auth + connection remediation plan:
819
- * returns the per-`{scope, peerNodeId}` actor handle that
820
- * drive-grant guest observers (DriveViewClient,
821
- * DriveShareRedeemPage) coalesce through. The first call for a
822
- * given key spawns the handle; subsequent calls return the same
1006
+ * Returns the per-`{scope, peerNodeId}` observer-side coalescer that
1007
+ * participating scoped guest callers (such as DriveViewClient) use. The
1008
+ * first call for a given key spawns the handle; subsequent calls return the same
823
1009
  * handle so concurrent observers share a single underlying dial.
824
1010
  *
825
- * The handle's `dial` function delegates to this RuntimeClient's
826
- * existing `connectPeer` API the actor is purely an observer
827
- * coalescer in Phase 4. Phase 7 collapses this onto the Rust
828
- * actor in `Client::drive_grant_actor_registry`.
1011
+ * The handle's `dial` function delegates to this RuntimeClient's existing
1012
+ * `connectPeer` API. It is a compatibility coalescer, not an independent
1013
+ * transport owner, and the Rust registry is not enabled by workspace
1014
+ * production startup.
829
1015
  *
830
- * Calling this from `app-user-device` paths is a programming bug
831
- * the registry is scoped to drive-grant-guest connections by
832
- * convention, and same-account auto-connect must not coalesce
833
- * onto a single shared dial (each device-pair has its own
834
- * lifecycle).
1016
+ * Managed `app-user-device` startup/presence/auto-connect must not call
1017
+ * this observer coalescer directly. Scoped protocol observers may use it
1018
+ * only with a narrow `dial` callback that delegates back through
1019
+ * `connectPeer`; the runtime `Client` remains the peer lifecycle owner.
835
1020
  */
836
- getDriveGrantActor(key: DriveGrantConnectionKey | {
1021
+ getScopedConnectionActor(key: DriveGrantConnectionKey | {
837
1022
  scope: string;
838
1023
  peerNodeId: string;
839
1024
  }, options?: {
@@ -851,37 +1036,26 @@ declare class RuntimeClient {
851
1036
  * router/disconnect handler to the connection object) can
852
1037
  * supply their own `dial` here. The actor's coalescer still
853
1038
  * ensures only one `dial(key)` runs even with N concurrent
854
- * `getDriveGrantActor(...).ensureReady()` callers — this just
1039
+ * `getScopedConnectionActor(...).ensureReady()` callers — this just
855
1040
  * lets the caller decide what "dial" means.
856
1041
  */
857
1042
  dial?: (key: DriveGrantConnectionKey) => Promise<void>;
858
1043
  }): DriveGrantActorHandle;
859
- /**
860
- * Domain-agnostic alias for `getDriveGrantActor`.
861
- *
862
- * OpenRTC’s per-`{scope, peerNodeId}` actor is a generic “scoped connection”
863
- * coalescer. Plutonium’s drive-grant flows were the first adopter, but other
864
- * app protocols (magic-link, user-device, etc.) may also want the same shape.
865
- *
866
- * Backward-compat: this is a thin wrapper with identical behavior.
867
- */
868
- getScopedConnectionActor(key: DriveGrantConnectionKey | {
869
- scope: string;
870
- peerNodeId: string;
871
- }, options?: Parameters<RuntimeClient['getDriveGrantActor']>[1]): DriveGrantActorHandle;
872
- /** Domain-agnostic alias for `shutdownDriveGrantActors`. */
1044
+ private assertScopedDialAllowed;
1045
+ private scopedDialCandidateIds;
1046
+ private hasManualDisconnectSuppression;
1047
+ /** Shuts down every scoped observer handle during managed-session teardown. */
873
1048
  shutdownScopedConnectionActors(): Promise<void>;
874
- /**
875
- * Phase 4: tear down all live drive-grant actors. Called from
876
- * managed-session teardown so a refresh between drive-grant guest
877
- * routes does not leak observers across navigations.
878
- */
879
- shutdownDriveGrantActors(): Promise<void>;
880
1049
  openPeerBi(id: string, options?: RuntimePeerStreamOptions): Promise<{
881
1050
  connection: Connection;
882
1051
  readable: ReadableStream<Uint8Array>;
883
1052
  writable: WritableStream<Uint8Array>;
884
1053
  }>;
1054
+ openPeerNativeBi(id: string, label: string, options?: RuntimePeerStreamOptions): Promise<{
1055
+ connection: Connection;
1056
+ readable: ReadableStream<Uint8Array>;
1057
+ writable: WritableStream<Uint8Array>;
1058
+ }>;
885
1059
  /** Like openPeerBi but skips the settled_ready requirement — usable for latency probes. */
886
1060
  openPeerBiTransportOnly(id: string, options?: RuntimePeerStreamOptions): Promise<{
887
1061
  connection: Connection;
@@ -929,8 +1103,15 @@ declare class RuntimeClient {
929
1103
  forceDisconnectPeer(id: string): Promise<void>;
930
1104
  getPeerHealth(id: string): Promise<RuntimePeerHealth>;
931
1105
  getConnectionStates(): Promise<RuntimeConnectionStateSnapshot[]>;
1106
+ /**
1107
+ * Subscribe to raw backend connection diagnostics.
1108
+ *
1109
+ * This compatibility surface intentionally does not drive peer lifecycle or
1110
+ * UI readiness; use watchPeerStates() for the reducer-owned lifecycle view.
1111
+ */
932
1112
  onConnectionStateChange(callback: (state: RuntimeConnectionStateSnapshot) => void): Promise<() => void> | (() => void);
933
1113
  getConnections(): Connection[];
1114
+ getApplicationReadyConnections(options?: RuntimeApplicationPayloadReadinessOptions): Connection[];
934
1115
  onConnection(callback: (connection: Connection) => void): () => void;
935
1116
  onDisconnection(callback: (connection: Connection) => void): () => void;
936
1117
  onConnectionEvent(callback: (event: RuntimeConnectionEvent) => void): () => void;
@@ -939,6 +1120,17 @@ declare class RuntimeClient {
939
1120
  connect(): Promise<void>;
940
1121
  /** Disconnect from the runtime (alias for stop()) */
941
1122
  disconnect(): void;
1123
+ /**
1124
+ * Terminal teardown of the runtime instance. Unlike stop()/disconnect()
1125
+ * (which only stop the listen loop and are used during normal operation),
1126
+ * destroy() tears down the underlying Client's coordinated dispose path —
1127
+ * document/window listeners, the periodic auth-refresh interval, and signaling
1128
+ * subscriptions (audit V2). Call this only when the runtime singleton is truly
1129
+ * going away (app shutdown, HMR dispose), never on a routine disconnect.
1130
+ */
1131
+ destroy(): void;
1132
+ /** Alias for destroy(); supports callers that expect a `dispose()` contract. */
1133
+ dispose(): void;
942
1134
  /** Alias for onIdentityChange */
943
1135
  onAuthChange(callback: (identity: RuntimeAuthIdentity | null) => void): () => void;
944
1136
  /** Alias for setAuthContext */
@@ -1038,4 +1230,65 @@ declare function createWasmClient(options: RuntimeFactoryOptions): PlutoRuntimeA
1038
1230
  */
1039
1231
  declare function createIpcClient(options: RuntimeFactoryOptions, bridge: PlutoIpcBridge): PlutoRuntimeAssembly<IpcRuntimeAdapter>;
1040
1232
 
1041
- export { DriveGrantActorError as $, type RuntimeAuthIdentity as A, type RuntimeAutoConnectPolicy as B, Connection as C, type DeviceProfile as D, type RuntimeConnectionTarget as E, type RuntimeDeviceStatus as F, type RuntimeJoinRoomOptions as G, type RuntimeManagedSession as H, type IncomingStream as I, type RuntimeManagedSessionKind as J, type RuntimeManagedSessionOptions as K, type RuntimeManagedSessionPlatform as L, type RuntimePeerLifecycleStatus as M, type RuntimePeerScope as N, OPENRTC_RUNTIME_POLICY as O, type PlutoRuntimeAssembly as P, type RuntimePeerSessionSnapshot as Q, type RuntimeClientOptions as R, type RuntimePresenceLoopOptions as S, type RuntimePresencePolicy as T, type RuntimeRoomMember as U, type RuntimeSession as V, type RuntimeUiConnectionStatus as W, ScopedSession as X, type Session as Y, type SessionOptions as Z, getRuntimePolicy as _, RuntimeClient as a, type DriveGrantActorErrorKind as a0, DriveGrantActorHandle as a1, type DriveGrantActorHandleOptions as a2, DriveGrantActorRegistry as a3, type DriveGrantClose as a4, type DriveGrantConnectionKey as a5, type DriveGrantDial as a6, type DriveGrantLogicalChannelHandle as a7, type DriveGrantWebRtcState as a8, type RuntimeFactoryOptions as a9, type Session$1 as aa, type SessionOptions$1 as ab, createIpcClient as ac, createWasmClient as ad, driveGrantConnectionKey as ae, driveGrantConnectionKeyToString as af, type RuntimeDevice as b, type RuntimeDeviceStatusSnapshot as c, type RuntimeDeviceConnectOptions as d, type RuntimeLocalDeviceInfo as e, type RuntimeLocalDeviceProfile as f, type RuntimePeerState as g, type RuntimePeerConnectOptions as h, type RuntimePeerReadinessSnapshot as i, type RuntimePeerHealth as j, type RuntimeChannelDescriptor as k, type RuntimePeerStreamOptions as l, type RuntimeIncomingStream as m, type RuntimePolicySnapshot as n, type RuntimeConnectionStateSnapshot as o, type RuntimeConnectionEvent as p, type RuntimeRawConnection as q, type ChannelDescriptor as r, type ConnectionEvent as s, type ConnectionTarget as t, type PeerConnectOptions as u, type PeerReadiness as v, type PeerStreamOptions as w, type PresenceLoopOptions as x, type RawConnection as y, type RuntimeAuthContext as z };
1233
+ interface RuntimeDeviceActiveOptions {
1234
+ now?: number;
1235
+ /**
1236
+ * Optional local freshness window for durable roster rows. Leave undefined
1237
+ * when the caller only wants to honor explicit online/expiresAt state.
1238
+ */
1239
+ staleAfterMs?: number;
1240
+ }
1241
+ type RuntimeDeviceActivityStatus = Pick<Partial<RuntimeDevice>, 'online' | 'lastSeenAt' | 'updatedAt' | 'createdAt' | 'expiresAt'>;
1242
+ type RuntimeDeviceRouteStatus = Pick<Partial<RuntimeDeviceStatusSnapshot>, 'settledReady' | 'readinessState' | 'connectionStatus'>;
1243
+ type RuntimeDevicePresenceStatus = Pick<RuntimeDevice, 'online' | 'ticket'> & Pick<Partial<RuntimeDeviceStatusSnapshot>, 'connectable' | 'presenceStatus' | 'online' | 'ticket'>;
1244
+ /**
1245
+ * True when a runtime device row is currently live enough to display or
1246
+ * consider for app-level routing. This is deliberately separate from
1247
+ * `isRuntimeDeviceConnectable`: a peer can be active/present before it has a
1248
+ * usable ticket, and a stale durable row should not keep app UIs alive.
1249
+ */
1250
+ declare function isRuntimeDeviceActive(device: RuntimeDeviceActivityStatus, options?: RuntimeDeviceActiveOptions): boolean;
1251
+ /**
1252
+ * True once OpenRTC has a routable peer projection for normal app payloads.
1253
+ *
1254
+ * This intentionally does not inspect discovery metadata or device
1255
+ * capabilities. Those fields describe the remote endpoint roster entry; they
1256
+ * are not proof that an application-level protocol has exchanged its own
1257
+ * ready/capability message over the mesh.
1258
+ */
1259
+ declare function isRuntimeDeviceRoutable(device: RuntimeDeviceRouteStatus): boolean;
1260
+ /**
1261
+ * True when the discovery row currently has enough fresh presence to attempt
1262
+ * a dial. This is a discovery gate, not a connected/readiness gate.
1263
+ */
1264
+ declare function isRuntimeDeviceConnectable(device: RuntimeDevicePresenceStatus): boolean;
1265
+
1266
+ /**
1267
+ * Pure byte / stream / timing helpers.
1268
+ *
1269
+ * Extracted from the `Client` god object (see
1270
+ * `docs/plans/client-ts-decomposition.md` Phase 1). These functions are
1271
+ * deliberately pure — no `Client` state, no `this`, no logging side effects — so
1272
+ * the same helper is reused everywhere instead of being re-implemented inline
1273
+ * (e.g. `concatBytes` previously existed both here and in `applicationCrypto.ts`,
1274
+ * and three near-identical exponential-backoff helpers existed across the class).
1275
+ */
1276
+
1277
+ type ByteArray = Uint8Array<ArrayBuffer>;
1278
+ type ByteArrayLike = Uint8Array<ArrayBufferLike>;
1279
+
1280
+ type RuntimeU32BEFrameHandler = (payload: ByteArray) => void;
1281
+ type RuntimeJsonFrameHandler<T = unknown> = (value: T, payload: ByteArray) => void;
1282
+ interface RuntimeJsonFrameDecoderOptions {
1283
+ onInvalidJson?: (error: unknown, payload: ByteArray) => void;
1284
+ }
1285
+ declare function encodeRuntimeU32BEFrame(payload: ByteArrayLike): ByteArray;
1286
+ declare function createRuntimeU32BEFrameDecoder(onFrame: RuntimeU32BEFrameHandler): (chunk: ByteArrayLike) => void;
1287
+ declare function encodeRuntimeJsonPayload(value: unknown): ByteArray;
1288
+ declare function decodeRuntimeJsonPayload<T = unknown>(payload: ByteArrayLike): T;
1289
+ declare function encodeRuntimeJsonFrame(value: unknown): ByteArray;
1290
+ declare function createRuntimeJsonFrameDecoder<T = unknown>(onValue: RuntimeJsonFrameHandler<T>, options?: RuntimeJsonFrameDecoderOptions): (chunk: ByteArrayLike) => void;
1291
+ declare function concatRuntimeBytes(a: ByteArrayLike, b: ByteArrayLike): ByteArray;
1292
+ declare function readRuntimeU32BE(bytes: ByteArrayLike, offset: number): number;
1293
+
1294
+ export { type RuntimePresenceLoopOptions as $, type ConnectionEvent as A, type ConnectionTarget as B, Connection as C, type DeviceProfile as D, type IncomingStream as E, type PeerConnectOptions as F, type PeerReadiness as G, type PeerStreamOptions as H, type ITransport as I, type PresenceLoopOptions as J, type RawConnection as K, type RuntimeAuthContext as L, type RuntimeAuthIdentity as M, type RuntimeAutoConnectPolicy as N, OPENRTC_RUNTIME_POLICY as O, type PeerReconciliationStats as P, type RuntimeConnectionTarget as Q, type RuntimeClientOptions as R, type RuntimeDevicePresenceStatus$1 as S, type RuntimeDeviceStatus as T, type RuntimeJsonFrameHandler as U, type RuntimeLogicalChannelFraming as V, type RuntimeManagedSessionKind as W, type RuntimeManagedSessionPlatform as X, type RuntimePeerLifecycleStatus as Y, type RuntimePeerScope as Z, type RuntimePeerSessionSnapshot as _, RuntimeClient as a, type RuntimePresencePolicy as a0, type RuntimeSession as a1, type RuntimeU32BEFrameHandler as a2, type RuntimeUiConnectionStatus as a3, type ScopedChannelOptions as a4, type ScopedLogicalChannel as a5, ScopedSession as a6, type Session as a7, type SessionOptions as a8, createRuntimeJsonFrameDecoder as a9, createIpcClient as aA, createWasmClient as aB, readRuntimeU32BE as aC, driveGrantConnectionKey as aD, driveGrantConnectionKeyToString as aE, createRuntimeU32BEFrameDecoder as aa, decodeRuntimeJsonPayload as ab, encodeRuntimeJsonFrame as ac, encodeRuntimeJsonPayload as ad, encodeRuntimeU32BEFrame as ae, getRuntimePolicy as af, isRuntimeDeviceActive as ag, isRuntimeDeviceConnectable as ah, isRuntimeDeviceRoutable as ai, type RuntimeDeviceActiveOptions as aj, type RuntimeFactoryOptions as ak, type RuntimeJsonFrameDecoderOptions as al, DriveGrantActorError as am, type DriveGrantActorErrorKind as an, DriveGrantActorHandle as ao, type DriveGrantActorHandleOptions as ap, DriveGrantActorRegistry as aq, type DriveGrantActorState as ar, type DriveGrantClose as as, type DriveGrantDial as at, type DriveGrantConnectionKey as au, type DriveGrantLogicalChannelHandle as av, type DriveGrantPreferredTransport as aw, type Session$1 as ax, type SessionOptions$1 as ay, concatRuntimeBytes as az, type RuntimeManagedSessionOptions as b, type RuntimeManagedSession as c, type RuntimeDevice as d, type RuntimeDeviceStatusSnapshot as e, type RuntimeDeviceConnectOptions as f, type RuntimeLocalDeviceInfo as g, type RuntimeLocalDeviceProfile as h, type RuntimePeerState as i, type RuntimePeerConnectOptions as j, type RuntimePeerReadinessSnapshot as k, type RuntimePeerHealth as l, type RuntimeChannelDescriptor as m, type RuntimePeerStreamOptions as n, type RuntimeIncomingStream as o, type RuntimeScopedChannelOptions as p, type RuntimeScopedLogicalChannel as q, type RuntimeJoinRoomOptions as r, type RuntimeRoomMember as s, type RuntimePolicySnapshot as t, type RuntimeConnectionStateSnapshot as u, type RuntimeApplicationPayloadReadinessOptions as v, type RuntimeConnectionEvent as w, type PlutoRuntimeAssembly as x, type RuntimeRawConnection as y, type ChannelDescriptor as z };