openrtc-netcode 1.0.0 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/dist/client.d.ts +2 -1
- package/dist/client.js +28 -43
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/latestState.d.ts +9 -0
- package/dist/latestState.js +44 -0
- package/dist/roomRuntime.d.ts +7 -0
- package/dist/roomRuntime.js +88 -0
- package/dist/types.d.ts +34 -19
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,23 +6,31 @@ and exposes reliable typed messages, lightweight replicated state, networked
|
|
|
6
6
|
objects, and browser voice-chat signaling.
|
|
7
7
|
|
|
8
8
|
Gameplay payloads are sent over OpenRTC peer data paths only: Iroh
|
|
9
|
-
relay/direct, WebRTC DataChannel upgrades, and MoQ routes when enabled.
|
|
10
|
-
and
|
|
11
|
-
|
|
12
|
-
Firestore as a state or message relay.
|
|
9
|
+
relay/direct, WebRTC DataChannel upgrades, and MoQ routes when enabled. Live
|
|
10
|
+
membership and signaling use the OpenRTC coordination gateway; neither core
|
|
11
|
+
netcode nor its payload path uses Firebase, Firestore, or RTDB.
|
|
13
12
|
|
|
14
13
|
`preferredPayloadTransports` filters OpenRTC's typed application-readiness
|
|
15
|
-
query; it does not request promotion.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
route events and never requests or suppresses upgrades.
|
|
14
|
+
query; it does not request promotion. OpenRTC owns optional transport promotion
|
|
15
|
+
and recovery, while netcode only observes route events. Netcode consumes one
|
|
16
|
+
already-activated room handle, so it never retains an unused user or space
|
|
17
|
+
avenue.
|
|
20
18
|
|
|
21
19
|
```ts
|
|
20
|
+
import { OpenRTC } from 'openrtc';
|
|
22
21
|
import { createNetcode } from 'openrtc-netcode';
|
|
23
22
|
|
|
24
|
-
const
|
|
25
|
-
|
|
23
|
+
const rtc = OpenRTC({
|
|
24
|
+
apiKey: 'pk_live_...',
|
|
25
|
+
transports: { webrtc: true },
|
|
26
|
+
});
|
|
27
|
+
const room = await rtc.rooms.join('duel-room', {
|
|
28
|
+
access: 'capability',
|
|
29
|
+
membership: 'ephemeral',
|
|
30
|
+
maxPeers: 4,
|
|
31
|
+
});
|
|
32
|
+
const netcode = createNetcode({ room });
|
|
33
|
+
await netcode.joinLobby('duel-room');
|
|
26
34
|
|
|
27
35
|
netcode.onMessage('chat', (message) => {
|
|
28
36
|
console.log(message.from, message.payload);
|
package/dist/client.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type { CreateLobbyOptions, JoinLobbyOptions, NetcodeClient as NetcodeClie
|
|
|
5
5
|
export declare class OpenRtcNetcodeClient implements NetcodeClientInterface {
|
|
6
6
|
private readonly options;
|
|
7
7
|
private runtime;
|
|
8
|
-
private ownsRuntime;
|
|
9
8
|
private localPeerId;
|
|
10
9
|
private currentLobby;
|
|
11
10
|
private currentRoomId;
|
|
@@ -32,6 +31,7 @@ export declare class OpenRtcNetcodeClient implements NetcodeClientInterface {
|
|
|
32
31
|
private readonly introPeerIds;
|
|
33
32
|
private readonly seenEnvelopeIds;
|
|
34
33
|
private readonly seenEnvelopeOrder;
|
|
34
|
+
private readonly latestStateQueues;
|
|
35
35
|
private readonly stateStore;
|
|
36
36
|
private readonly objectStore;
|
|
37
37
|
private readonly voiceController;
|
|
@@ -83,6 +83,7 @@ export declare class OpenRtcNetcodeClient implements NetcodeClientInterface {
|
|
|
83
83
|
private refreshPeerPayloadState;
|
|
84
84
|
private stopPeerPingLoop;
|
|
85
85
|
private broadcastEnvelope;
|
|
86
|
+
private broadcastLatestState;
|
|
86
87
|
private sendEnvelope;
|
|
87
88
|
private sendDirectEnvelope;
|
|
88
89
|
private warnDirectSendFailure;
|
package/dist/client.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { OpenRTC } from 'openrtc';
|
|
2
1
|
import { TypedEmitter } from './events.js';
|
|
3
2
|
import { decodeEnvelope, encodeEnvelope, isRecord, NETCODE_PROTOCOL, NETCODE_PROTOCOL_VERSION, } from './protocol.js';
|
|
4
3
|
import { isNetcodeObjectState, ReplicatedObjectStore } from './objects.js';
|
|
5
4
|
import { ReplicatedState } from './state.js';
|
|
6
5
|
import { NetcodeVoiceController } from './voice.js';
|
|
6
|
+
import { runtimeFromRoom } from './roomRuntime.js';
|
|
7
|
+
import { LatestStateQueue } from './latestState.js';
|
|
7
8
|
const DEFAULT_MAX_PEERS = 8;
|
|
8
9
|
const NETCODE_CHANNEL_ID = 'openrtc-netcode/v1';
|
|
9
10
|
const PING_INTERVAL_MS = 2000;
|
|
@@ -16,7 +17,6 @@ export class OpenRtcNetcodeClient {
|
|
|
16
17
|
constructor(options = {}) {
|
|
17
18
|
this.options = options;
|
|
18
19
|
this.runtime = null;
|
|
19
|
-
this.ownsRuntime = false;
|
|
20
20
|
this.localPeerId = null;
|
|
21
21
|
this.currentLobby = null;
|
|
22
22
|
this.currentRoomId = null;
|
|
@@ -43,8 +43,9 @@ export class OpenRtcNetcodeClient {
|
|
|
43
43
|
this.introPeerIds = new Set();
|
|
44
44
|
this.seenEnvelopeIds = new Set();
|
|
45
45
|
this.seenEnvelopeOrder = [];
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
46
|
+
this.latestStateQueues = new Map();
|
|
47
|
+
this.stateStore = new ReplicatedState((options) => this.resolveStateOwner(options), (key, value, owner) => this.broadcastLatestState(`state:${key}`, 'state.patch', { key, value, owner }));
|
|
48
|
+
this.objectStore = new ReplicatedObjectStore(() => this.localPeerId, (object) => this.broadcastLatestState(`object:${object.id}`, 'object.upsert', { object }), (id) => this.broadcastEnvelope('object.remove', { id }));
|
|
48
49
|
this.voiceController = new NetcodeVoiceController(() => this.localPeerId, () => this.peers, shouldInitiateConnection, (peerId, signal) => this.sendEnvelope(peerId, 'voice.signal', { signal }));
|
|
49
50
|
}
|
|
50
51
|
get peerId() {
|
|
@@ -74,23 +75,10 @@ export class OpenRtcNetcodeClient {
|
|
|
74
75
|
}
|
|
75
76
|
this.started = true;
|
|
76
77
|
this.stopped = false;
|
|
77
|
-
this.runtime
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
discoveryMode: this.options.discoveryMode,
|
|
82
|
-
allowAnonymousHostedDefaults: this.options.allowAnonymousHostedDefaults,
|
|
83
|
-
space: this.options.space,
|
|
84
|
-
spaceKey: this.options.spaceKey,
|
|
85
|
-
spaceTokenProvider: this.options.spaceTokenProvider,
|
|
86
|
-
storagePrefix: this.options.storagePrefix,
|
|
87
|
-
nodeIdPersistence: this.options.nodeIdPersistence,
|
|
88
|
-
transports: this.options.transports,
|
|
89
|
-
transportPriority: this.options.transportPriority,
|
|
90
|
-
strictMode: this.options.strictMode,
|
|
91
|
-
turnCredentialsProvider: this.options.turnCredentialsProvider,
|
|
92
|
-
});
|
|
93
|
-
this.ownsRuntime = !this.options.runtime;
|
|
78
|
+
if (!this.options.runtime && !this.options.room) {
|
|
79
|
+
throw new Error('openrtc-netcode requires an activated OpenRTC 2.0 room adapter or an explicit openrtc/runtime client.');
|
|
80
|
+
}
|
|
81
|
+
this.runtime = this.options.runtime ?? runtimeFromRoom(this.options.room);
|
|
94
82
|
await callOptional(this.runtime, 'initialize');
|
|
95
83
|
await callOptional(this.runtime, 'start');
|
|
96
84
|
this.localPeerId = await this.runtime.getNodeId();
|
|
@@ -123,15 +111,13 @@ export class OpenRtcNetcodeClient {
|
|
|
123
111
|
this.peerStatsById.clear();
|
|
124
112
|
this.seenEnvelopeIds.clear();
|
|
125
113
|
this.seenEnvelopeOrder.length = 0;
|
|
114
|
+
this.latestStateQueues.clear();
|
|
126
115
|
this.stopPeerPingLoop();
|
|
127
116
|
this.peersById.clear();
|
|
128
117
|
this.objectStore.clear();
|
|
129
118
|
await this.voiceController.stop();
|
|
130
119
|
this.emitter.clear();
|
|
131
120
|
this.messageListeners.clear();
|
|
132
|
-
if (this.ownsRuntime) {
|
|
133
|
-
await callOptional(this.runtime, 'stop');
|
|
134
|
-
}
|
|
135
121
|
this.runtime = null;
|
|
136
122
|
this.started = false;
|
|
137
123
|
}
|
|
@@ -237,6 +223,7 @@ export class OpenRtcNetcodeClient {
|
|
|
237
223
|
this.peerStatsById.clear();
|
|
238
224
|
this.seenEnvelopeIds.clear();
|
|
239
225
|
this.seenEnvelopeOrder.length = 0;
|
|
226
|
+
this.latestStateQueues.clear();
|
|
240
227
|
this.objectStore.clear();
|
|
241
228
|
await this.voiceController.stop();
|
|
242
229
|
if (runtime && roomId) {
|
|
@@ -326,6 +313,14 @@ export class OpenRtcNetcodeClient {
|
|
|
326
313
|
if (!this.runtime || !this.localPeerId) {
|
|
327
314
|
return;
|
|
328
315
|
}
|
|
316
|
+
// An OpenRTC 2.0 room capability is the sole owner of its peer
|
|
317
|
+
// lifecycle. Calling connectByTicket here races that owner and can
|
|
318
|
+
// restart reciprocal application-key agreement on an already-admitted
|
|
319
|
+
// physical connection. Explicit legacy/test runtimes retain the
|
|
320
|
+
// deterministic mesh dial below.
|
|
321
|
+
if (this.runtime.managedConnectionLifecycle) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
329
324
|
const maxPeers = this.options.maxPeers ?? DEFAULT_MAX_PEERS;
|
|
330
325
|
for (const peer of this.peersById.values()) {
|
|
331
326
|
if (this.connectionsByPeer.size >= maxPeers) {
|
|
@@ -684,6 +679,14 @@ export class OpenRtcNetcodeClient {
|
|
|
684
679
|
const sends = Array.from(this.connectionsByPeer.keys()).map((peerId) => this.sendDirectEnvelope(peerId, envelope));
|
|
685
680
|
await Promise.all(sends);
|
|
686
681
|
}
|
|
682
|
+
broadcastLatestState(key, kind, body) {
|
|
683
|
+
let queue = this.latestStateQueues.get(key);
|
|
684
|
+
if (!queue) {
|
|
685
|
+
queue = new LatestStateQueue((state) => this.broadcastEnvelope(state.kind, state.body));
|
|
686
|
+
this.latestStateQueues.set(key, queue);
|
|
687
|
+
}
|
|
688
|
+
return queue.push({ kind, body });
|
|
689
|
+
}
|
|
687
690
|
async sendEnvelope(peerId, kind, body) {
|
|
688
691
|
const envelope = this.createEnvelope(kind, body, peerId);
|
|
689
692
|
if (this.fallbackChannel) {
|
|
@@ -969,10 +972,7 @@ export class OpenRtcNetcodeClient {
|
|
|
969
972
|
return this.connectionsByPeer.get(peerId) ?? null;
|
|
970
973
|
}
|
|
971
974
|
payloadReadinessOptions() {
|
|
972
|
-
const preferredTransports = this.options.preferredPayloadTransports
|
|
973
|
-
?? (this.ownsRuntime
|
|
974
|
-
? derivePreferredPayloadTransports(this.options.transports, this.options.transportPriority)
|
|
975
|
-
: []);
|
|
975
|
+
const preferredTransports = this.options.preferredPayloadTransports ?? [];
|
|
976
976
|
if (preferredTransports.length === 0) {
|
|
977
977
|
return {};
|
|
978
978
|
}
|
|
@@ -1127,21 +1127,6 @@ function isRoomAdmissionTransientError(error) {
|
|
|
1127
1127
|
return isRoomNotFoundError(error)
|
|
1128
1128
|
|| /permission[_ -]?denied|permission denied|forbidden|code["']?\s*:\s*403|status["']?\s*:\s*["']?permission_denied/i.test(text);
|
|
1129
1129
|
}
|
|
1130
|
-
function derivePreferredPayloadTransports(transports, transportPriority) {
|
|
1131
|
-
const configuredPriority = (transportPriority ?? [])
|
|
1132
|
-
.filter((transport) => UPGRADED_PAYLOAD_TRANSPORTS.has(transport));
|
|
1133
|
-
if (configuredPriority.length > 0) {
|
|
1134
|
-
return [...new Set(configuredPriority)];
|
|
1135
|
-
}
|
|
1136
|
-
const preferred = [];
|
|
1137
|
-
if (transports?.webrtc) {
|
|
1138
|
-
preferred.push('webrtc-lan', 'webrtc');
|
|
1139
|
-
}
|
|
1140
|
-
if (transports?.moq) {
|
|
1141
|
-
preferred.push('moq');
|
|
1142
|
-
}
|
|
1143
|
-
return preferred;
|
|
1144
|
-
}
|
|
1145
1130
|
function delay(ms) {
|
|
1146
1131
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1147
1132
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { OpenRtcNetcodeClient, createNetcode, shouldInitiateConnection, } from './client.js';
|
|
2
|
+
export { runtimeFromRoom } from './roomRuntime.js';
|
|
2
3
|
export { ReplicatedObjectStore } from './objects.js';
|
|
3
4
|
export { NETCODE_PROTOCOL, NETCODE_PROTOCOL_VERSION, decodeEnvelope, encodeEnvelope, isEnvelopeKind, } from './protocol.js';
|
|
4
5
|
export type { HelloBody, MetadataBody, NetcodeEnvelope, NetcodeEnvelopeKind, ObjectRemoveBody, ObjectSnapshotBody, ObjectUpsertBody, StatePatchBody, StateSnapshotBody, UserMessageBody, VoiceSignalBody, } from './protocol.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { OpenRtcNetcodeClient, createNetcode, shouldInitiateConnection, } from './client.js';
|
|
2
|
+
export { runtimeFromRoom } from './roomRuntime.js';
|
|
2
3
|
export { ReplicatedObjectStore } from './objects.js';
|
|
3
4
|
export { NETCODE_PROTOCOL, NETCODE_PROTOCOL_VERSION, decodeEnvelope, encodeEnvelope, isEnvelopeKind, } from './protocol.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Coalesces only queued values; the in-flight state is allowed to finish. */
|
|
2
|
+
export declare class LatestStateQueue<T> {
|
|
3
|
+
private readonly transmit;
|
|
4
|
+
private pending;
|
|
5
|
+
private running;
|
|
6
|
+
constructor(transmit: (value: T) => Promise<void>);
|
|
7
|
+
push(value: T): Promise<void>;
|
|
8
|
+
private pump;
|
|
9
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Coalesces only queued values; the in-flight state is allowed to finish. */
|
|
2
|
+
export class LatestStateQueue {
|
|
3
|
+
constructor(transmit) {
|
|
4
|
+
this.transmit = transmit;
|
|
5
|
+
this.pending = null;
|
|
6
|
+
this.running = false;
|
|
7
|
+
}
|
|
8
|
+
push(value) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
if (this.pending) {
|
|
11
|
+
this.pending.value = value;
|
|
12
|
+
this.pending.waiters.push({ resolve, reject });
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
this.pending = { value, waiters: [{ resolve, reject }] };
|
|
16
|
+
}
|
|
17
|
+
this.pump();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
pump() {
|
|
21
|
+
if (this.running)
|
|
22
|
+
return;
|
|
23
|
+
this.running = true;
|
|
24
|
+
void (async () => {
|
|
25
|
+
while (this.pending) {
|
|
26
|
+
const current = this.pending;
|
|
27
|
+
this.pending = null;
|
|
28
|
+
try {
|
|
29
|
+
await this.transmit(current.value);
|
|
30
|
+
for (const waiter of current.waiters)
|
|
31
|
+
waiter.resolve();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
for (const waiter of current.waiters)
|
|
35
|
+
waiter.reject(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})().finally(() => {
|
|
39
|
+
this.running = false;
|
|
40
|
+
if (this.pending)
|
|
41
|
+
this.pump();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OpenRTCRoomHandle } from 'openrtc';
|
|
2
|
+
import type { NetcodeRuntime } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Adapts one OpenRTC 2.0 capability handle to netcode's transport-only runtime
|
|
5
|
+
* contract. It never creates a second room, user scope, or base space.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runtimeFromRoom(room: OpenRTCRoomHandle): NetcodeRuntime;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
function normalized(value) {
|
|
2
|
+
return value.trim().toUpperCase();
|
|
3
|
+
}
|
|
4
|
+
function assertRoom(room, requested) {
|
|
5
|
+
if (normalized(room.id) !== normalized(requested)) {
|
|
6
|
+
throw new Error(`Activated OpenRTC room '${room.id}' cannot join '${requested}'.`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function peerMember(peer) {
|
|
10
|
+
const peerId = String(peer.nodeId ?? peer.id ?? peer.deviceId ?? '');
|
|
11
|
+
return {
|
|
12
|
+
nodeId: peerId,
|
|
13
|
+
userId: typeof peer.userId === 'string' ? peer.userId : undefined,
|
|
14
|
+
ticket: typeof peer.ticket === 'string' ? peer.ticket : undefined,
|
|
15
|
+
joinedAt: typeof peer.connectedAt === 'number' ? peer.connectedAt : Date.now(),
|
|
16
|
+
lastSeenAt: Date.now(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Adapts one OpenRTC 2.0 capability handle to netcode's transport-only runtime
|
|
21
|
+
* contract. It never creates a second room, user scope, or base space.
|
|
22
|
+
*/
|
|
23
|
+
export function runtimeFromRoom(room) {
|
|
24
|
+
const connections = () => room.diagnostics.connections();
|
|
25
|
+
return {
|
|
26
|
+
managedConnectionLifecycle: true,
|
|
27
|
+
channels: room.channels,
|
|
28
|
+
initialize: async () => undefined,
|
|
29
|
+
start: async () => undefined,
|
|
30
|
+
stop: async () => undefined,
|
|
31
|
+
getNodeId: async () => {
|
|
32
|
+
const status = await room.diagnostics.status();
|
|
33
|
+
const nodeId = typeof status.localNodeId === 'string' ? status.localNodeId.trim() : '';
|
|
34
|
+
if (!nodeId)
|
|
35
|
+
throw new Error('The activated OpenRTC room has no local peer identity yet.');
|
|
36
|
+
return nodeId;
|
|
37
|
+
},
|
|
38
|
+
createRoom: async (roomId = room.id) => {
|
|
39
|
+
assertRoom(room, roomId);
|
|
40
|
+
return normalized(room.id);
|
|
41
|
+
},
|
|
42
|
+
joinRoom: async (roomId) => {
|
|
43
|
+
assertRoom(room, roomId);
|
|
44
|
+
return connections();
|
|
45
|
+
},
|
|
46
|
+
leaveRoom: async (roomId) => {
|
|
47
|
+
assertRoom(room, roomId);
|
|
48
|
+
await room.leave();
|
|
49
|
+
},
|
|
50
|
+
watchRoom: (roomId, callback) => {
|
|
51
|
+
assertRoom(room, roomId);
|
|
52
|
+
return room.peers.watch((peers) => callback(peers.map((peer) => peerMember(peer))));
|
|
53
|
+
},
|
|
54
|
+
getRoomMembers: async (roomId) => {
|
|
55
|
+
assertRoom(room, roomId);
|
|
56
|
+
return room.peers.listConnected()
|
|
57
|
+
.map((peer) => peerMember(peer));
|
|
58
|
+
},
|
|
59
|
+
onConnection: (callback) => {
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const publish = (connection) => {
|
|
62
|
+
const id = String(connection.id ?? connection.remoteNodeId ?? '');
|
|
63
|
+
if (!id || seen.has(id))
|
|
64
|
+
return;
|
|
65
|
+
seen.add(id);
|
|
66
|
+
callback(connection);
|
|
67
|
+
};
|
|
68
|
+
connections().forEach(publish);
|
|
69
|
+
return room.diagnostics.onConnection((connection) => {
|
|
70
|
+
publish(connection);
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
getConnections: connections,
|
|
74
|
+
connectByTicket: ({ ticket, timeoutMs }) => room.peers.connect({ ticket, timeoutMs }),
|
|
75
|
+
// The room capability already authorized the peer route. A protocol
|
|
76
|
+
// channel is not another admission avenue and must not replace that
|
|
77
|
+
// lifecycle scope with the channel identifier (which would trigger a
|
|
78
|
+
// second scoped dial on every send while the new scope is absent).
|
|
79
|
+
connectScopedChannel: (options) => room.channels.connectScoped({
|
|
80
|
+
...options,
|
|
81
|
+
scope: `v2:room:${room.id}`,
|
|
82
|
+
existingRouteOnly: true,
|
|
83
|
+
}),
|
|
84
|
+
onIncomingChannelStream: (channelId, callback) => room.channels.onIncomingChannel(channelId, callback),
|
|
85
|
+
getApplicationReadyConnections: () => connections().filter((connection) => typeof connection.isReadyForApplicationPayload !== 'function'
|
|
86
|
+
|| connection.isReadyForApplicationPayload()),
|
|
87
|
+
};
|
|
88
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { RuntimeApplicationPayloadReadinessOptions as OpenRtcApplicationPayloadReadinessOptions, RuntimeIncomingStream, RuntimeScopedChannelOptions, RuntimeScopedLogicalChannel } from 'openrtc/runtime';
|
|
2
|
+
import type { OpenRTCRoomHandle } from 'openrtc';
|
|
3
3
|
import type { MetadataBody, NetcodeEnvelope, ObjectRemoveBody, ObjectSnapshotBody, ObjectUpsertBody, StatePatchBody, StateSnapshotBody, UserMessageBody, VoiceSignalBody } from './protocol.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export interface NetcodeRuntime {
|
|
5
|
+
/**
|
|
6
|
+
* The activated capability owns discovery and physical peer lifecycle.
|
|
7
|
+
* Protocol layers must observe its connections instead of issuing a
|
|
8
|
+
* second ticket dial for the same avenue.
|
|
9
|
+
*/
|
|
10
|
+
managedConnectionLifecycle?: boolean;
|
|
11
|
+
initialize?(): Promise<void> | void;
|
|
12
|
+
start?(): Promise<void> | void;
|
|
13
|
+
stop?(): Promise<void> | void;
|
|
14
|
+
getNodeId(): Promise<string>;
|
|
15
|
+
createRoom(roomId?: string): Promise<string>;
|
|
16
|
+
joinRoom(roomId: string, options?: {
|
|
17
|
+
bootstrapPeers?: boolean;
|
|
18
|
+
}): Promise<RuntimeConnectionLike[]>;
|
|
19
|
+
leaveRoom(roomId: string): Promise<void>;
|
|
20
|
+
watchRoom(roomId: string, callback: (members: RuntimeMemberLike[]) => void): () => void;
|
|
21
|
+
getRoomMembers(roomId: string): Promise<RuntimeMemberLike[]>;
|
|
22
|
+
onConnection(callback: (connection: RuntimeConnectionLike) => void): () => void;
|
|
23
|
+
getConnections(): RuntimeConnectionLike[];
|
|
24
|
+
connectByTicket(target: {
|
|
25
|
+
ticket: string;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<RuntimeConnectionLike>;
|
|
28
|
+
connectScopedChannel(options: RuntimeScopedChannelOptions): Promise<RuntimeScopedLogicalChannel>;
|
|
29
|
+
onIncomingChannelStream(channelId: string, callback: (stream: RuntimeIncomingStream) => boolean | void): () => void;
|
|
30
|
+
getApplicationReadyConnections(options?: RuntimeApplicationPayloadReadinessOptions): RuntimeConnectionLike[];
|
|
31
|
+
channels?: OpenRTCRoomHandle['channels'];
|
|
32
|
+
}
|
|
6
33
|
export type NetcodeReliability = 'reliable';
|
|
7
34
|
export interface NetcodeOptions {
|
|
8
35
|
runtime?: NetcodeRuntime;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
authMode?: OpenrtcClientOptions['authMode'];
|
|
12
|
-
discoveryMode?: OpenrtcClientOptions['discoveryMode'];
|
|
13
|
-
allowAnonymousHostedDefaults?: OpenrtcClientOptions['allowAnonymousHostedDefaults'];
|
|
14
|
-
space?: OpenrtcClientOptions['space'];
|
|
15
|
-
spaceKey?: OpenrtcClientOptions['spaceKey'];
|
|
16
|
-
spaceTokenProvider?: NonNullable<OpenrtcClientOptions>['spaceTokenProvider'];
|
|
17
|
-
storagePrefix?: OpenrtcClientOptions['storagePrefix'];
|
|
18
|
-
nodeIdPersistence?: OpenrtcClientOptions['nodeIdPersistence'];
|
|
19
|
-
transports?: OpenrtcClientOptions['transports'];
|
|
20
|
-
transportPriority?: OpenrtcClientOptions['transportPriority'];
|
|
36
|
+
/** Preferred 2.0 path: one already-activated ephemeral match room. */
|
|
37
|
+
room?: OpenRTCRoomHandle;
|
|
21
38
|
preferredPayloadTransports?: RuntimeApplicationPayloadReadinessOptions['preferredTransports'];
|
|
22
|
-
strictMode?: OpenrtcClientOptions['strictMode'];
|
|
23
|
-
turnCredentialsProvider?: NonNullable<OpenrtcClientOptions>['turnCredentialsProvider'];
|
|
24
39
|
displayName?: string;
|
|
25
40
|
maxPeers?: number;
|
|
26
41
|
localFallback?: boolean;
|
|
@@ -235,6 +250,7 @@ export type RuntimeConnectionLike = {
|
|
|
235
250
|
parallelTransport?: string | null;
|
|
236
251
|
};
|
|
237
252
|
getAvailableTransports?(): string[];
|
|
253
|
+
isReadyForApplicationPayload?(options?: RuntimeApplicationPayloadReadinessOptions): boolean;
|
|
238
254
|
onUpgradeStateChange?(callback: (state: 'none' | 'upgrading' | 'upgraded' | 'failed') => void): void;
|
|
239
255
|
requestWebRTCUpgrade?(reason?: string): void;
|
|
240
256
|
send(message: unknown): Promise<void> | void;
|
|
@@ -253,4 +269,3 @@ export type RuntimeMemberLike = {
|
|
|
253
269
|
metadata?: string | Record<string, unknown> | null;
|
|
254
270
|
};
|
|
255
271
|
export type RuntimeEnvelopeBody = UserMessageBody | StateSnapshotBody | StatePatchBody | ObjectSnapshotBody | ObjectUpsertBody | ObjectRemoveBody | MetadataBody | VoiceSignalBody | Record<string, unknown>;
|
|
256
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openrtc-netcode",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-rc.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test:watch": "vitest"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"openrtc": "
|
|
45
|
+
"openrtc": "2.0.0-rc.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^24.10.1",
|