@minicode-dev/chat 0.1.7 → 0.1.9

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 (37) hide show
  1. package/README.md +25 -0
  2. package/dist/chunks/{BattleshipGameCard-CAWHxqJE.js → BattleshipGameCard-BFqA0cjT.js} +247 -246
  3. package/dist/chunks/{BattleshipGameCard-CAWHxqJE.js.map → BattleshipGameCard-BFqA0cjT.js.map} +1 -1
  4. package/dist/chunks/{ChatContext-CUiEAMKK.js → ChatContext-dty_KPDX.js} +2 -2
  5. package/dist/chunks/{ChatContext-CUiEAMKK.js.map → ChatContext-dty_KPDX.js.map} +1 -1
  6. package/dist/chunks/TanksGameCard-D0n-lzTs.js +1535 -0
  7. package/dist/chunks/TanksGameCard-D0n-lzTs.js.map +1 -0
  8. package/dist/chunks/TanksLiveGameCard-BmNRXP66.js +849 -0
  9. package/dist/chunks/TanksLiveGameCard-BmNRXP66.js.map +1 -0
  10. package/dist/chunks/WebRtcLab-B7xBlJy1.js +510 -0
  11. package/dist/chunks/WebRtcLab-B7xBlJy1.js.map +1 -0
  12. package/dist/chunks/{connect4Sounds-BDXQAvU1.js → connect4Sounds-BN7d_TM2.js} +654 -618
  13. package/dist/chunks/connect4Sounds-BN7d_TM2.js.map +1 -0
  14. package/dist/chunks/createLiveScene-CtYPcOPo.js +364 -0
  15. package/dist/chunks/createLiveScene-CtYPcOPo.js.map +1 -0
  16. package/dist/chunks/createPeerLink-qei05vpV.js +478 -0
  17. package/dist/chunks/createPeerLink-qei05vpV.js.map +1 -0
  18. package/dist/chunks/createTankScene-CV7Brxmt.js +685 -0
  19. package/dist/chunks/createTankScene-CV7Brxmt.js.map +1 -0
  20. package/dist/chunks/fx-BHsl4S_J.js +1470 -0
  21. package/dist/chunks/fx-BHsl4S_J.js.map +1 -0
  22. package/dist/chunks/gameStatus-Cu1Z8un9.js +69 -0
  23. package/dist/chunks/gameStatus-Cu1Z8un9.js.map +1 -0
  24. package/dist/chunks/signalFaults-Dv2YjjKy.js +36 -0
  25. package/dist/chunks/signalFaults-Dv2YjjKy.js.map +1 -0
  26. package/dist/chunks/tanksSounds-C99OD2xD.js +177 -0
  27. package/dist/chunks/tanksSounds-C99OD2xD.js.map +1 -0
  28. package/dist/index.js +1956 -1857
  29. package/dist/index.js.map +1 -1
  30. package/dist/styles.css +1 -1
  31. package/index.d.ts +61 -0
  32. package/package.json +1 -1
  33. package/dist/chunks/TanksGameCard-BEnXTz_J.js +0 -1705
  34. package/dist/chunks/TanksGameCard-BEnXTz_J.js.map +0 -1
  35. package/dist/chunks/connect4Sounds-BDXQAvU1.js.map +0 -1
  36. package/dist/chunks/createTankScene-BuliAxwR.js +0 -2150
  37. package/dist/chunks/createTankScene-BuliAxwR.js.map +0 -1
