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.
- package/README.md +106 -0
- package/dist/OpenRTC.d.ts +71 -0
- package/dist/OpenRTC.js +130 -0
- package/dist/api/MediaTransport.d.ts +7 -0
- package/dist/api/MediaTransport.js +262 -0
- package/dist/api/PlutoPeerConnection.d.ts +50 -0
- package/dist/api/PlutoPeerConnection.js +434 -0
- package/dist/api/PlutoWebSocket.d.ts +24 -0
- package/dist/api/PlutoWebSocket.js +113 -0
- package/dist/api/PlutoWebTransport.d.ts +29 -0
- package/dist/api/PlutoWebTransport.js +105 -0
- package/dist/auth/host.d.ts +73 -0
- package/dist/auth/host.js +513 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/internal.d.ts +1 -0
- package/dist/auth/internal.js +1 -0
- package/dist/compat/index.d.ts +20 -0
- package/dist/compat/index.js +20 -0
- package/dist/core/Client.d.ts +352 -0
- package/dist/core/Client.js +3689 -0
- package/dist/core/Connection.d.ts +67 -0
- package/dist/core/Connection.js +404 -0
- package/dist/core/IEngineBridge.d.ts +18 -0
- package/dist/core/IEngineBridge.js +1 -0
- package/dist/core/ISignalingTransport.d.ts +13 -0
- package/dist/core/ISignalingTransport.js +1 -0
- package/dist/core/PeerLifecycleManager.d.ts +43 -0
- package/dist/core/PeerLifecycleManager.js +330 -0
- package/dist/core/PeerProjectionStore.d.ts +43 -0
- package/dist/core/PeerProjectionStore.js +327 -0
- package/dist/core/PlutoRTC.d.ts +13 -0
- package/dist/core/PlutoRTC.js +26 -0
- package/dist/core/Room.d.ts +47 -0
- package/dist/core/Room.js +376 -0
- package/dist/core/Signaling.d.ts +39 -0
- package/dist/core/Signaling.js +147 -0
- package/dist/core/bridges/BridgeCodecs.d.ts +19 -0
- package/dist/core/bridges/BridgeCodecs.js +317 -0
- package/dist/core/bridges/BrowserFirestoreRooms.d.ts +36 -0
- package/dist/core/bridges/BrowserFirestoreRooms.js +169 -0
- package/dist/core/bridges/BrowserFirestoreSignaling.d.ts +111 -0
- package/dist/core/bridges/BrowserFirestoreSignaling.js +1010 -0
- package/dist/core/bridges/NativeIpcBridge.d.ts +156 -0
- package/dist/core/bridges/NativeIpcBridge.js +1472 -0
- package/dist/core/bridges/RtdbPresence.d.ts +122 -0
- package/dist/core/bridges/RtdbPresence.js +201 -0
- package/dist/core/bridges/TauriBridge.d.ts +8 -0
- package/dist/core/bridges/TauriBridge.js +7 -0
- package/dist/core/bridges/WasmBridge.d.ts +233 -0
- package/dist/core/bridges/WasmBridge.js +1811 -0
- package/dist/core/config.d.ts +31 -0
- package/dist/core/config.js +138 -0
- package/dist/core/transports/ITransport.d.ts +12 -0
- package/dist/core/transports/ITransport.js +1 -0
- package/dist/core/transports/LocalDaemonBridge.d.ts +41 -0
- package/dist/core/transports/LocalDaemonBridge.js +227 -0
- package/dist/core/transports/TauriSignaling.d.ts +28 -0
- package/dist/core/transports/TauriSignaling.js +373 -0
- package/dist/core/transports/TransportFactory.d.ts +9 -0
- package/dist/core/transports/TransportFactory.js +59 -0
- package/dist/core/transports/WasmSignaling.d.ts +78 -0
- package/dist/core/transports/WasmSignaling.js +522 -0
- package/dist/core/transports/WebRTCTransport.d.ts +23 -0
- package/dist/core/transports/WebRTCTransport.js +132 -0
- package/dist/core/trust.d.ts +23 -0
- package/dist/core/trust.js +99 -0
- package/dist/core/types.d.ts +559 -0
- package/dist/core/types.js +19 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/moq/MoQMediaTransport.d.ts +6 -0
- package/dist/moq/MoQMediaTransport.js +106 -0
- package/dist/moq/MoQPublisher.d.ts +22 -0
- package/dist/moq/MoQPublisher.js +179 -0
- package/dist/moq/MoQSession.d.ts +35 -0
- package/dist/moq/MoQSession.js +166 -0
- package/dist/moq/MoQSubscriber.d.ts +19 -0
- package/dist/moq/MoQSubscriber.js +235 -0
- package/dist/moq/common.d.ts +47 -0
- package/dist/moq/common.js +304 -0
- package/dist/moq/messages.d.ts +105 -0
- package/dist/moq/messages.js +367 -0
- package/dist/presets/plutoHosted.d.ts +13 -0
- package/dist/presets/plutoHosted.js +18 -0
- package/dist/runtime/DelegatingRuntimeAdapter.d.ts +68 -0
- package/dist/runtime/DelegatingRuntimeAdapter.js +250 -0
- package/dist/runtime/IpcRuntimeAdapter.d.ts +9 -0
- package/dist/runtime/IpcRuntimeAdapter.js +13 -0
- package/dist/runtime/RuntimeClient.d.ts +257 -0
- package/dist/runtime/RuntimeClient.js +1484 -0
- package/dist/runtime/TauriRuntimeAdapter.d.ts +1 -0
- package/dist/runtime/TauriRuntimeAdapter.js +1 -0
- package/dist/runtime/WasmRuntimeAdapter.d.ts +6 -0
- package/dist/runtime/WasmRuntimeAdapter.js +11 -0
- package/dist/runtime/factories.d.ts +24 -0
- package/dist/runtime/factories.js +66 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/ipc.d.ts +8 -0
- package/dist/runtime/ipc.js +65 -0
- package/dist/runtime/ipcContract.d.ts +46 -0
- package/dist/runtime/ipcContract.js +1 -0
- package/dist/runtime/tauri.d.ts +16 -0
- package/dist/runtime/tauri.js +22 -0
- package/dist/runtime/types.d.ts +110 -0
- package/dist/runtime/types.js +1 -0
- package/dist/transports/TauriTransport.d.ts +17 -0
- package/dist/transports/TauriTransport.js +49 -0
- package/dist/transports/WebTransport.d.ts +23 -0
- package/dist/transports/WebTransport.js +71 -0
- package/dist/utils/debug.d.ts +4 -0
- package/dist/utils/debug.js +33 -0
- package/dist/utils/runtime.d.ts +1 -0
- package/dist/utils/runtime.js +10 -0
- package/package.json +97 -0
- package/wasm/README.md +64 -0
- package/wasm/openrtc.d.ts +284 -0
- package/wasm/openrtc.js +2132 -0
- package/wasm/openrtc_bg.wasm +0 -0
- package/wasm/openrtc_bg.wasm.d.ts +110 -0
- 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
|
+
}
|