y-openrtc 0.1.1 → 1.0.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 +2 -1
- package/dist/provider.d.ts +3 -2
- package/dist/provider.js +52 -29
- package/dist/types.d.ts +6 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -69,6 +69,8 @@ Keep TURN credentials server-generated and short lived. Do not commit `.env` fil
|
|
|
69
69
|
|
|
70
70
|
- `signaling` and `peerOpts` are accepted for compatibility, but ignored.
|
|
71
71
|
- OpenRTC remains the source of truth for room membership and peer transport.
|
|
72
|
+
- Runtime connection events and room-membership changes drive peer attachment;
|
|
73
|
+
the provider does not poll or own base-connection retries.
|
|
72
74
|
- Same-tab sync still prefers `BroadcastChannel` when available.
|
|
73
75
|
- `password` encrypts provider payloads end-to-end on top of OpenRTC.
|
|
74
76
|
|
|
@@ -79,7 +81,6 @@ The live test at `tests/OpenrtcProvider.live.test.ts` reads package-local env va
|
|
|
79
81
|
Create `openrtc/packages/y-openrtc/.env.local` with:
|
|
80
82
|
|
|
81
83
|
```bash
|
|
82
|
-
OPENRTC_LIVE_TESTS=1
|
|
83
84
|
OPENRTC_Y_OPENRTC_API_KEY=<your public OpenRTC API key>
|
|
84
85
|
```
|
|
85
86
|
|
package/dist/provider.d.ts
CHANGED
|
@@ -34,7 +34,6 @@ export declare class OpenrtcProvider {
|
|
|
34
34
|
private localObserversAttached;
|
|
35
35
|
private connectPromise;
|
|
36
36
|
private reconcileTimer;
|
|
37
|
-
private runtimeRefreshTimer;
|
|
38
37
|
private readonly diagnostics;
|
|
39
38
|
constructor(roomName: string, doc: Y.Doc, options?: OpenrtcProviderOptions);
|
|
40
39
|
get connected(): boolean;
|
|
@@ -49,7 +48,9 @@ export declare class OpenrtcProvider {
|
|
|
49
48
|
private warnCompatibilityOptions;
|
|
50
49
|
private emitStatus;
|
|
51
50
|
private emitSynced;
|
|
51
|
+
private currentPeersPayload;
|
|
52
52
|
private emitPeers;
|
|
53
|
+
private replayEvent;
|
|
53
54
|
private attachLocalObservers;
|
|
54
55
|
private detachLocalObservers;
|
|
55
56
|
private connectInternal;
|
|
@@ -64,12 +65,12 @@ export declare class OpenrtcProvider {
|
|
|
64
65
|
private hasBcNodeId;
|
|
65
66
|
private isRelevantConnection;
|
|
66
67
|
private refreshKnownRuntimeConnections;
|
|
67
|
-
private startRuntimeConnectionRefresh;
|
|
68
68
|
private resyncPeer;
|
|
69
69
|
private sendInitialSync;
|
|
70
70
|
private attachConnection;
|
|
71
71
|
private sendToConnection;
|
|
72
72
|
private sendControlToConnection;
|
|
73
|
+
private sendControlBestEffort;
|
|
73
74
|
private sendBroadcastPayload;
|
|
74
75
|
private broadcastProviderPayload;
|
|
75
76
|
private broadcastInitialBcState;
|
package/dist/provider.js
CHANGED
|
@@ -68,6 +68,9 @@ function isRoomFullError(error) {
|
|
|
68
68
|
function isTransientStartupError(error) {
|
|
69
69
|
return /relay-only endpoint ticket unavailable|failed to fetch|network|timed out/i.test(errorText(error));
|
|
70
70
|
}
|
|
71
|
+
function isClosedConnectionSendError(error) {
|
|
72
|
+
return /connection is closed|closed connection|not open|writer is closed|application crypto is required but the connection is closed/i.test(errorText(error));
|
|
73
|
+
}
|
|
71
74
|
function sleep(ms) {
|
|
72
75
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
73
76
|
}
|
|
@@ -93,7 +96,6 @@ export class OpenrtcProvider {
|
|
|
93
96
|
this.localObserversAttached = false;
|
|
94
97
|
this.connectPromise = null;
|
|
95
98
|
this.reconcileTimer = null;
|
|
96
|
-
this.runtimeRefreshTimer = null;
|
|
97
99
|
this.diagnostics = {
|
|
98
100
|
openrtcSent: 0,
|
|
99
101
|
openrtcReceived: 0,
|
|
@@ -129,6 +131,9 @@ export class OpenrtcProvider {
|
|
|
129
131
|
}
|
|
130
132
|
const clients = added.concat(updated, removed);
|
|
131
133
|
void this.broadcastProviderPayload(encodeAwarenessUpdate(this.awareness, clients));
|
|
134
|
+
if (added.concat(removed).some((clientId) => clientId !== this.doc.clientID)) {
|
|
135
|
+
this.emitPeers([], []);
|
|
136
|
+
}
|
|
132
137
|
};
|
|
133
138
|
this.destroyHandler = () => {
|
|
134
139
|
void this.destroyInternal();
|
|
@@ -144,6 +149,7 @@ export class OpenrtcProvider {
|
|
|
144
149
|
const listeners = this.listeners.get(name) ?? new Set();
|
|
145
150
|
listeners.add(listener);
|
|
146
151
|
this.listeners.set(name, listeners);
|
|
152
|
+
this.replayEvent(name, listener);
|
|
147
153
|
return listener;
|
|
148
154
|
}
|
|
149
155
|
off(name, listener) {
|
|
@@ -154,7 +160,9 @@ export class OpenrtcProvider {
|
|
|
154
160
|
this.off(name, wrapped);
|
|
155
161
|
listener(payload);
|
|
156
162
|
});
|
|
157
|
-
this.
|
|
163
|
+
const listeners = this.listeners.get(name) ?? new Set();
|
|
164
|
+
listeners.add(wrapped);
|
|
165
|
+
this.listeners.set(name, listeners);
|
|
158
166
|
}
|
|
159
167
|
emit(name, payload) {
|
|
160
168
|
for (const listener of this.listeners.get(name) ?? []) {
|
|
@@ -206,16 +214,37 @@ export class OpenrtcProvider {
|
|
|
206
214
|
this.synced = nextSynced;
|
|
207
215
|
this.emit('synced', { synced: nextSynced });
|
|
208
216
|
}
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
currentPeersPayload(added = [], removed = []) {
|
|
218
|
+
return {
|
|
211
219
|
added,
|
|
212
220
|
removed,
|
|
213
221
|
openrtcPeers: Array.from(this.openrtcConnections.keys()),
|
|
214
222
|
webrtcPeers: Array.from(this.openrtcConnections.keys()),
|
|
215
223
|
bcPeers: Array.from(this.bcPeers.keys()),
|
|
224
|
+
awarenessPeers: Array.from(this.awareness.getStates().keys())
|
|
225
|
+
.filter((clientId) => clientId !== this.doc.clientID),
|
|
216
226
|
};
|
|
227
|
+
}
|
|
228
|
+
emitPeers(added, removed) {
|
|
229
|
+
const payload = this.currentPeersPayload(added, removed);
|
|
217
230
|
this.emit('peers', payload);
|
|
218
231
|
}
|
|
232
|
+
replayEvent(name, listener) {
|
|
233
|
+
queueMicrotask(() => {
|
|
234
|
+
if (this.destroyed || !this.listeners.get(name)?.has(listener)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (name === 'status') {
|
|
238
|
+
listener({ connected: this.connected });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (name === 'synced') {
|
|
242
|
+
listener({ synced: this.synced });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
listener(this.currentPeersPayload());
|
|
246
|
+
});
|
|
247
|
+
}
|
|
219
248
|
attachLocalObservers() {
|
|
220
249
|
if (this.localObserversAttached) {
|
|
221
250
|
return;
|
|
@@ -255,7 +284,6 @@ export class OpenrtcProvider {
|
|
|
255
284
|
return;
|
|
256
285
|
}
|
|
257
286
|
this.attachConnection(connection);
|
|
258
|
-
void this.reconcilePeers();
|
|
259
287
|
});
|
|
260
288
|
if (typeof runtime.onConnectionStateChange === 'function') {
|
|
261
289
|
const stopRuntimeConnectionStates = runtime.onConnectionStateChange((state) => {
|
|
@@ -323,7 +351,6 @@ export class OpenrtcProvider {
|
|
|
323
351
|
this.requestReconcile();
|
|
324
352
|
});
|
|
325
353
|
this.refreshKnownRuntimeConnections();
|
|
326
|
-
this.startRuntimeConnectionRefresh();
|
|
327
354
|
this.broadcastInitialBcState();
|
|
328
355
|
this.requestReconcile();
|
|
329
356
|
this.updateSyncedState();
|
|
@@ -481,7 +508,6 @@ export class OpenrtcProvider {
|
|
|
481
508
|
}
|
|
482
509
|
catch (error) {
|
|
483
510
|
console.warn(`[y-openrtc] failed to connect to room peer ${member.nodeId}:`, error);
|
|
484
|
-
this.requestReconcile();
|
|
485
511
|
}
|
|
486
512
|
finally {
|
|
487
513
|
this.pendingNodeIds.delete(member.nodeId);
|
|
@@ -513,31 +539,22 @@ export class OpenrtcProvider {
|
|
|
513
539
|
}
|
|
514
540
|
}
|
|
515
541
|
}
|
|
516
|
-
startRuntimeConnectionRefresh() {
|
|
517
|
-
if (this.runtimeRefreshTimer) {
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
this.runtimeRefreshTimer = setInterval(() => {
|
|
521
|
-
this.refreshKnownRuntimeConnections();
|
|
522
|
-
this.requestReconcile();
|
|
523
|
-
}, 1000);
|
|
524
|
-
}
|
|
525
542
|
resyncPeer(remoteNodeId) {
|
|
526
543
|
const record = this.openrtcConnections.get(remoteNodeId);
|
|
527
544
|
if (!record) {
|
|
528
545
|
return;
|
|
529
546
|
}
|
|
530
547
|
record.synced = false;
|
|
531
|
-
|
|
532
|
-
|
|
548
|
+
this.sendControlBestEffort(record.connection, encodeSyncStep1(this.doc));
|
|
549
|
+
this.sendControlBestEffort(record.connection, encodeAwarenessQuery());
|
|
533
550
|
this.updateSyncedState();
|
|
534
551
|
}
|
|
535
552
|
sendInitialSync(connection, record) {
|
|
536
|
-
|
|
537
|
-
|
|
553
|
+
this.sendControlBestEffort(connection, encodeSyncStep1(this.doc));
|
|
554
|
+
this.sendControlBestEffort(connection, encodeAwarenessQuery());
|
|
538
555
|
const awarenessStates = Array.from(this.awareness.getStates().keys());
|
|
539
556
|
if (awarenessStates.length > 0) {
|
|
540
|
-
|
|
557
|
+
this.sendControlBestEffort(connection, encodeAwarenessUpdate(this.awareness, awarenessStates));
|
|
541
558
|
}
|
|
542
559
|
for (const delayMs of [250, 1000, 3000]) {
|
|
543
560
|
setTimeout(() => {
|
|
@@ -548,8 +565,8 @@ export class OpenrtcProvider {
|
|
|
548
565
|
|| this.openrtcConnections.get(connection.remoteNodeId)?.connection.id !== connection.id) {
|
|
549
566
|
return;
|
|
550
567
|
}
|
|
551
|
-
|
|
552
|
-
|
|
568
|
+
this.sendControlBestEffort(connection, encodeSyncStep1(this.doc));
|
|
569
|
+
this.sendControlBestEffort(connection, encodeAwarenessQuery());
|
|
553
570
|
}, delayMs);
|
|
554
571
|
}
|
|
555
572
|
}
|
|
@@ -577,7 +594,11 @@ export class OpenrtcProvider {
|
|
|
577
594
|
return;
|
|
578
595
|
}
|
|
579
596
|
this.diagnostics.openrtcReceived += 1;
|
|
580
|
-
void this.handleIncomingPayload(normalizeBinaryPayload(message), { type: 'openrtc', nodeId, connectionId: connection.id }, (reply) => this.sendControlToConnection(connection, reply), record)
|
|
597
|
+
void this.handleIncomingPayload(normalizeBinaryPayload(message), { type: 'openrtc', nodeId, connectionId: connection.id }, (reply) => this.sendControlToConnection(connection, reply), record).catch((error) => {
|
|
598
|
+
if (!isClosedConnectionSendError(error)) {
|
|
599
|
+
console.warn('[y-openrtc] incoming payload handler failed', error);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
581
602
|
});
|
|
582
603
|
connection.onDisconnect(() => {
|
|
583
604
|
if (!this.openrtcConnections.delete(nodeId)) {
|
|
@@ -585,7 +606,6 @@ export class OpenrtcProvider {
|
|
|
585
606
|
}
|
|
586
607
|
this.emitPeers([], [nodeId]);
|
|
587
608
|
this.updateSyncedState();
|
|
588
|
-
this.requestReconcile();
|
|
589
609
|
});
|
|
590
610
|
this.sendInitialSync(connection, record);
|
|
591
611
|
}
|
|
@@ -615,6 +635,13 @@ export class OpenrtcProvider {
|
|
|
615
635
|
}
|
|
616
636
|
await connection.send(encrypted);
|
|
617
637
|
}
|
|
638
|
+
sendControlBestEffort(connection, payload) {
|
|
639
|
+
void this.sendControlToConnection(connection, payload).catch((error) => {
|
|
640
|
+
if (!isClosedConnectionSendError(error)) {
|
|
641
|
+
console.warn('[y-openrtc] control send failed', error);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
618
645
|
async sendBroadcastPayload(payload) {
|
|
619
646
|
const encrypted = await encryptFrame(payload, await this.keyPromise);
|
|
620
647
|
this.diagnostics.broadcastSent += 1;
|
|
@@ -704,10 +731,6 @@ export class OpenrtcProvider {
|
|
|
704
731
|
clearTimeout(this.reconcileTimer);
|
|
705
732
|
this.reconcileTimer = null;
|
|
706
733
|
}
|
|
707
|
-
if (this.runtimeRefreshTimer) {
|
|
708
|
-
clearInterval(this.runtimeRefreshTimer);
|
|
709
|
-
this.runtimeRefreshTimer = null;
|
|
710
|
-
}
|
|
711
734
|
this.broadcastTransport?.disconnect();
|
|
712
735
|
this.broadcastTransport = null;
|
|
713
736
|
this.roomMembers.clear();
|
package/dist/types.d.ts
CHANGED
|
@@ -52,6 +52,12 @@ export interface PeersEventPayload {
|
|
|
52
52
|
openrtcPeers: string[];
|
|
53
53
|
webrtcPeers: string[];
|
|
54
54
|
bcPeers: string[];
|
|
55
|
+
/**
|
|
56
|
+
* Remote Yjs awareness client ids currently visible to this provider.
|
|
57
|
+
* This is the best source for participant counts in collaborative UIs,
|
|
58
|
+
* because OpenRTC may use a mesh subset or BroadcastChannel for transport.
|
|
59
|
+
*/
|
|
60
|
+
awarenessPeers: number[];
|
|
55
61
|
}
|
|
56
62
|
export interface OpenrtcProviderEvents {
|
|
57
63
|
status: StatusEventPayload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "y-openrtc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -34,14 +34,16 @@
|
|
|
34
34
|
"./package.json": "./package.json"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"build": "tsc",
|
|
37
|
+
"build": "rm -rf dist && tsc",
|
|
38
|
+
"prepublishOnly": "pnpm run build",
|
|
38
39
|
"test": "vitest run tests",
|
|
40
|
+
"test:deterministic": "vitest run tests/TetrisSync.test.ts tests/TetrisPeerDiagnostic.test.ts tests/OpenrtcProvider.test.ts tests/PublicPackageGuard.test.ts tests/crypto.test.ts",
|
|
39
41
|
"test:watch": "vitest",
|
|
40
|
-
"test:live": "
|
|
42
|
+
"test:live": "vitest run tests/OpenrtcProvider.live.test.ts"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"lib0": "^0.2.114",
|
|
44
|
-
"openrtc": "
|
|
46
|
+
"openrtc": "^1.0.0",
|
|
45
47
|
"y-protocols": "^1.0.6"
|
|
46
48
|
},
|
|
47
49
|
"peerDependencies": {
|