openrtc 0.1.0

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 (122) hide show
  1. package/README.md +106 -0
  2. package/dist/OpenRTC.d.ts +71 -0
  3. package/dist/OpenRTC.js +130 -0
  4. package/dist/api/MediaTransport.d.ts +7 -0
  5. package/dist/api/MediaTransport.js +262 -0
  6. package/dist/api/PlutoPeerConnection.d.ts +50 -0
  7. package/dist/api/PlutoPeerConnection.js +434 -0
  8. package/dist/api/PlutoWebSocket.d.ts +24 -0
  9. package/dist/api/PlutoWebSocket.js +113 -0
  10. package/dist/api/PlutoWebTransport.d.ts +29 -0
  11. package/dist/api/PlutoWebTransport.js +105 -0
  12. package/dist/auth/host.d.ts +73 -0
  13. package/dist/auth/host.js +513 -0
  14. package/dist/auth/index.d.ts +1 -0
  15. package/dist/auth/index.js +1 -0
  16. package/dist/auth/internal.d.ts +1 -0
  17. package/dist/auth/internal.js +1 -0
  18. package/dist/compat/index.d.ts +20 -0
  19. package/dist/compat/index.js +20 -0
  20. package/dist/core/Client.d.ts +352 -0
  21. package/dist/core/Client.js +3689 -0
  22. package/dist/core/Connection.d.ts +67 -0
  23. package/dist/core/Connection.js +404 -0
  24. package/dist/core/IEngineBridge.d.ts +18 -0
  25. package/dist/core/IEngineBridge.js +1 -0
  26. package/dist/core/ISignalingTransport.d.ts +13 -0
  27. package/dist/core/ISignalingTransport.js +1 -0
  28. package/dist/core/PeerLifecycleManager.d.ts +43 -0
  29. package/dist/core/PeerLifecycleManager.js +330 -0
  30. package/dist/core/PeerProjectionStore.d.ts +43 -0
  31. package/dist/core/PeerProjectionStore.js +327 -0
  32. package/dist/core/PlutoRTC.d.ts +13 -0
  33. package/dist/core/PlutoRTC.js +26 -0
  34. package/dist/core/Room.d.ts +47 -0
  35. package/dist/core/Room.js +376 -0
  36. package/dist/core/Signaling.d.ts +39 -0
  37. package/dist/core/Signaling.js +147 -0
  38. package/dist/core/bridges/BridgeCodecs.d.ts +19 -0
  39. package/dist/core/bridges/BridgeCodecs.js +317 -0
  40. package/dist/core/bridges/BrowserFirestoreRooms.d.ts +36 -0
  41. package/dist/core/bridges/BrowserFirestoreRooms.js +169 -0
  42. package/dist/core/bridges/BrowserFirestoreSignaling.d.ts +111 -0
  43. package/dist/core/bridges/BrowserFirestoreSignaling.js +1010 -0
  44. package/dist/core/bridges/NativeIpcBridge.d.ts +156 -0
  45. package/dist/core/bridges/NativeIpcBridge.js +1472 -0
  46. package/dist/core/bridges/RtdbPresence.d.ts +122 -0
  47. package/dist/core/bridges/RtdbPresence.js +201 -0
  48. package/dist/core/bridges/TauriBridge.d.ts +8 -0
  49. package/dist/core/bridges/TauriBridge.js +7 -0
  50. package/dist/core/bridges/WasmBridge.d.ts +233 -0
  51. package/dist/core/bridges/WasmBridge.js +1811 -0
  52. package/dist/core/config.d.ts +31 -0
  53. package/dist/core/config.js +138 -0
  54. package/dist/core/transports/ITransport.d.ts +12 -0
  55. package/dist/core/transports/ITransport.js +1 -0
  56. package/dist/core/transports/LocalDaemonBridge.d.ts +41 -0
  57. package/dist/core/transports/LocalDaemonBridge.js +227 -0
  58. package/dist/core/transports/TauriSignaling.d.ts +28 -0
  59. package/dist/core/transports/TauriSignaling.js +373 -0
  60. package/dist/core/transports/TransportFactory.d.ts +9 -0
  61. package/dist/core/transports/TransportFactory.js +59 -0
  62. package/dist/core/transports/WasmSignaling.d.ts +78 -0
  63. package/dist/core/transports/WasmSignaling.js +522 -0
  64. package/dist/core/transports/WebRTCTransport.d.ts +23 -0
  65. package/dist/core/transports/WebRTCTransport.js +132 -0
  66. package/dist/core/trust.d.ts +23 -0
  67. package/dist/core/trust.js +99 -0
  68. package/dist/core/types.d.ts +559 -0
  69. package/dist/core/types.js +19 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.js +3 -0
  72. package/dist/moq/MoQMediaTransport.d.ts +6 -0
  73. package/dist/moq/MoQMediaTransport.js +106 -0
  74. package/dist/moq/MoQPublisher.d.ts +22 -0
  75. package/dist/moq/MoQPublisher.js +179 -0
  76. package/dist/moq/MoQSession.d.ts +35 -0
  77. package/dist/moq/MoQSession.js +166 -0
  78. package/dist/moq/MoQSubscriber.d.ts +19 -0
  79. package/dist/moq/MoQSubscriber.js +235 -0
  80. package/dist/moq/common.d.ts +47 -0
  81. package/dist/moq/common.js +304 -0
  82. package/dist/moq/messages.d.ts +105 -0
  83. package/dist/moq/messages.js +367 -0
  84. package/dist/presets/plutoHosted.d.ts +13 -0
  85. package/dist/presets/plutoHosted.js +18 -0
  86. package/dist/runtime/DelegatingRuntimeAdapter.d.ts +68 -0
  87. package/dist/runtime/DelegatingRuntimeAdapter.js +250 -0
  88. package/dist/runtime/IpcRuntimeAdapter.d.ts +9 -0
  89. package/dist/runtime/IpcRuntimeAdapter.js +13 -0
  90. package/dist/runtime/RuntimeClient.d.ts +257 -0
  91. package/dist/runtime/RuntimeClient.js +1484 -0
  92. package/dist/runtime/TauriRuntimeAdapter.d.ts +1 -0
  93. package/dist/runtime/TauriRuntimeAdapter.js +1 -0
  94. package/dist/runtime/WasmRuntimeAdapter.d.ts +6 -0
  95. package/dist/runtime/WasmRuntimeAdapter.js +11 -0
  96. package/dist/runtime/factories.d.ts +24 -0
  97. package/dist/runtime/factories.js +66 -0
  98. package/dist/runtime/index.d.ts +6 -0
  99. package/dist/runtime/index.js +5 -0
  100. package/dist/runtime/ipc.d.ts +8 -0
  101. package/dist/runtime/ipc.js +65 -0
  102. package/dist/runtime/ipcContract.d.ts +46 -0
  103. package/dist/runtime/ipcContract.js +1 -0
  104. package/dist/runtime/tauri.d.ts +16 -0
  105. package/dist/runtime/tauri.js +22 -0
  106. package/dist/runtime/types.d.ts +110 -0
  107. package/dist/runtime/types.js +1 -0
  108. package/dist/transports/TauriTransport.d.ts +17 -0
  109. package/dist/transports/TauriTransport.js +49 -0
  110. package/dist/transports/WebTransport.d.ts +23 -0
  111. package/dist/transports/WebTransport.js +71 -0
  112. package/dist/utils/debug.d.ts +4 -0
  113. package/dist/utils/debug.js +33 -0
  114. package/dist/utils/runtime.d.ts +1 -0
  115. package/dist/utils/runtime.js +10 -0
  116. package/package.json +97 -0
  117. package/wasm/README.md +64 -0
  118. package/wasm/openrtc.d.ts +284 -0
  119. package/wasm/openrtc.js +2132 -0
  120. package/wasm/openrtc_bg.wasm +0 -0
  121. package/wasm/openrtc_bg.wasm.d.ts +110 -0
  122. package/wasm/package.json +16 -0
