@bloopjs/web 0.0.43 → 0.0.45
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/dist/App.d.ts +30 -1
- package/dist/App.d.ts.map +1 -1
- package/dist/mod.d.ts +1 -0
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +834 -26
- package/dist/mod.js.map +10 -5
- package/dist/netcode/broker.d.ts +12 -0
- package/dist/netcode/broker.d.ts.map +1 -0
- package/dist/netcode/logs.d.ts +37 -0
- package/dist/netcode/logs.d.ts.map +1 -0
- package/dist/netcode/mod.d.ts +7 -0
- package/dist/netcode/mod.d.ts.map +1 -0
- package/dist/netcode/protocol.d.ts +42 -0
- package/dist/netcode/protocol.d.ts.map +1 -0
- package/dist/netcode/transport.d.ts +16 -0
- package/dist/netcode/transport.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/App.ts +48 -3
- package/src/mod.ts +1 -0
- package/src/netcode/broker.ts +136 -0
- package/src/netcode/logs.ts +81 -0
- package/src/netcode/mod.ts +14 -0
- package/src/netcode/protocol.ts +56 -0
- package/src/netcode/transport.ts +237 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { WebSocket } from "partysocket";
|
|
2
|
+
import { logger } from "./logs.ts";
|
|
3
|
+
import type { BrokerMessage, PeerMessage } from "./protocol.ts";
|
|
4
|
+
|
|
5
|
+
const iceServers: RTCConfiguration["iceServers"] = [
|
|
6
|
+
{ urls: "stun:stun.cloudflare.com:3478" },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export type WebRtcPipe = {
|
|
10
|
+
peerConnection: RTCPeerConnection;
|
|
11
|
+
reliable: RTCDataChannel;
|
|
12
|
+
unreliable: RTCDataChannel;
|
|
13
|
+
peerId: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function connect(
|
|
17
|
+
ws: WebSocket,
|
|
18
|
+
peerId: string,
|
|
19
|
+
/** defaults to 10s */
|
|
20
|
+
timeoutMs: number = 10000,
|
|
21
|
+
): Promise<WebRtcPipe> {
|
|
22
|
+
const pc = new RTCPeerConnection({ iceServers });
|
|
23
|
+
const reliable = pc.createDataChannel("reliable", {});
|
|
24
|
+
const unreliable = pc.createDataChannel("unreliable", {
|
|
25
|
+
ordered: false,
|
|
26
|
+
maxRetransmits: 0,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const offer = await pc.createOffer();
|
|
30
|
+
await pc.setLocalDescription(offer);
|
|
31
|
+
logger.log({ source: "webrtc", label: "set offer", json: offer });
|
|
32
|
+
await gatherIce(pc, timeoutMs);
|
|
33
|
+
logger.log({ source: "webrtc", label: "gathered ICE candidates" });
|
|
34
|
+
|
|
35
|
+
logger.log({
|
|
36
|
+
source: "ws",
|
|
37
|
+
label: "sending local description",
|
|
38
|
+
json: pc.localDescription,
|
|
39
|
+
to: peerId,
|
|
40
|
+
});
|
|
41
|
+
send(ws, {
|
|
42
|
+
type: "offer",
|
|
43
|
+
payload: btoa(JSON.stringify(pc.localDescription)),
|
|
44
|
+
target: peerId,
|
|
45
|
+
});
|
|
46
|
+
await waitForAnswer(ws, pc, timeoutMs);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
peerConnection: pc,
|
|
50
|
+
reliable,
|
|
51
|
+
unreliable,
|
|
52
|
+
peerId,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function logErrors(dc: RTCDataChannel) {
|
|
57
|
+
dc.onerror = (event) => {
|
|
58
|
+
logger.error({
|
|
59
|
+
source: "webrtc",
|
|
60
|
+
label: `error on ${dc.label} channel`,
|
|
61
|
+
json: event.error,
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
dc.onclosing = (_event) => {
|
|
66
|
+
logger.log({
|
|
67
|
+
source: "webrtc",
|
|
68
|
+
label: `closing ${dc.label} channel`,
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
dc.onopen = (_event) => {
|
|
73
|
+
logger.log({
|
|
74
|
+
source: "webrtc",
|
|
75
|
+
label: `opened ${dc.label} channel`,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
dc.onclose = (_event) => {
|
|
80
|
+
logger.log({
|
|
81
|
+
source: "webrtc",
|
|
82
|
+
label: `closed ${dc.label} channel`,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function logPeerConnection(pc: RTCPeerConnection, peerId: string) {
|
|
88
|
+
pc.onconnectionstatechange = () => {
|
|
89
|
+
logger.log({
|
|
90
|
+
source: "webrtc",
|
|
91
|
+
label: `[${peerId.substring(0, 6)}] connectionState = ${pc.connectionState}`,
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
pc.onsignalingstatechange = () => {
|
|
96
|
+
logger.log({
|
|
97
|
+
source: "webrtc",
|
|
98
|
+
label: `[${peerId.substring(0, 6)}] signalingState = ${pc.signalingState}`,
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function gatherIce(
|
|
104
|
+
pc: RTCPeerConnection,
|
|
105
|
+
timeoutMs: number,
|
|
106
|
+
): Promise<boolean | Error> {
|
|
107
|
+
return new Promise<boolean | Error>((yes, no) => {
|
|
108
|
+
setTimeout(
|
|
109
|
+
() => no(new Error("Timed out waiting for completion")),
|
|
110
|
+
timeoutMs,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
pc.onicegatheringstatechange = () => {
|
|
114
|
+
logger.log({
|
|
115
|
+
source: "webrtc",
|
|
116
|
+
label: `icegatheringstatechange: ${pc.iceGatheringState}`,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (pc.iceGatheringState === "complete") {
|
|
120
|
+
yes(true);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function waitForAnswer(
|
|
127
|
+
ws: WebSocket,
|
|
128
|
+
pc: RTCPeerConnection,
|
|
129
|
+
timeoutMs: number,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
return new Promise<void>((yes, no) => {
|
|
132
|
+
const timeoutId = setTimeout(no, timeoutMs);
|
|
133
|
+
const messageListener = async (event: MessageEvent) => {
|
|
134
|
+
const serverMsg: BrokerMessage = JSON.parse(event.data);
|
|
135
|
+
if (serverMsg.type !== "message:json") {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const peerMsg = serverMsg.message;
|
|
139
|
+
if (peerMsg.type !== "answer") {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const answerDesc = JSON.parse(atob(peerMsg.payload));
|
|
143
|
+
logger.log({
|
|
144
|
+
source: "webrtc",
|
|
145
|
+
label: "received answer",
|
|
146
|
+
json: answerDesc,
|
|
147
|
+
});
|
|
148
|
+
await pc.setRemoteDescription(new RTCSessionDescription(answerDesc));
|
|
149
|
+
logger.log({
|
|
150
|
+
source: "webrtc",
|
|
151
|
+
label: "set remote description with answer",
|
|
152
|
+
});
|
|
153
|
+
clearTimeout(timeoutId);
|
|
154
|
+
yes();
|
|
155
|
+
};
|
|
156
|
+
ws.addEventListener("message", messageListener);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function listenForOffers(ws: WebSocket, cb: (pipe: WebRtcPipe) => void) {
|
|
161
|
+
const messageListener = async (event: MessageEvent) => {
|
|
162
|
+
const envelope: BrokerMessage = JSON.parse(event.data);
|
|
163
|
+
if (envelope.type !== "message:json") {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const msg = envelope.message;
|
|
167
|
+
if (msg.type !== "offer") {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
logger.log({ source: "webrtc", label: "received offer" });
|
|
171
|
+
const offer = JSON.parse(atob(msg.payload));
|
|
172
|
+
const pc = new RTCPeerConnection({ iceServers });
|
|
173
|
+
|
|
174
|
+
await pc.setRemoteDescription(offer);
|
|
175
|
+
logger.log({
|
|
176
|
+
source: "webrtc",
|
|
177
|
+
label: "set remote description",
|
|
178
|
+
json: { offer, remoteDescription: pc.remoteDescription },
|
|
179
|
+
});
|
|
180
|
+
const answer = await pc.createAnswer();
|
|
181
|
+
await pc.setLocalDescription(answer);
|
|
182
|
+
logger.log({
|
|
183
|
+
source: "webrtc",
|
|
184
|
+
label: "set local description",
|
|
185
|
+
json: pc.localDescription,
|
|
186
|
+
});
|
|
187
|
+
await gatherIce(pc, 10000);
|
|
188
|
+
|
|
189
|
+
const channels = {
|
|
190
|
+
reliable: null as RTCDataChannel | null,
|
|
191
|
+
unreliable: null as RTCDataChannel | null,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
pc.ondatachannel = (event) => {
|
|
195
|
+
logger.log({
|
|
196
|
+
source: "webrtc",
|
|
197
|
+
label: `received datachannel ${event.channel.label}`,
|
|
198
|
+
});
|
|
199
|
+
switch (event.channel.label) {
|
|
200
|
+
case "reliable":
|
|
201
|
+
channels.reliable = event.channel;
|
|
202
|
+
break;
|
|
203
|
+
case "unreliable":
|
|
204
|
+
channels.unreliable = event.channel;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
if (channels.reliable && channels.unreliable) {
|
|
208
|
+
pc.ondatachannel = null;
|
|
209
|
+
cb({
|
|
210
|
+
peerConnection: pc,
|
|
211
|
+
reliable: channels.reliable,
|
|
212
|
+
unreliable: channels.unreliable,
|
|
213
|
+
peerId: envelope.peerId,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
logger.log({
|
|
219
|
+
source: "webrtc",
|
|
220
|
+
label: "sending answer",
|
|
221
|
+
json: pc.localDescription,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
send(ws, {
|
|
225
|
+
type: "answer",
|
|
226
|
+
payload: btoa(JSON.stringify(pc.localDescription)),
|
|
227
|
+
target: envelope.peerId,
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
ws.addEventListener("message", messageListener);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function send(ws: WebSocket, msg: PeerMessage) {
|
|
235
|
+
ws.send(JSON.stringify(msg));
|
|
236
|
+
logger.log({ source: "ws", direction: "outbound", json: msg });
|
|
237
|
+
}
|