@@ -0,0 +1,478 @@
1
+ // src/net/webrtc/protocol.js
2
+ var e = "LIRA_WEBRTC_V1:", t = 1e4, n = [
3
+ "hello",
4
+ "ready",
5
+ "offer",
6
+ "answer",
7
+ "ice",
8
+ "ack",
9
+ "bye"
10
+ ], r = /^[A-Za-z0-9_-]{4,64}$/, i = 32, a = (e) => typeof e == "string" && e.startsWith("LIRA_WEBRTC_V1:"), o = (e, t) => {
11
+ if (t == null) return [
12
+ "hello",
13
+ "ready",
14
+ "ack",
15
+ "bye"
16
+ ].includes(e);
17
+ if (typeof t != "object" || Array.isArray(t)) return !1;
18
+ switch (e) {
19
+ case "offer":
20
+ case "answer": return typeof t.sdp == "string" && t.sdp.length > 0;
21
+ case "ice": return Array.isArray(t.c) && t.c.length <= i && t.c.every((e) => e === null || e && typeof e.candidate == "string");
22
+ case "ack": return typeof t.of == "string" && r.test(t.of);
23
+ default: return !0;
24
+ }
25
+ };
26
+ function s(e) {
27
+ return !!e && typeof e == "object" && e.v === 1 && n.includes(e.t) && typeof e.sid == "string" && r.test(e.sid) && typeof e.mid == "string" && r.test(e.mid) && typeof e.from == "string" && r.test(e.from) && (e.to == null || typeof e.to == "string" && r.test(e.to)) && Number.isInteger(e.gen ?? 0) && (e.gen ?? 0) >= 0 && (e.at == null || Number.isFinite(e.at)) && o(e.t, e.d);
28
+ }
29
+ function c(t) {
30
+ let n = e + JSON.stringify({
31
+ v: 1,
32
+ ...t
33
+ });
34
+ return n.length <= 1e4 ? n : null;
35
+ }
36
+ function l(e) {
37
+ if (!a(e) || e.length > 1e4) return null;
38
+ try {
39
+ let t = JSON.parse(e.slice(15));
40
+ return s(t) ? t : null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ var u = () => {
46
+ try {
47
+ if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID().replace(/-/g, "").slice(0, 20);
48
+ } catch {}
49
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;
50
+ }, d = [{ urls: ["stun:stun.l.google.com:19302", "stun:stun.cloudflare.com:3478"] }], f = 1200, p = 6, ee = 2e3, te = 3e4, ne = 80, m = 3e3, h = 2e4, g = 1e3, _ = 1e4, v = 2, re = 5, y = 16384, ie = 500, b = () => ({
51
+ sent: 0,
52
+ dropped: 0,
53
+ received: 0,
54
+ firstSeq: null,
55
+ maxSeq: 0,
56
+ outOfOrder: 0,
57
+ lossPct: 0,
58
+ hzIn: 0,
59
+ hzOut: 0,
60
+ windowIn: 0,
61
+ windowOut: 0
62
+ }), ae = {
63
+ host: "directa (red local)",
64
+ srflx: "directa (NAT vía STUN)",
65
+ prflx: "directa (NAT)",
66
+ relay: "TURN (relay)"
67
+ };
68
+ function x({ signaling: e, selfId: t, peerId: n, roomId: r = null, tabId: i = u(), iceServers: o = d, iceTransportPolicy: s = "all", autoReconnect: x = !0, listen: S = !0, RTCPeerConnectionImpl: C = globalThis.RTCPeerConnection, onChange: oe = () => {}, onMove: se = () => {}, onEvent: ce = () => {}, log: w = () => {} } = {}) {
69
+ if (!e?.send || !e?.subscribe) throw Error("createPeerLink: signaling { send, subscribe } requerido");
70
+ t = String(t), n = String(n);
71
+ let T = S, E = !1, D = null, O = 0, k = null, A = null, j = /* @__PURE__ */ new Set(), M = {
72
+ phase: "idle",
73
+ role: null,
74
+ sid: null,
75
+ gen: 0,
76
+ tabId: i,
77
+ peerTab: null,
78
+ ice: "new",
79
+ connection: "new",
80
+ signalingState: "stable",
81
+ gathering: "new",
82
+ route: null,
83
+ rtt: null,
84
+ statsRtt: null,
85
+ reconnects: 0,
86
+ restarts: 0,
87
+ error: null,
88
+ move: b(),
89
+ signals: {
90
+ sent: 0,
91
+ received: 0,
92
+ retransmits: 0,
93
+ duplicates: 0,
94
+ ignored: 0,
95
+ invalid: 0,
96
+ tooLarge: 0,
97
+ maxLength: 0
98
+ },
99
+ events: {
100
+ sent: 0,
101
+ received: 0
102
+ }
103
+ }, N = () => {
104
+ E || oe(P());
105
+ }, P = () => ({
106
+ ...M,
107
+ move: { ...M.move },
108
+ signals: { ...M.signals },
109
+ events: { ...M.events }
110
+ }), F = (e, t = null) => {
111
+ M.phase = e, M.error = t, w(`fase: ${e}${t ? ` (${t})` : ""}`), N();
112
+ }, I = (t) => {
113
+ try {
114
+ return Promise.resolve(e.send(t)).then(() => !0, () => !1);
115
+ } catch {
116
+ return Promise.resolve(!1);
117
+ }
118
+ }, L = (e, t, n, { to: r = e?.peerTab ?? null, reliable: a = !0, gen: o = e?.gen ?? 0, sid: s = e?.sid } = {}) => {
119
+ let l = u(), d = c({
120
+ t,
121
+ sid: s,
122
+ gen: o,
123
+ mid: l,
124
+ at: Date.now(),
125
+ from: i,
126
+ to: r,
127
+ d: n ?? void 0
128
+ });
129
+ if (!d) return M.signals.tooLarge += 1, w(`señal ${t} supera el límite y no se envía`), e && U(e, "signal_too_large"), null;
130
+ if (M.signals.sent += 1, M.signals.maxLength = Math.max(M.signals.maxLength, d.length), I(d), a && e) {
131
+ let n = {
132
+ text: d,
133
+ tries: 1,
134
+ timer: null
135
+ }, r = () => {
136
+ !e.closed && e.outbox.has(l) && (n.tries >= p ? (e.outbox.delete(l), w(`señal ${t} sin ACK tras ${p} intentos`), t !== "bye" && U(e, "signal_timeout")) : (n.tries += 1, M.signals.retransmits += 1, I(d), n.timer = setTimeout(r, f)));
137
+ };
138
+ n.timer = setTimeout(r, f), e.outbox.set(l, n);
139
+ }
140
+ return l;
141
+ }, R = (e) => !j.has(e) && (j.add(e), j.size > ie && j.delete(j.values().next().value), !0), z = e.subscribe((e) => {
142
+ if (E || !e || !a(e.text) || r != null && e.roomId && String(e.roomId) !== String(r)) return;
143
+ let t = l(e.text);
144
+ if (!t) M.signals.invalid += 1;
145
+ else if (String(e.userId) !== n || t.from === i || t.to && t.to !== i) M.signals.ignored += 1;
146
+ else if (M.signals.received += 1, t.t !== "hello" && t.t !== "ack" && L(null, "ack", { of: t.mid }, {
147
+ to: t.from,
148
+ reliable: !1,
149
+ sid: t.sid,
150
+ gen: t.gen
151
+ }), !R(`${t.from}:${t.mid}`)) M.signals.duplicates += 1, N();
152
+ else {
153
+ switch (t.t) {
154
+ case "ack":
155
+ ge(t);
156
+ break;
157
+ case "hello":
158
+ _e(t);
159
+ break;
160
+ case "ready":
161
+ ve(t);
162
+ break;
163
+ case "offer":
164
+ ye(t);
165
+ break;
166
+ case "answer":
167
+ be(t);
168
+ break;
169
+ case "ice":
170
+ xe(t);
171
+ break;
172
+ case "bye": Se(t);
173
+ }
174
+ D && t.sid === D.sid && t.from === D.peerTab && t.at && (D.peerAt = Math.max(D.peerAt, t.at));
175
+ }
176
+ }), B = (e, t = u(), n = null) => ({
177
+ role: e,
178
+ sid: t,
179
+ peerTab: n,
180
+ gen: 0,
181
+ pc: null,
182
+ move: null,
183
+ events: null,
184
+ closed: !1,
185
+ outbox: /* @__PURE__ */ new Map(),
186
+ pendingIce: [],
187
+ localIce: [],
188
+ iceTimer: null,
189
+ helloTimer: null,
190
+ helloStarted: Date.now(),
191
+ disconnectTimer: null,
192
+ connectTimer: null,
193
+ pingTimer: null,
194
+ statsTimer: null,
195
+ tickTimer: null,
196
+ lastSeen: Date.now(),
197
+ pingSeq: 0,
198
+ moveSeq: 0,
199
+ restarts: 0,
200
+ open: 0,
201
+ peerAt: 0
202
+ }), V = (e, { bye: t = !1 } = {}) => {
203
+ if (e && !e.closed) {
204
+ if (t && e.peerTab && L(e, "bye", null, { reliable: !1 }), e.closed = !0, [
205
+ e.iceTimer,
206
+ e.disconnectTimer,
207
+ e.connectTimer
208
+ ].forEach((e) => clearTimeout(e)), [
209
+ e.helloTimer,
210
+ e.pingTimer,
211
+ e.statsTimer,
212
+ e.tickTimer
213
+ ].forEach((e) => clearInterval(e)), e.outbox.forEach((e) => clearTimeout(e.timer)), e.outbox.clear(), [e.move, e.events].forEach((e) => {
214
+ if (e) {
215
+ e.onopen = e.onclose = e.onmessage = e.onerror = null;
216
+ try {
217
+ e.close();
218
+ } catch {}
219
+ }
220
+ }), e.pc) {
221
+ e.pc.onicecandidate = e.pc.oniceconnectionstatechange = e.pc.onconnectionstatechange = null, e.pc.onsignalingstatechange = e.pc.onicegatheringstatechange = null;
222
+ try {
223
+ e.pc.close();
224
+ } catch {}
225
+ }
226
+ D === e && (D = null);
227
+ }
228
+ }, H = (e) => {
229
+ if (E || !x || k) return;
230
+ if (O >= re) {
231
+ F("failed", `${e}; sin más reintentos`);
232
+ return;
233
+ }
234
+ let t = Math.min(8e3, 1e3 * 2 ** O);
235
+ O += 1, M.reconnects = O, F("reconnecting", e), k = setTimeout(() => {
236
+ k = null, !E && !D && $();
237
+ }, t);
238
+ }, U = (e, t) => {
239
+ if (e.closed) return;
240
+ let n = e.role === "caller";
241
+ A = e.sid, V(e, { bye: !0 }), n ? H(t) : F(T ? "listening" : "idle", t);
242
+ }, W = (e) => {
243
+ M.role = e.role, M.sid = e.sid, M.gen = e.gen, M.peerTab = e.peerTab, M.ice = "new", M.connection = "new", M.signalingState = "stable", M.gathering = "new", M.route = null, M.rtt = null, M.statsRtt = null, M.restarts = 0, M.move = b();
244
+ }, G = (e) => {
245
+ if (!C) return U(e, "webrtc_unavailable"), null;
246
+ let t = new C({
247
+ iceServers: o,
248
+ iceTransportPolicy: s
249
+ });
250
+ return e.pc = t, t.onicecandidate = ({ candidate: t }) => {
251
+ e.closed || (e.localIce.push(t ? t.toJSON?.() ?? t : null), e.iceTimer || (e.iceTimer = setTimeout(() => le(e), ne)));
252
+ }, t.onicegatheringstatechange = () => {
253
+ M.gathering = t.iceGatheringState, N();
254
+ }, t.onsignalingstatechange = () => {
255
+ M.signalingState = t.signalingState, N();
256
+ }, t.onconnectionstatechange = () => {
257
+ M.connection = t.connectionState, N();
258
+ }, t.oniceconnectionstatechange = () => ue(e), e.move = t.createDataChannel("move", {
259
+ negotiated: !0,
260
+ id: 0,
261
+ ordered: !1,
262
+ maxRetransmits: 0
263
+ }), e.events = t.createDataChannel("events", {
264
+ negotiated: !0,
265
+ id: 1,
266
+ ordered: !0
267
+ }), [e.move, e.events].forEach((t) => {
268
+ t.onopen = () => de(e), t.onclose = () => {
269
+ !e.closed && M.phase === "connected" && X(e, "canal cerrado");
270
+ };
271
+ }), e.move.onmessage = (t) => me(e, t.data), e.events.onmessage = (t) => pe(e, t.data), e.connectTimer = setTimeout(() => {
272
+ M.phase !== "connected" && U(e, "connect_timeout");
273
+ }, h), t;
274
+ }, le = (e) => {
275
+ if (e.iceTimer = null, e.closed || !e.localIce.length) return;
276
+ let t = e.localIce.splice(0, e.localIce.length);
277
+ for (let n = 0; n < t.length; n += 16) L(e, "ice", { c: t.slice(n, n + 16) });
278
+ }, K = async (e, t) => {
279
+ for (let n of t) try {
280
+ n && await e.pc.addIceCandidate(n);
281
+ } catch (e) {
282
+ w(`ICE remoto rechazado: ${e?.message ?? e}`);
283
+ }
284
+ }, q = (e) => {
285
+ let t = e.pendingIce.filter((t) => t.gen === e.gen);
286
+ e.pendingIce = e.pendingIce.filter((t) => t.gen > e.gen), t.forEach((t) => void K(e, t.c));
287
+ }, J = async (e, t = !1) => {
288
+ try {
289
+ let n = await e.pc.createOffer(t ? { iceRestart: !0 } : void 0);
290
+ if (e.closed || (await e.pc.setLocalDescription(n), e.closed)) return;
291
+ L(e, "offer", { sdp: e.pc.localDescription.sdp });
292
+ } catch (t) {
293
+ U(e, `offer: ${t?.message ?? t}`);
294
+ }
295
+ }, Y = (e) => {
296
+ e.closed || e.role !== "caller" || (e.restarts >= v ? X(e, "ICE no se recupera") : (e.restarts += 1, M.restarts = e.restarts, e.gen += 1, M.gen = e.gen, F("restarting"), J(e, !0)));
297
+ }, ue = (e) => {
298
+ if (e.closed) return;
299
+ let t = e.pc.iceConnectionState;
300
+ M.ice = t, clearTimeout(e.disconnectTimer), t === "connected" || t === "completed" ? (M.phase === "restarting" && F("connected"), Q(e)) : t === "disconnected" ? e.disconnectTimer = setTimeout(() => Y(e), m) : t === "failed" && Y(e), N();
301
+ }, de = (e) => {
302
+ e.open += 1, !(e.open < 2 || e.closed) && (clearTimeout(e.connectTimer), O = 0, e.lastSeen = Date.now(), e.pingTimer = setInterval(() => fe(e), g), e.statsTimer = setInterval(() => void Q(e), 2e3), e.tickTimer = setInterval(() => he(e), 1e3), F("connected"), Q(e));
303
+ }, X = (e, t) => U(e, t), fe = (e) => {
304
+ Date.now() - e.lastSeen > _ ? X(e, "sin latido del rival") : Z(e.events, JSON.stringify({
305
+ k: "ping",
306
+ n: ++e.pingSeq,
307
+ ts: performance.now()
308
+ }));
309
+ }, Z = (e, t) => {
310
+ if (e?.readyState !== "open") return !1;
311
+ try {
312
+ return e.send(t), !0;
313
+ } catch {
314
+ return !1;
315
+ }
316
+ }, pe = (e, t) => {
317
+ e.lastSeen = Date.now();
318
+ let n;
319
+ try {
320
+ n = JSON.parse(t);
321
+ } catch {
322
+ return;
323
+ }
324
+ n?.k === "ping" ? Z(e.events, JSON.stringify({
325
+ k: "pong",
326
+ ts: n.ts
327
+ })) : n?.k === "pong" ? M.rtt = Math.round((performance.now() - n.ts) * 10) / 10 : n?.k === "app" && (M.events.received += 1, ce(n.d));
328
+ }, me = (e, t) => {
329
+ e.lastSeen = Date.now();
330
+ let n;
331
+ try {
332
+ n = JSON.parse(t);
333
+ } catch {
334
+ return;
335
+ }
336
+ if (!Array.isArray(n) || !Number.isInteger(n[0])) return;
337
+ let [r, , i] = n, a = M.move;
338
+ a.received += 1, a.windowIn += 1, a.firstSeq ?? (a.firstSeq = r), r < a.maxSeq ? a.outOfOrder += 1 : (a.maxSeq = r, se(i, { seq: r }));
339
+ }, he = (e) => {
340
+ let t = M.move;
341
+ t.hzIn = t.windowIn, t.hzOut = t.windowOut, t.windowIn = 0, t.windowOut = 0;
342
+ let n = t.firstSeq == null ? 0 : t.maxSeq - t.firstSeq + 1;
343
+ t.lossPct = n > 0 ? Math.max(0, Math.round((1 - t.received / n) * 1e3) / 10) : 0, e.closed || N();
344
+ }, Q = async (e) => {
345
+ if (!e.closed && e.pc?.getStats) try {
346
+ let t = await e.pc.getStats(), n = /* @__PURE__ */ new Map();
347
+ t.forEach((e) => n.set(e.id, e));
348
+ let r = null;
349
+ if (t.forEach((e) => {
350
+ e.type === "transport" && e.selectedCandidatePairId && (r = n.get(e.selectedCandidatePairId));
351
+ }), r || t.forEach((e) => {
352
+ e.type === "candidate-pair" && (e.selected || e.nominated) && e.state === "succeeded" && (r = e);
353
+ }), !r) return;
354
+ let i = n.get(r.localCandidateId), a = n.get(r.remoteCandidateId), o = i?.candidateType === "relay" || a?.candidateType === "relay", s = o ? "relay" : i?.candidateType ?? "host";
355
+ M.route = {
356
+ local: i?.candidateType ?? null,
357
+ remote: a?.candidateType ?? null,
358
+ protocol: i?.protocol ?? null,
359
+ relay: o,
360
+ label: ae[s] ?? s
361
+ }, typeof r.currentRoundTripTime == "number" && (M.statsRtt = Math.round(r.currentRoundTripTime * 1e4) / 10), N();
362
+ } catch {}
363
+ }, ge = (e) => {
364
+ let t = D?.outbox.get(e.d.of);
365
+ t && (clearTimeout(t.timer), D.outbox.delete(e.d.of));
366
+ }, _e = (e) => {
367
+ if (D && D.sid === e.sid || D && D.peerTab === e.from && (e.at ?? 0) <= D.peerAt) return;
368
+ let t = D?.role === "caller" && !D.peerTab;
369
+ if (t && i < e.from) return;
370
+ if (!T && !t) {
371
+ M.signals.ignored += 1;
372
+ return;
373
+ }
374
+ D && V(D, { bye: !t });
375
+ let n = B("answerer", e.sid, e.from);
376
+ D = n, W(n), G(n) && (L(n, "ready", null), F("negotiating"));
377
+ }, ve = (e) => {
378
+ let t = D;
379
+ t && t.role === "caller" && t.sid === e.sid && (t.peerTab && t.peerTab !== e.from ? L(t, "bye", null, {
380
+ to: e.from,
381
+ reliable: !1
382
+ }) : t.peerTab || (t.peerTab = e.from, M.peerTab = e.from, clearInterval(t.helloTimer), G(t) && (F("negotiating"), J(t))));
383
+ }, ye = async (e) => {
384
+ let t = D;
385
+ if (!(!t || t.role !== "answerer" || t.sid !== e.sid || t.peerTab !== e.from || e.gen < t.gen)) {
386
+ t.gen = e.gen, M.gen = t.gen;
387
+ try {
388
+ if (await t.pc.setRemoteDescription({
389
+ type: "offer",
390
+ sdp: e.d.sdp
391
+ }), t.closed) return;
392
+ q(t);
393
+ let n = await t.pc.createAnswer();
394
+ if (t.closed || (await t.pc.setLocalDescription(n), t.closed)) return;
395
+ L(t, "answer", { sdp: t.pc.localDescription.sdp });
396
+ } catch (e) {
397
+ U(t, `answer: ${e?.message ?? e}`);
398
+ }
399
+ }
400
+ }, be = async (e) => {
401
+ let t = D;
402
+ if (t && t.role === "caller" && t.sid === e.sid && t.peerTab === e.from && e.gen === t.gen && t.pc.signalingState === "have-local-offer") try {
403
+ if (await t.pc.setRemoteDescription({
404
+ type: "answer",
405
+ sdp: e.d.sdp
406
+ }), t.closed) return;
407
+ q(t), M.phase === "restarting" && ["connected", "completed"].includes(t.pc.iceConnectionState) && F("connected");
408
+ } catch (e) {
409
+ U(t, `answer remota: ${e?.message ?? e}`);
410
+ }
411
+ }, xe = (e) => {
412
+ let t = D;
413
+ !t || t.sid !== e.sid || t.peerTab !== e.from || e.gen < t.gen || (e.gen > t.gen || !t.pc?.remoteDescription ? t.pendingIce.push({
414
+ gen: e.gen,
415
+ c: e.d.c
416
+ }) : K(t, e.d.c));
417
+ }, Se = (e) => {
418
+ if (e.sid === A && (k || D?.role === "caller" && !D.peerTab)) {
419
+ clearTimeout(k), k = null, O = 0, D && V(D), F(T ? "listening" : "idle", "el rival cerró la conexión");
420
+ return;
421
+ }
422
+ let t = D;
423
+ t && t.sid === e.sid && (t.peerTab && t.peerTab !== e.from || (V(t), F(T ? "listening" : "idle", "el rival cerró la conexión")));
424
+ };
425
+ function $() {
426
+ if (E) return;
427
+ clearTimeout(k), k = null, D && V(D, { bye: !0 });
428
+ let e = B("caller");
429
+ D = e, W(e), F("calling");
430
+ let t = () => {
431
+ e.closed || e.peerTab || (Date.now() - e.helloStarted > te ? (V(e), F("failed", "el rival no respondió")) : L(e, "hello", null, {
432
+ to: null,
433
+ reliable: !1
434
+ }));
435
+ };
436
+ t(), e.helloTimer = setInterval(t, ee);
437
+ }
438
+ return T && F("listening"), {
439
+ tabId: i,
440
+ connect() {
441
+ O = 0, $();
442
+ },
443
+ setListening(e) {
444
+ T = !!e, D || F(T ? "listening" : "idle");
445
+ },
446
+ sendMove(e) {
447
+ let t = D;
448
+ if (!t || t.move?.readyState !== "open") return !1;
449
+ if (t.move.bufferedAmount > y) return M.move.dropped += 1, !1;
450
+ let n = Z(t.move, JSON.stringify([
451
+ ++t.moveSeq,
452
+ Date.now(),
453
+ e
454
+ ]));
455
+ return n && (M.move.sent += 1, M.move.windowOut += 1), n;
456
+ },
457
+ sendEvent(e) {
458
+ let t = Z(D?.events, JSON.stringify({
459
+ k: "app",
460
+ d: e
461
+ }));
462
+ return t && (M.events.sent += 1), t;
463
+ },
464
+ restartIce() {
465
+ D && Y(D);
466
+ },
467
+ getState: P,
468
+ hangup() {
469
+ clearTimeout(k), k = null, D && V(D, { bye: !0 }), F(T ? "listening" : "idle");
470
+ },
471
+ destroy() {
472
+ E || (clearTimeout(k), D && V(D, { bye: !0 }), E = !0, z?.());
473
+ }
474
+ };
475
+ }
476
+ export { l as a, e as i, x as n, c as o, t as r, a as s, d as t };
477
+
478
+ //# sourceMappingURL=createPeerLink-qei05vpV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createPeerLink-qei05vpV.js","names":["WEBRTC_PREFIX","MAX_SIGNAL_LENGTH","SIGNAL_TYPES","ID","MAX_CANDIDATES","isWebrtcMessage","text","validData","d","t","c","isValidSignal","signal","encodeSignal","decodeSignal","newSignalId","DEFAULT_ICE_SERVERS","RETRY_MS","MAX_TRIES","HELLO_EVERY_MS","HELLO_GIVE_UP_MS","ICE_BATCH_MS","DISCONNECTED_GRACE_MS","CONNECT_TIMEOUT_MS","PING_EVERY_MS","HEARTBEAT_TIMEOUT_MS","MAX_RESTARTS","MAX_RECONNECTS","MAX_BUFFERED","MAX_SEEN","emptyMoveStats","ROUTE_LABELS","createPeerLink","signaling","selfId","peerId","listening","listen","destroyed","session","reconnects","reconnectTimer","endedSid","seen","state","emit","onChange","snapshot","setPhase","phase","error","log","rawSend","sendSignal","s","mid","tabId","fail","reliable","entry","retry","remember","key","unsubscribe","frame","roomId","newSession","teardown","bye","timer","channel","scheduleReconnect","autoReconnect","reason","delay","call","wasCaller","bindState","createPc","RTCPeerConnectionImpl","pc","candidate","flushLocalIce","onIceState","onChannelOpen","lose","onMoveMessage","event","onEventMessage","batch","i","addRemoteIce","candidates","flushPendingIce","ready","item","makeOffer","offer","iceRestart","restartIce","ice","refreshStats","ping","tick","sendRaw","message","data","onEvent","packet","seq","payload","m","onMove","expected","report","byId","pair","local","remote","relay","type","onAck","onHello","glare","onReady","onOffer","answer","onAnswer","onIce","onBye","hello","value","sent"],"sources":["../../src/net/webrtc/protocol.js","../../src/net/webrtc/createPeerLink.js"],"sourcesContent":["// WebRTC signaling frames carried inside ordinary CHAT_MESSAGE text, the same idea\n// as LIRA_GAME_V1:. The chat backend only relays them; the sender is the\n// server-stamped user_id of the envelope, never a field of the payload.\n//\n// LIRA_WEBRTC_V1:{\"v\":1,\"t\":\"offer\",\"sid\":\"…\",\"gen\":0,\"mid\":\"…\",\"from\":\"tab\",\"to\":\"tab\",\"d\":{…}}\n//\n// t: hello (call, to = null) | ready (reply to hello) | offer | answer | ice | ack | bye\n// sid: session id chosen by the caller; gen: negotiation round (ICE restarts);\n// mid: message id for ACK/dedupe; from/to: tab ids, so several tabs of one user\n// and the server echo to the sender are told apart; at: sender clock (ms), only\n// compared with earlier frames of the same tab to spot late, stale calls.\n\nexport const WEBRTC_PREFIX = 'LIRA_WEBRTC_V1:';\n// Backend limit for CHAT_MESSAGE.message (Django audit). Frames above it are refused locally.\nexport const MAX_SIGNAL_LENGTH = 10000;\nexport const SIGNAL_TYPES = ['hello', 'ready', 'offer', 'answer', 'ice', 'ack', 'bye'];\n\nconst ID = /^[A-Za-z0-9_-]{4,64}$/;\nconst MAX_CANDIDATES = 32;\n\nexport const isWebrtcMessage = (text) => typeof text === 'string' && text.startsWith(WEBRTC_PREFIX);\n\nconst validData = (t, d) => {\n if (d == null) return ['hello', 'ready', 'ack', 'bye'].includes(t);\n if (typeof d !== 'object' || Array.isArray(d)) return false;\n switch (t) {\n case 'offer':\n case 'answer':\n return typeof d.sdp === 'string' && d.sdp.length > 0;\n case 'ice':\n return Array.isArray(d.c) && d.c.length <= MAX_CANDIDATES\n && d.c.every((c) => c === null || (c && typeof c.candidate === 'string'));\n case 'ack':\n return typeof d.of === 'string' && ID.test(d.of);\n default:\n return true;\n }\n};\n\nexport function isValidSignal(signal) {\n return Boolean(signal) && typeof signal === 'object'\n && signal.v === 1\n && SIGNAL_TYPES.includes(signal.t)\n && typeof signal.sid === 'string' && ID.test(signal.sid)\n && typeof signal.mid === 'string' && ID.test(signal.mid)\n && typeof signal.from === 'string' && ID.test(signal.from)\n && (signal.to == null || (typeof signal.to === 'string' && ID.test(signal.to)))\n && Number.isInteger(signal.gen ?? 0) && (signal.gen ?? 0) >= 0\n && (signal.at == null || Number.isFinite(signal.at))\n && validData(signal.t, signal.d);\n}\n\n/** Returns the frame text, or null when it would exceed MAX_SIGNAL_LENGTH. */\nexport function encodeSignal(signal) {\n const text = WEBRTC_PREFIX + JSON.stringify({ v: 1, ...signal });\n return text.length <= MAX_SIGNAL_LENGTH ? text : null;\n}\n\nexport function decodeSignal(text) {\n if (!isWebrtcMessage(text) || text.length > MAX_SIGNAL_LENGTH) return null;\n try {\n const signal = JSON.parse(text.slice(WEBRTC_PREFIX.length));\n return isValidSignal(signal) ? signal : null;\n } catch {\n return null;\n }\n}\n\nexport const newSignalId = () => {\n try {\n if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID().replace(/-/g, '').slice(0, 20);\n } catch { /* fallback below */ }\n return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;\n};\n\n// Normalizes a chat frame (flat or nested in data/payload) to { userId, roomId, text }.\nexport function readChatFrame(frame) {\n if (!frame || typeof frame !== 'object') return null;\n const nested = frame.data ?? frame.payload;\n const body = nested && typeof nested === 'object' ? { ...nested, ...frame } : frame;\n const text = frame.message ?? frame.text ?? nested?.message ?? nested?.text;\n if (typeof text !== 'string') return null;\n return { userId: String(body.user_id ?? ''), roomId: String(body.room_id ?? ''), text };\n}\n","// One WebRTC link between two chat users, signaled through the chat itself.\n//\n// Signaling: LIRA_WEBRTC_V1: frames (protocol.js) sent as ordinary CHAT_MESSAGE\n// text in the users' private room. The relay echoes frames to every tab of both\n// users, may duplicate them after a reconnect and gives no delivery guarantee, so:\n// - frames carry tab ids (from/to): own echoes and other tabs are ignored;\n// - every frame except hello/ack is ACKed and retransmitted until ACKed;\n// - duplicates are dropped by message id; ICE that arrives before its\n// description waits in a queue; stale sessions/generations are ignored.\n// Session: caller sends hello (repeated until answered); the first tab of the\n// peer that answers ready is bound, gets the offer and the others a bye.\n// Simultaneous calls resolve by tab id (the lower one keeps calling).\n//\n// Data: two negotiated DataChannels (no ondatachannel race):\n// move id 0, unordered, maxRetransmits 0 — latest position wins;\n// events id 1, ordered and reliable — pings, hits, game events.\n// Recovery: ICE 'failed' (or 'disconnected' for 3 s) triggers an ICE restart by\n// the caller; no traffic for HEARTBEAT_TIMEOUT_MS ends the session and the caller\n// starts a new one (autoReconnect, with backoff).\n\nimport { encodeSignal, decodeSignal, isWebrtcMessage, newSignalId } from './protocol.js';\n\nexport const DEFAULT_ICE_SERVERS = [{ urls: ['stun:stun.l.google.com:19302', 'stun:stun.cloudflare.com:3478'] }];\n\nconst RETRY_MS = 1200;\nconst MAX_TRIES = 6;\nconst HELLO_EVERY_MS = 2000;\nconst HELLO_GIVE_UP_MS = 30000;\nconst ICE_BATCH_MS = 80;\nconst DISCONNECTED_GRACE_MS = 3000;\nconst CONNECT_TIMEOUT_MS = 20000;\nconst PING_EVERY_MS = 1000;\nconst HEARTBEAT_TIMEOUT_MS = 10000;\nconst MAX_RESTARTS = 2;\nconst MAX_RECONNECTS = 5;\nconst MAX_BUFFERED = 16 * 1024;\nconst MAX_SEEN = 500;\n\nconst emptyMoveStats = () => ({ sent: 0, dropped: 0, received: 0, firstSeq: null, maxSeq: 0, outOfOrder: 0, lossPct: 0, hzIn: 0, hzOut: 0, windowIn: 0, windowOut: 0 });\n\nconst ROUTE_LABELS = { host: 'directa (red local)', srflx: 'directa (NAT vía STUN)', prflx: 'directa (NAT)', relay: 'TURN (relay)' };\n\nexport function createPeerLink({\n signaling,\n selfId,\n peerId,\n roomId = null,\n tabId = newSignalId(),\n iceServers = DEFAULT_ICE_SERVERS,\n iceTransportPolicy = 'all',\n autoReconnect = true,\n listen = true,\n RTCPeerConnectionImpl = globalThis.RTCPeerConnection,\n onChange = () => {},\n onMove = () => {},\n onEvent = () => {},\n log = () => {},\n} = {}) {\n if (!signaling?.send || !signaling?.subscribe) throw new Error('createPeerLink: signaling { send, subscribe } requerido');\n selfId = String(selfId);\n peerId = String(peerId);\n\n let listening = listen;\n let destroyed = false;\n let session = null;\n let reconnects = 0;\n let reconnectTimer = null;\n let endedSid = null; // last session that ended without the peer's bye\n const seen = new Set();\n const state = {\n phase: 'idle', role: null, sid: null, gen: 0, tabId, peerTab: null,\n ice: 'new', connection: 'new', signalingState: 'stable', gathering: 'new',\n route: null, rtt: null, statsRtt: null, reconnects: 0, restarts: 0, error: null,\n move: emptyMoveStats(),\n signals: { sent: 0, received: 0, retransmits: 0, duplicates: 0, ignored: 0, invalid: 0, tooLarge: 0, maxLength: 0 },\n events: { sent: 0, received: 0 },\n };\n\n const emit = () => { if (!destroyed) onChange(snapshot()); };\n const snapshot = () => ({ ...state, move: { ...state.move }, signals: { ...state.signals }, events: { ...state.events } });\n const setPhase = (phase, error = null) => {\n state.phase = phase;\n state.error = error;\n log(`fase: ${phase}${error ? ` (${error})` : ''}`);\n emit();\n };\n\n // ---- signaling --------------------------------------------------------\n\n const rawSend = (text) => {\n try {\n return Promise.resolve(signaling.send(text)).then(() => true, () => false);\n } catch {\n return Promise.resolve(false);\n }\n };\n\n const sendSignal = (s, t, d, { to = s?.peerTab ?? null, reliable = true, gen = s?.gen ?? 0, sid = s?.sid } = {}) => {\n const mid = newSignalId();\n const text = encodeSignal({ t, sid, gen, mid, at: Date.now(), from: tabId, to, d: d ?? undefined });\n if (!text) {\n state.signals.tooLarge += 1;\n log(`señal ${t} supera el límite y no se envía`);\n if (s) fail(s, 'signal_too_large');\n return null;\n }\n state.signals.sent += 1;\n state.signals.maxLength = Math.max(state.signals.maxLength, text.length);\n void rawSend(text);\n if (reliable && s) {\n const entry = { text, tries: 1, timer: null };\n const retry = () => {\n if (s.closed || !s.outbox.has(mid)) return;\n if (entry.tries >= MAX_TRIES) {\n s.outbox.delete(mid);\n log(`señal ${t} sin ACK tras ${MAX_TRIES} intentos`);\n if (t !== 'bye') fail(s, 'signal_timeout');\n return;\n }\n entry.tries += 1;\n state.signals.retransmits += 1;\n void rawSend(text);\n entry.timer = setTimeout(retry, RETRY_MS);\n };\n entry.timer = setTimeout(retry, RETRY_MS);\n s.outbox.set(mid, entry);\n }\n return mid;\n };\n\n const remember = (key) => {\n if (seen.has(key)) return false;\n seen.add(key);\n if (seen.size > MAX_SEEN) seen.delete(seen.values().next().value);\n return true;\n };\n\n const handleFrame = (frame) => {\n if (destroyed || !frame || !isWebrtcMessage(frame.text)) return;\n if (roomId != null && frame.roomId && String(frame.roomId) !== String(roomId)) return;\n const signal = decodeSignal(frame.text);\n if (!signal) { state.signals.invalid += 1; return; }\n // Own echoes, own other tabs and third parties never drive this link.\n if (String(frame.userId) !== peerId || signal.from === tabId || (signal.to && signal.to !== tabId)) {\n state.signals.ignored += 1;\n return;\n }\n state.signals.received += 1;\n if (signal.t !== 'hello' && signal.t !== 'ack') {\n // ACK even duplicates: the first ACK may be the one that got lost.\n sendSignal(null, 'ack', { of: signal.mid }, { to: signal.from, reliable: false, sid: signal.sid, gen: signal.gen });\n }\n if (!remember(`${signal.from}:${signal.mid}`)) { state.signals.duplicates += 1; emit(); return; }\n switch (signal.t) {\n case 'ack': onAck(signal); break;\n case 'hello': onHello(signal); break;\n case 'ready': onReady(signal); break;\n case 'offer': void onOffer(signal); break;\n case 'answer': void onAnswer(signal); break;\n case 'ice': onIce(signal); break;\n case 'bye': onBye(signal); break;\n default: break;\n }\n // After dispatch, so the frame that binds the peer tab (hello/ready) counts too.\n if (session && signal.sid === session.sid && signal.from === session.peerTab && signal.at) {\n session.peerAt = Math.max(session.peerAt, signal.at);\n }\n };\n\n const unsubscribe = signaling.subscribe(handleFrame);\n\n // ---- session lifecycle ------------------------------------------------\n\n const newSession = (role, sid = newSignalId(), peerTab = null) => ({\n role, sid, peerTab, gen: 0, pc: null, move: null, events: null, closed: false,\n outbox: new Map(), pendingIce: [], localIce: [], iceTimer: null, helloTimer: null, helloStarted: Date.now(),\n disconnectTimer: null, connectTimer: null, pingTimer: null, statsTimer: null, tickTimer: null,\n lastSeen: Date.now(), pingSeq: 0, moveSeq: 0, restarts: 0, open: 0, peerAt: 0,\n });\n\n const teardown = (s, { bye = false } = {}) => {\n if (!s || s.closed) return;\n if (bye && s.peerTab) sendSignal(s, 'bye', null, { reliable: false });\n s.closed = true;\n [s.iceTimer, s.disconnectTimer, s.connectTimer].forEach((timer) => clearTimeout(timer));\n [s.helloTimer, s.pingTimer, s.statsTimer, s.tickTimer].forEach((timer) => clearInterval(timer));\n s.outbox.forEach((entry) => clearTimeout(entry.timer));\n s.outbox.clear();\n [s.move, s.events].forEach((channel) => {\n if (!channel) return;\n channel.onopen = channel.onclose = channel.onmessage = channel.onerror = null;\n try { channel.close(); } catch { /* already closed */ }\n });\n if (s.pc) {\n s.pc.onicecandidate = s.pc.oniceconnectionstatechange = s.pc.onconnectionstatechange = null;\n s.pc.onsignalingstatechange = s.pc.onicegatheringstatechange = null;\n try { s.pc.close(); } catch { /* already closed */ }\n }\n if (session === s) session = null;\n };\n\n const scheduleReconnect = (reason) => {\n if (destroyed || !autoReconnect || reconnectTimer) return;\n if (reconnects >= MAX_RECONNECTS) { setPhase('failed', `${reason}; sin más reintentos`); return; }\n const delay = Math.min(8000, 1000 * 2 ** reconnects);\n reconnects += 1;\n state.reconnects = reconnects;\n setPhase('reconnecting', reason);\n reconnectTimer = setTimeout(() => { reconnectTimer = null; if (!destroyed && !session) call(); }, delay);\n };\n\n const fail = (s, reason) => {\n if (s.closed) return;\n const wasCaller = s.role === 'caller';\n endedSid = s.sid;\n teardown(s, { bye: true });\n if (wasCaller) scheduleReconnect(reason);\n else setPhase(listening ? 'listening' : 'idle', reason);\n };\n\n const bindState = (s) => {\n state.role = s.role;\n state.sid = s.sid;\n state.gen = s.gen;\n state.peerTab = s.peerTab;\n state.ice = 'new';\n state.connection = 'new';\n state.signalingState = 'stable';\n state.gathering = 'new';\n state.route = null;\n state.rtt = null;\n state.statsRtt = null;\n state.restarts = 0;\n state.move = emptyMoveStats();\n };\n\n // ---- peer connection --------------------------------------------------\n\n const createPc = (s) => {\n if (!RTCPeerConnectionImpl) { fail(s, 'webrtc_unavailable'); return null; }\n const pc = new RTCPeerConnectionImpl({ iceServers, iceTransportPolicy });\n s.pc = pc;\n pc.onicecandidate = ({ candidate }) => {\n if (s.closed) return;\n s.localIce.push(candidate ? candidate.toJSON?.() ?? candidate : null);\n if (!s.iceTimer) s.iceTimer = setTimeout(() => flushLocalIce(s), ICE_BATCH_MS);\n };\n pc.onicegatheringstatechange = () => { state.gathering = pc.iceGatheringState; emit(); };\n pc.onsignalingstatechange = () => { state.signalingState = pc.signalingState; emit(); };\n pc.onconnectionstatechange = () => { state.connection = pc.connectionState; emit(); };\n pc.oniceconnectionstatechange = () => onIceState(s);\n s.move = pc.createDataChannel('move', { negotiated: true, id: 0, ordered: false, maxRetransmits: 0 });\n s.events = pc.createDataChannel('events', { negotiated: true, id: 1, ordered: true });\n [s.move, s.events].forEach((channel) => {\n channel.onopen = () => onChannelOpen(s);\n channel.onclose = () => { if (!s.closed && state.phase === 'connected') lose(s, 'canal cerrado'); };\n });\n s.move.onmessage = (event) => onMoveMessage(s, event.data);\n s.events.onmessage = (event) => onEventMessage(s, event.data);\n s.connectTimer = setTimeout(() => { if (state.phase !== 'connected') fail(s, 'connect_timeout'); }, CONNECT_TIMEOUT_MS);\n return pc;\n };\n\n const flushLocalIce = (s) => {\n s.iceTimer = null;\n if (s.closed || !s.localIce.length) return;\n const batch = s.localIce.splice(0, s.localIce.length);\n for (let i = 0; i < batch.length; i += 16) sendSignal(s, 'ice', { c: batch.slice(i, i + 16) });\n };\n\n const addRemoteIce = async (s, candidates) => {\n for (const candidate of candidates) {\n try {\n if (candidate) await s.pc.addIceCandidate(candidate);\n } catch (error) {\n log(`ICE remoto rechazado: ${error?.message ?? error}`);\n }\n }\n };\n\n const flushPendingIce = (s) => {\n const ready = s.pendingIce.filter((item) => item.gen === s.gen);\n s.pendingIce = s.pendingIce.filter((item) => item.gen > s.gen);\n ready.forEach((item) => void addRemoteIce(s, item.c));\n };\n\n const makeOffer = async (s, iceRestart = false) => {\n try {\n const offer = await s.pc.createOffer(iceRestart ? { iceRestart: true } : undefined);\n if (s.closed) return;\n await s.pc.setLocalDescription(offer);\n if (s.closed) return;\n sendSignal(s, 'offer', { sdp: s.pc.localDescription.sdp });\n } catch (error) {\n fail(s, `offer: ${error?.message ?? error}`);\n }\n };\n\n const restartIce = (s) => {\n if (s.closed || s.role !== 'caller') return;\n if (s.restarts >= MAX_RESTARTS) { lose(s, 'ICE no se recupera'); return; }\n s.restarts += 1;\n state.restarts = s.restarts;\n s.gen += 1;\n state.gen = s.gen;\n setPhase('restarting');\n void makeOffer(s, true);\n };\n\n const onIceState = (s) => {\n if (s.closed) return;\n const ice = s.pc.iceConnectionState;\n state.ice = ice;\n clearTimeout(s.disconnectTimer);\n if (ice === 'connected' || ice === 'completed') {\n if (state.phase === 'restarting') setPhase('connected');\n void refreshStats(s);\n } else if (ice === 'disconnected') {\n s.disconnectTimer = setTimeout(() => restartIce(s), DISCONNECTED_GRACE_MS);\n } else if (ice === 'failed') {\n restartIce(s);\n }\n emit();\n };\n\n const onChannelOpen = (s) => {\n s.open += 1;\n if (s.open < 2 || s.closed) return;\n clearTimeout(s.connectTimer);\n reconnects = 0;\n s.lastSeen = Date.now();\n s.pingTimer = setInterval(() => ping(s), PING_EVERY_MS);\n s.statsTimer = setInterval(() => void refreshStats(s), 2000);\n s.tickTimer = setInterval(() => tick(s), 1000);\n setPhase('connected');\n void refreshStats(s);\n };\n\n // A session that stops hearing the peer is over; the caller starts a new one.\n const lose = (s, reason) => fail(s, reason);\n\n // ---- data -------------------------------------------------------------\n\n const ping = (s) => {\n if (Date.now() - s.lastSeen > HEARTBEAT_TIMEOUT_MS) { lose(s, 'sin latido del rival'); return; }\n sendRaw(s.events, JSON.stringify({ k: 'ping', n: ++s.pingSeq, ts: performance.now() }));\n };\n\n const sendRaw = (channel, text) => {\n if (channel?.readyState !== 'open') return false;\n try { channel.send(text); return true; } catch { return false; }\n };\n\n const onEventMessage = (s, data) => {\n s.lastSeen = Date.now();\n let message;\n try { message = JSON.parse(data); } catch { return; }\n if (message?.k === 'ping') sendRaw(s.events, JSON.stringify({ k: 'pong', ts: message.ts }));\n else if (message?.k === 'pong') { state.rtt = Math.round((performance.now() - message.ts) * 10) / 10; }\n else if (message?.k === 'app') { state.events.received += 1; onEvent(message.d); }\n };\n\n const onMoveMessage = (s, data) => {\n s.lastSeen = Date.now();\n let packet;\n try { packet = JSON.parse(data); } catch { return; }\n if (!Array.isArray(packet) || !Number.isInteger(packet[0])) return;\n const [seq, , payload] = packet;\n const m = state.move;\n m.received += 1;\n m.windowIn += 1;\n if (m.firstSeq == null) m.firstSeq = seq;\n if (seq < m.maxSeq) { m.outOfOrder += 1; return; } // stale position: counted, not applied\n m.maxSeq = seq;\n onMove(payload, { seq });\n };\n\n const tick = (s) => {\n const m = state.move;\n m.hzIn = m.windowIn;\n m.hzOut = m.windowOut;\n m.windowIn = 0;\n m.windowOut = 0;\n const expected = m.firstSeq == null ? 0 : m.maxSeq - m.firstSeq + 1;\n m.lossPct = expected > 0 ? Math.max(0, Math.round((1 - m.received / expected) * 1000) / 10) : 0;\n if (!s.closed) emit();\n };\n\n const refreshStats = async (s) => {\n if (s.closed || !s.pc?.getStats) return;\n try {\n const report = await s.pc.getStats();\n const byId = new Map();\n report.forEach((entry) => byId.set(entry.id, entry));\n let pair = null;\n report.forEach((entry) => {\n if (entry.type === 'transport' && entry.selectedCandidatePairId) pair = byId.get(entry.selectedCandidatePairId);\n });\n if (!pair) report.forEach((entry) => { if (entry.type === 'candidate-pair' && (entry.selected || entry.nominated) && entry.state === 'succeeded') pair = entry; });\n if (!pair) return;\n const local = byId.get(pair.localCandidateId);\n const remote = byId.get(pair.remoteCandidateId);\n const relay = local?.candidateType === 'relay' || remote?.candidateType === 'relay';\n const type = relay ? 'relay' : (local?.candidateType ?? 'host');\n state.route = {\n local: local?.candidateType ?? null,\n remote: remote?.candidateType ?? null,\n protocol: local?.protocol ?? null,\n relay,\n label: ROUTE_LABELS[type] ?? type,\n };\n if (typeof pair.currentRoundTripTime === 'number') state.statsRtt = Math.round(pair.currentRoundTripTime * 10000) / 10;\n emit();\n } catch { /* stats are optional */ }\n };\n\n // ---- signal handlers ----------------------------------------------------\n\n const onAck = (signal) => {\n const entry = session?.outbox.get(signal.d.of);\n if (!entry) return;\n clearTimeout(entry.timer);\n session.outbox.delete(signal.d.of);\n };\n\n const onHello = (signal) => {\n if (session && session.sid === signal.sid) return;\n // A late hello from a call the bound tab already dropped (glare, reorder).\n if (session && session.peerTab === signal.from && (signal.at ?? 0) <= session.peerAt) return;\n const glare = session?.role === 'caller' && !session.peerTab;\n // Both called at once: the lower tab id keeps calling, the other answers.\n if (glare && tabId < signal.from) return;\n if (!listening && !glare) { state.signals.ignored += 1; return; }\n // Otherwise the peer started over (reload, lost session): the new call replaces ours.\n if (session) teardown(session, { bye: !glare });\n const s = newSession('answerer', signal.sid, signal.from);\n session = s;\n bindState(s);\n if (!createPc(s)) return;\n sendSignal(s, 'ready', null);\n setPhase('negotiating');\n };\n\n const onReady = (signal) => {\n const s = session;\n if (!s || s.role !== 'caller' || s.sid !== signal.sid) return;\n if (s.peerTab && s.peerTab !== signal.from) {\n // Another tab of the peer answered later: release it.\n sendSignal(s, 'bye', null, { to: signal.from, reliable: false });\n return;\n }\n if (s.peerTab) return;\n s.peerTab = signal.from;\n state.peerTab = signal.from;\n clearInterval(s.helloTimer);\n if (!createPc(s)) return;\n setPhase('negotiating');\n void makeOffer(s);\n };\n\n const onOffer = async (signal) => {\n const s = session;\n if (!s || s.role !== 'answerer' || s.sid !== signal.sid || s.peerTab !== signal.from || signal.gen < s.gen) return;\n s.gen = signal.gen;\n state.gen = s.gen;\n try {\n await s.pc.setRemoteDescription({ type: 'offer', sdp: signal.d.sdp });\n if (s.closed) return;\n flushPendingIce(s);\n const answer = await s.pc.createAnswer();\n if (s.closed) return;\n await s.pc.setLocalDescription(answer);\n if (s.closed) return;\n sendSignal(s, 'answer', { sdp: s.pc.localDescription.sdp });\n } catch (error) {\n fail(s, `answer: ${error?.message ?? error}`);\n }\n };\n\n const onAnswer = async (signal) => {\n const s = session;\n if (!s || s.role !== 'caller' || s.sid !== signal.sid || s.peerTab !== signal.from || signal.gen !== s.gen) return;\n if (s.pc.signalingState !== 'have-local-offer') return;\n try {\n await s.pc.setRemoteDescription({ type: 'answer', sdp: signal.d.sdp });\n if (s.closed) return;\n flushPendingIce(s);\n // An ICE restart over a working path may never leave 'connected'.\n if (state.phase === 'restarting' && ['connected', 'completed'].includes(s.pc.iceConnectionState)) setPhase('connected');\n } catch (error) {\n fail(s, `answer remota: ${error?.message ?? error}`);\n }\n };\n\n const onIce = (signal) => {\n const s = session;\n if (!s || s.sid !== signal.sid || s.peerTab !== signal.from || signal.gen < s.gen) return;\n if (signal.gen > s.gen || !s.pc?.remoteDescription) {\n s.pendingIce.push({ gen: signal.gen, c: signal.d.c }); // arrived before its offer/answer\n return;\n }\n void addRemoteIce(s, signal.d.c);\n };\n\n const onBye = (signal) => {\n // The channel may close before the bye arrives through the chat: the peer left\n // on purpose, so the reconnection that started meanwhile is cancelled.\n if (signal.sid === endedSid && (reconnectTimer || (session?.role === 'caller' && !session.peerTab))) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n reconnects = 0;\n if (session) teardown(session);\n setPhase(listening ? 'listening' : 'idle', 'el rival cerró la conexión');\n return;\n }\n const s = session;\n if (!s || s.sid !== signal.sid) return;\n if (s.peerTab && s.peerTab !== signal.from) return;\n teardown(s);\n setPhase(listening ? 'listening' : 'idle', 'el rival cerró la conexión');\n };\n\n // ---- public API -------------------------------------------------------\n\n function call() {\n if (destroyed) return;\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n if (session) teardown(session, { bye: true });\n const s = newSession('caller');\n session = s;\n bindState(s);\n setPhase('calling');\n const hello = () => {\n if (s.closed || s.peerTab) return;\n if (Date.now() - s.helloStarted > HELLO_GIVE_UP_MS) {\n teardown(s);\n setPhase('failed', 'el rival no respondió');\n return;\n }\n sendSignal(s, 'hello', null, { to: null, reliable: false });\n };\n hello();\n s.helloTimer = setInterval(hello, HELLO_EVERY_MS);\n }\n\n if (listening) setPhase('listening');\n\n return {\n tabId,\n /** Starts a new session as caller (the peer must be listening). */\n connect() { reconnects = 0; call(); },\n /** Accept (true) or ignore (false) incoming calls. */\n setListening(value) {\n listening = Boolean(value);\n if (!session) setPhase(listening ? 'listening' : 'idle');\n },\n /** Latest-wins position update over the unreliable channel. */\n sendMove(payload) {\n const s = session;\n if (!s || s.move?.readyState !== 'open') return false;\n if (s.move.bufferedAmount > MAX_BUFFERED) { state.move.dropped += 1; return false; }\n const sent = sendRaw(s.move, JSON.stringify([++s.moveSeq, Date.now(), payload]));\n if (sent) { state.move.sent += 1; state.move.windowOut += 1; }\n return sent;\n },\n /** Reliable, ordered game event. */\n sendEvent(payload) {\n const sent = sendRaw(session?.events, JSON.stringify({ k: 'app', d: payload }));\n if (sent) state.events.sent += 1;\n return sent;\n },\n /** Forces an ICE restart (caller) — for tests. */\n restartIce() { if (session) restartIce(session); },\n getState: snapshot,\n /** Ends the session (bye to the peer) but keeps listening. */\n hangup() {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n if (session) teardown(session, { bye: true });\n setPhase(listening ? 'listening' : 'idle');\n },\n /** Full cleanup: session, timers and the signaling subscription. */\n destroy() {\n if (destroyed) return;\n clearTimeout(reconnectTimer);\n if (session) teardown(session, { bye: true });\n destroyed = true;\n unsubscribe?.();\n },\n };\n}\n\nexport default createPeerLink;\n"],"mappings":"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"}