@@ -0,0 +1,67 @@
1
+ import { ConnectionOptions } from './types';
2
+ export declare class Connection {
3
+ private static readonly textDecoder;
4
+ private static readonly textEncoder;
5
+ private listeners;
6
+ private closeListeners;
7
+ private writer;
8
+ private reader;
9
+ private _isClosed;
10
+ get isClosed(): boolean;
11
+ private receiveBuffer;
12
+ readonly id: string;
13
+ readonly remoteNodeId: string;
14
+ /** @deprecated Compatibility alias for legacy consumers. This is the remote node id, not a device id. */
15
+ readonly deviceId: string;
16
+ readonly localNodeId: string;
17
+ private options;
18
+ private writeMutex;
19
+ private webrtcTransport;
20
+ private upgradeState;
21
+ constructor(id: string, localNodeId: string, remoteNodeId: string, writer: WritableStreamDefaultWriter<Uint8Array>, reader: ReadableStreamDefaultReader<Uint8Array>, options?: ConnectionOptions);
22
+ get transportType(): 'iroh' | 'webrtc' | 'moq';
23
+ peerMoQReady: boolean;
24
+ private mediaListeners;
25
+ /**
26
+ * Send a message to the other peer.
27
+ * Messages can be strings or objects (JSON stringified).
28
+ */
29
+ send(message: string | object | ArrayBuffer | Uint8Array): Promise<void>;
30
+ /**
31
+ * Send media data (video/audio header + payload)
32
+ * Internal use for now.
33
+ */
34
+ sendMedia(type: 'audio' | 'video', data: Uint8Array): Promise<void>;
35
+ private sendTyped;
36
+ /**
37
+ * Disconnect the connection
38
+ */
39
+ disconnect(): Promise<void>;
40
+ /**
41
+ * Listen for incoming messages
42
+ */
43
+ onMessage(callback: (message: any) => void): void;
44
+ /**
45
+ * Listen for incoming media
46
+ */
47
+ onMedia(callback: (type: 'audio' | 'video', data: Uint8Array) => void): void;
48
+ /**
49
+ * Listen for disconnection
50
+ */
51
+ onDisconnect(callback: () => void): void;
52
+ private close;
53
+ private readLoop;
54
+ injectData(chunk: Uint8Array): void;
55
+ private handleData;
56
+ private processDataPayload;
57
+ private isLikelyTextPayload;
58
+ private emitMedia;
59
+ private emitMessage;
60
+ private trackListeners;
61
+ addTrack(track: MediaStreamTrack): RTCRtpSender | null;
62
+ addTransceiver(trackOrKind: MediaStreamTrack | string, init?: RTCRtpTransceiverInit): RTCRtpTransceiver | null;
63
+ onTrack(callback: (event: RTCTrackEvent) => void): void;
64
+ private attemptUpgrade;
65
+ private sendInternalSignal;
66
+ private handleInternalSignal;
67
+ }
@@ -0,0 +1,404 @@
1
+ import { TransportFactory } from './transports/TransportFactory';
2
+ export class Connection {
3
+ get isClosed() { return this._isClosed; }
4
+ constructor(id, localNodeId, remoteNodeId, writer, reader, options = {}) {
5
+ this.listeners = [];
6
+ this.closeListeners = [];
7
+ // We'll keep track of the internal stream
8
+ this.writer = null;
9
+ this.reader = null;
10
+ this._isClosed = false;
11
+ // Buffer for incoming data
12
+ this.receiveBuffer = new Uint8Array(0);
13
+ this.writeMutex = Promise.resolve();
14
+ // Transports
15
+ this.webrtcTransport = null;
16
+ this.upgradeState = 'none';
17
+ this.peerMoQReady = false;
18
+ this.mediaListeners = [];
19
+ // --- Transports and Plugins API ---
20
+ this.trackListeners = [];
21
+ this.id = id;
22
+ this.localNodeId = localNodeId;
23
+ this.remoteNodeId = remoteNodeId;
24
+ this.deviceId = remoteNodeId;
25
+ this.writer = writer;
26
+ this.reader = reader;
27
+ this.options = options;
28
+ this.readLoop();
29
+ if (this.options.rtcConfig) {
30
+ this.attemptUpgrade();
31
+ }
32
+ }
33
+ get transportType() {
34
+ if (this.upgradeState === 'upgraded')
35
+ return 'webrtc';
36
+ if (this.peerMoQReady)
37
+ return 'moq';
38
+ return 'iroh';
39
+ }
40
+ /**
41
+ * Send a message to the other peer.
42
+ * Messages can be strings or objects (JSON stringified).
43
+ */
44
+ async send(message) {
45
+ return this.sendTyped(0, message);
46
+ }
47
+ /**
48
+ * Send media data (video/audio header + payload)
49
+ * Internal use for now.
50
+ */
51
+ async sendMedia(type, data) {
52
+ const typeId = type === 'video' ? 1 : 2;
53
+ // Media always prioritizes efficient transport (MoQ/WebRTC) but fallbacks to Iroh
54
+ // For media, we usually use separate tracks.
55
+ // This method is for "Legacy Iroh Media" or "Datagram Media".
56
+ return this.sendTyped(typeId, data);
57
+ }
58
+ async sendTyped(typeId, message) {
59
+ const context = this.options.transportContext;
60
+ const priorities = context?.priorities || ['webrtc', 'moq', 'iroh'];
61
+ // Prepare Payload
62
+ let payload;
63
+ if (typeof message === 'string') {
64
+ payload = new TextEncoder().encode(message);
65
+ }
66
+ else if (message instanceof Uint8Array) {
67
+ payload = message;
68
+ }
69
+ else if (message instanceof ArrayBuffer) {
70
+ payload = new Uint8Array(message);
71
+ }
72
+ else {
73
+ payload = new TextEncoder().encode(JSON.stringify(message));
74
+ }
75
+ // Construct Frame (for Iroh/Stream transports)
76
+ // [Len 4][Type 1][Payload N]
77
+ const totalLen = 4 + 1 + payload.length;
78
+ const frame = new Uint8Array(totalLen);
79
+ const view = new DataView(frame.buffer);
80
+ view.setUint32(0, 1 + payload.length, false);
81
+ frame[4] = typeId;
82
+ frame.set(payload, 5);
83
+ // --- Priority Loop ---
84
+ for (const transport of priorities) {
85
+ if (transport === 'webrtc') {
86
+ if (this.webrtcTransport && this.upgradeState === 'upgraded') {
87
+ try {
88
+ if (typeId === 0) {
89
+ const data = (typeof message === 'object' && !(message instanceof Uint8Array) && !(message instanceof ArrayBuffer))
90
+ ? JSON.stringify(message)
91
+ : message;
92
+ const buffer = typeof data === 'string' ? new TextEncoder().encode(data) : data;
93
+ await this.webrtcTransport.send(buffer);
94
+ return;
95
+ }
96
+ }
97
+ catch (e) {
98
+ console.warn("[Connection] WebRTC Send Failed:", e);
99
+ }
100
+ }
101
+ }
102
+ else if (transport === 'moq') {
103
+ if (context && context.isMoQReady() && this.peerMoQReady) {
104
+ try {
105
+ // MoQ Data is usually Type 0 (User Data).
106
+ // MoQ tracks are streams.
107
+ // We use `sendMoQ` provided by context.
108
+ // We send the Payload (not the frame) because MoQ framing handle it?
109
+ // The receiver `handleMoQObject` needs to know it's a message.
110
+ // We are sending on `data/peerId`.
111
+ // Receiver processes that track.
112
+ await context.sendMoQ(frame); // Send FRAMED data so receiver can parse Type?
113
+ // Wait, `Frame` includes length header.
114
+ // If we send `Frame` as an object, receiver gets `[Len][Type][Payload]`.
115
+ // We should check how receiver consumes it.
116
+ return;
117
+ }
118
+ catch (e) {
119
+ console.warn("[Connection] MoQ Send Failed:", e);
120
+ }
121
+ }
122
+ }
123
+ else if (transport === 'iroh') {
124
+ if (context?.disableIrohFallback && typeId !== 0) {
125
+ // Start exception: Allow Type 0 (Signaling)?
126
+ // No, `disableIrohFallback` means "Only use for initial bootstrap".
127
+ // If we are here, other transports failed.
128
+ console.warn("[Connection] Iroh fallback disabled, dropping packet.");
129
+ return;
130
+ }
131
+ // Iroh Send Logic (Mutex + Writer)
132
+ if (this.isClosed || !this.writer)
133
+ break; // Should throw?
134
+ const previousMutex = this.writeMutex;
135
+ let releaseMutex;
136
+ this.writeMutex = new Promise((resolve) => { releaseMutex = resolve; });
137
+ try {
138
+ await previousMutex;
139
+ if (this.writer) {
140
+ await this.writer.write(frame);
141
+ return; // Success
142
+ }
143
+ }
144
+ catch (err) {
145
+ console.error('[Connection] Iroh Send error:', err);
146
+ this.close(`Send Error: ${err}`);
147
+ throw err; // Stop loop
148
+ }
149
+ finally {
150
+ releaseMutex();
151
+ }
152
+ }
153
+ }
154
+ // If we get here, all transports failed
155
+ // throw new Error("All transports failed");
156
+ }
157
+ /**
158
+ * Disconnect the connection
159
+ */
160
+ async disconnect() {
161
+ this.close('Explicit Disconnect');
162
+ }
163
+ /**
164
+ * Listen for incoming messages
165
+ */
166
+ onMessage(callback) {
167
+ this.listeners.push(callback);
168
+ }
169
+ /**
170
+ * Listen for incoming media
171
+ */
172
+ onMedia(callback) {
173
+ this.mediaListeners.push(callback);
174
+ }
175
+ /**
176
+ * Listen for disconnection
177
+ */
178
+ onDisconnect(callback) {
179
+ this.closeListeners.push(callback);
180
+ }
181
+ close(reason) {
182
+ if (this._isClosed)
183
+ return;
184
+ console.log(`[Connection] Closing connection ${this.id}. Reason: ${reason || 'Unknown'}`);
185
+ this._isClosed = true;
186
+ // Close writer/reader
187
+ this.writer?.close().catch(() => { });
188
+ this.reader?.cancel().catch(() => { });
189
+ this.writer = null;
190
+ this.reader = null;
191
+ // MoQ session is now managed at the Client level
192
+ // Notify listeners
193
+ this.closeListeners.forEach(cb => cb());
194
+ }
195
+ async readLoop() {
196
+ if (!this.reader)
197
+ return;
198
+ try {
199
+ while (!this.isClosed) {
200
+ const { done, value } = await this.reader.read();
201
+ if (done) {
202
+ this.close('Read Done (Remote Closed)');
203
+ break;
204
+ }
205
+ if (value) {
206
+ this.handleData(value);
207
+ }
208
+ }
209
+ }
210
+ catch (err) {
211
+ console.error('[Connection] Read loop error:', err);
212
+ this.close(`Read Error: ${err}`);
213
+ }
214
+ }
215
+ injectData(chunk) {
216
+ this.handleData(chunk);
217
+ }
218
+ handleData(chunk) {
219
+ // Append to buffer
220
+ const newBuffer = new Uint8Array(this.receiveBuffer.length + chunk.length);
221
+ newBuffer.set(this.receiveBuffer);
222
+ newBuffer.set(chunk, this.receiveBuffer.length);
223
+ this.receiveBuffer = newBuffer;
224
+ // Process messages
225
+ while (true) {
226
+ if (this.receiveBuffer.length < 4)
227
+ break; // Need at least length header
228
+ const view = new DataView(this.receiveBuffer.buffer, this.receiveBuffer.byteOffset, this.receiveBuffer.byteLength);
229
+ const length = view.getUint32(0, false);
230
+ if (this.receiveBuffer.length < 4 + length)
231
+ break; // Wait for full message
232
+ // Extract message
233
+ // First byte of body is Type
234
+ // Body is technically at index 4, length includes Type
235
+ const body = this.receiveBuffer.slice(4, 4 + length);
236
+ // However, we need to handle Legacy fallback?
237
+ // If we upgrade, we assume all traffic is upgraded.
238
+ // If the user connects to an old client, the old client sends [Len][Data].
239
+ // We interpret [Data[0]] as Type. This is RISKY if old client sends binary starting with 0, 1, 2.
240
+ // But since old client sends JSON strings usually, '{' is 123.
241
+ // Strings start with chars.
242
+ // We can try to heuristic?
243
+ // Actually, if we control both ends (deployment), we enforce new protocol.
244
+ // Assuming this is a breaking change OR we are careful.
245
+ // New Protocol: Length = N (where N includes the type byte).
246
+ // So if payload is 10 bytes, N=11.
247
+ if (body.length > 0) {
248
+ const typeId = body[0];
249
+ const payload = body.slice(1);
250
+ // console.log(`[Connection] Packet received. Type: ${typeId}, Payload Len: ${payload.length}`);
251
+ if (typeId === 1) {
252
+ this.emitMedia('video', payload);
253
+ }
254
+ else if (typeId === 2) {
255
+ this.emitMedia('audio', payload);
256
+ }
257
+ else {
258
+ // Type 0 or unknown -> Treat as Data
259
+ this.processDataPayload(payload);
260
+ }
261
+ }
262
+ this.receiveBuffer = this.receiveBuffer.slice(4 + length);
263
+ }
264
+ }
265
+ processDataPayload(messageData) {
266
+ try {
267
+ const text = Connection.textDecoder.decode(messageData);
268
+ if (!this.isLikelyTextPayload(messageData, text)) {
269
+ this.emitMessage(messageData);
270
+ return;
271
+ }
272
+ const json = JSON.parse(text);
273
+ // Intercept Internal Signals
274
+ if (json && typeof json === 'object' && json.type === '#pluto-signal') {
275
+ this.handleInternalSignal(json);
276
+ return;
277
+ }
278
+ if (typeof json === 'object' && json !== null) {
279
+ this.emitMessage(json);
280
+ }
281
+ else {
282
+ this.emitMessage(text);
283
+ }
284
+ }
285
+ catch {
286
+ try {
287
+ const text = Connection.textDecoder.decode(messageData);
288
+ if (this.isLikelyTextPayload(messageData, text)) {
289
+ this.emitMessage(text);
290
+ return;
291
+ }
292
+ }
293
+ catch {
294
+ // fall through to raw binary payload
295
+ }
296
+ this.emitMessage(messageData);
297
+ }
298
+ }
299
+ isLikelyTextPayload(messageData, text) {
300
+ try {
301
+ for (let index = 0; index < text.length; index += 1) {
302
+ const code = text.charCodeAt(index);
303
+ if (code < 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) {
304
+ return false;
305
+ }
306
+ }
307
+ const roundTrip = Connection.textEncoder.encode(text);
308
+ if (roundTrip.byteLength !== messageData.byteLength) {
309
+ return false;
310
+ }
311
+ for (let index = 0; index < roundTrip.byteLength; index += 1) {
312
+ if (roundTrip[index] !== messageData[index]) {
313
+ return false;
314
+ }
315
+ }
316
+ return true;
317
+ }
318
+ catch {
319
+ return false;
320
+ }
321
+ }
322
+ emitMedia(type, data) {
323
+ this.mediaListeners.forEach(cb => cb(type, data));
324
+ }
325
+ emitMessage(msg) {
326
+ this.listeners.forEach(cb => cb(msg));
327
+ }
328
+ addTrack(track) {
329
+ const native = this.webrtcTransport?.getNativeTransport?.();
330
+ if (native && typeof native.addTrack === 'function') {
331
+ return native.addTrack(track);
332
+ }
333
+ return null;
334
+ }
335
+ addTransceiver(trackOrKind, init) {
336
+ const native = this.webrtcTransport?.getNativeTransport?.();
337
+ if (native && typeof native.addTransceiver === 'function') {
338
+ return native.addTransceiver(trackOrKind, init);
339
+ }
340
+ return null;
341
+ }
342
+ onTrack(callback) {
343
+ this.trackListeners.push(callback);
344
+ }
345
+ async attemptUpgrade() {
346
+ if (this.upgradeState !== 'none')
347
+ return;
348
+ this.upgradeState = 'upgrading';
349
+ try {
350
+ this.webrtcTransport = TransportFactory.createTransport('webrtc');
351
+ }
352
+ catch (error) {
353
+ this.upgradeState = 'failed';
354
+ console.warn('[Connection] WebRTC upgrade unavailable:', error);
355
+ return;
356
+ }
357
+ // @ts-ignore Set up internal signal sender if the transport is castable to WebRTCTransport
358
+ if (typeof this.webrtcTransport.setSignalSender === 'function') {
359
+ // @ts-ignore
360
+ this.webrtcTransport.setSignalSender((msg) => this.sendInternalSignal(msg));
361
+ }
362
+ this.webrtcTransport.onMessage((data) => {
363
+ this.processDataPayload(data);
364
+ });
365
+ this.webrtcTransport.onStateChange((state) => {
366
+ console.log(`[Connection] 🌐 Native PC State: ${state}`);
367
+ if (state === 'connected') {
368
+ this.upgradeState = 'upgraded';
369
+ console.log("%c[Connection] 🚀 UPGRADE COMPLETE: Switched to Native WebRTC", "color: green; font-weight: bold;");
370
+ }
371
+ else if (state === 'failed' || state === 'disconnected') {
372
+ this.upgradeState = 'failed';
373
+ console.warn("%c[Connection] ⚠️ Native Connection Failed. Falling back to Iroh Relay.", "color: orange; font-weight: bold;");
374
+ }
375
+ });
376
+ await this.webrtcTransport.init(this.localNodeId, this.deviceId, { rtcConfig: this.options.rtcConfig });
377
+ const native = this.webrtcTransport.getNativeTransport?.();
378
+ if (native && native.ontrack !== undefined) {
379
+ native.ontrack = (ev) => {
380
+ console.log(`[Connection] Peer -> Track: ${ev.track.kind}`);
381
+ this.trackListeners.forEach(cb => cb(ev));
382
+ };
383
+ }
384
+ }
385
+ async sendInternalSignal(msg) {
386
+ const envelope = {
387
+ type: '#pluto-signal',
388
+ content: msg
389
+ };
390
+ return this.send(envelope);
391
+ }
392
+ handleInternalSignal(msg) {
393
+ if (msg.content && msg.content.transport === 'webrtc' && this.webrtcTransport?.handleSignalingMessage) {
394
+ this.webrtcTransport.handleSignalingMessage(msg.content);
395
+ }
396
+ else if (msg.content && !msg.content.transport && this.webrtcTransport?.handleSignalingMessage) {
397
+ // Legacy WebRTC signaling compatibility where transport identifier was absent
398
+ msg.content.transport = 'webrtc';
399
+ this.webrtcTransport.handleSignalingMessage(msg.content);
400
+ }
401
+ }
402
+ }
403
+ Connection.textDecoder = new TextDecoder('utf-8', { fatal: true });
404
+ Connection.textEncoder = new TextEncoder();
@@ -0,0 +1,18 @@
1
+ import type { Device, SignalingSession } from './types';
2
+ export type AuthContext = {
3
+ userId: string;
4
+ token?: string | null;
5
+ refreshToken?: string | null;
6
+ customToken?: string | null;
7
+ tokenProvider?: (forceRefresh?: boolean) => Promise<string | null>;
8
+ expiresAtMs?: number | null;
9
+ };
10
+ export interface IEngineBridge {
11
+ setAuthContext(context: AuthContext | null): Promise<void> | void;
12
+ searchDevices(excludeNodeId?: string): Promise<Device[]>;
13
+ onDevicesChange(callback: (devices: Device[]) => void, excludeNodeId?: string): () => void;
14
+ subscribeSessions(localDeviceId: string, callback: (session: SignalingSession) => void): Promise<() => void>;
15
+ updatePresence(localNodeId: string, ticket: string, isOnline?: boolean, ttlMs?: number, metadata?: string): Promise<void>;
16
+ setOffline(localNodeId: string): Promise<void>;
17
+ forceReconnectSnapshot(): void;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { Device, SignalingSession } from './types';
2
+ export type AuthContext = {
3
+ userId: string;
4
+ token?: string | null;
5
+ };
6
+ export interface ISignalingTransport {
7
+ setAuthContext(context: AuthContext | null): Promise<void> | void;
8
+ searchDevices(excludeNodeId?: string): Promise<Device[]>;
9
+ onDevicesChange(callback: (devices: Device[]) => void, excludeNodeId?: string): () => void;
10
+ subscribeSessions(localDeviceId: string, callback: (session: SignalingSession) => void): Promise<() => void>;
11
+ updatePresence(localNodeId: string, ticket: string, isOnline?: boolean, ttlMs?: number, metadata?: string): Promise<void>;
12
+ setOffline(localNodeId: string): Promise<void>;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { Device, PeerHealth, PeerLifecycleStatus, PeerScope, PeerState } from './types';
2
+ export type PeerLifecycleUpsert = {
3
+ peerId?: string;
4
+ deviceId?: string;
5
+ deviceIdHint?: string;
6
+ nodeId?: string;
7
+ connectionId?: string;
8
+ ticket?: string;
9
+ online?: boolean;
10
+ deviceName?: string;
11
+ platformType?: string;
12
+ status?: PeerLifecycleStatus;
13
+ health?: PeerHealth;
14
+ transportState?: string;
15
+ protocolState?: string;
16
+ routable?: boolean;
17
+ scopes?: Iterable<PeerScope>;
18
+ error?: string;
19
+ lastSeenAt?: number;
20
+ };
21
+ export declare class PeerLifecycleManager {
22
+ private readonly peers;
23
+ private readonly aliases;
24
+ private readonly watchers;
25
+ private resolveCanonicalPeerId;
26
+ private setAlias;
27
+ private snapshot;
28
+ private emit;
29
+ private rekeyPeer;
30
+ upsert(input: PeerLifecycleUpsert): PeerState;
31
+ upsertDevice(device: Device): PeerState;
32
+ watch(callback: (peers: PeerState[]) => void): () => void;
33
+ get(id: string): PeerState | undefined;
34
+ list(): PeerState[];
35
+ listConnected(): PeerState[];
36
+ addScope(id: string, scope?: PeerScope): PeerState | undefined;
37
+ releaseScope(id: string, scope?: PeerScope): PeerState | undefined;
38
+ hasScope(id: string, scope: PeerScope): boolean;
39
+ hasScopes(id: string): boolean;
40
+ removeConnection(id: string): PeerState | undefined;
41
+ updateHealth(id: string, health: PeerHealth, status?: PeerLifecycleStatus): PeerState | undefined;
42
+ private resolveRecord;
43
+ }