@memizy/host-sdk 1.0.0-rc.5 → 1.0.0-rc.6

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.
@@ -1,8 +1,9 @@
1
1
  import { RelaySocket as e, relayWebSocketUrl as t } from "./socket.js";
2
- import { blobToJson as n, serializeError as r } from "./messages.js";
3
- import { HOST_API_METHODS as i, PLUGIN_API_METHODS as a, RELAY_PROTOCOL as o } from "@memizy/protocol";
2
+ import { blobToJson as n, serializeError as r, unknownMethod as i } from "./messages.js";
3
+ import { HOST_API_METHODS as a, PLUGIN_API_METHODS as o, RELAY_PROTOCOL as s } from "@memizy/protocol";
4
+ import { RELAY_LIMITS as c, jsonByteSize as l } from "@memizy/protocol/internal";
4
5
  //#region src/relay/host.ts
5
- var s = new Set(i), c = class i {
6
+ var u = new Set(a), d = class a {
6
7
  pin;
7
8
  hostToken;
8
9
  players = [];
@@ -23,12 +24,15 @@ var s = new Set(i), c = class i {
23
24
  hosting = !1;
24
25
  hostingWaiters = [];
25
26
  stopSession = null;
27
+ seatOf = /* @__PURE__ */ new Map();
28
+ seatsMode = !1;
29
+ appVersions = /* @__PURE__ */ new Map();
26
30
  constructor(n, r) {
27
31
  this.options = n, this.pin = r.pin, this.hostToken = r.hostToken, this.socket = new e({
28
32
  url: t(n.serverUrl),
29
33
  attach: () => ({
30
34
  t: "host",
31
- protocol: o,
35
+ protocol: s,
32
36
  pin: this.pin,
33
37
  hostToken: this.hostToken
34
38
  }),
@@ -41,10 +45,10 @@ var s = new Set(i), c = class i {
41
45
  static async create(e) {
42
46
  let t = await (e.fetch ?? fetch)(new URL("/api/rooms", e.serverUrl), { method: "POST" });
43
47
  if (!t.ok) throw Error(`Could not create a room (${t.status}): ${await t.text()}`);
44
- return new i(e, await t.json());
48
+ return new a(e, await t.json());
45
49
  }
46
- static resume(e, t) {
47
- return new i(e, t);
50
+ static rejoin(e, t) {
51
+ return new a(e, t);
48
52
  }
49
53
  whenHosting() {
50
54
  return this.hosting ? Promise.resolve() : new Promise((e) => this.hostingWaiters.push(e));
@@ -59,7 +63,7 @@ var s = new Set(i), c = class i {
59
63
  for (let t of this.listeners) t(e);
60
64
  }
61
65
  async uploadBundle(e, t) {
62
- let n = await (this.options.fetch ?? fetch)(new URL(`/api/rooms/${this.pin}/bundle`, this.options.serverUrl), {
66
+ let n = t.publicSet, r = await (this.options.fetch ?? fetch)(new URL(`/api/rooms/${this.pin}/bundle`, this.options.serverUrl), {
63
67
  method: "PUT",
64
68
  headers: {
65
69
  Authorization: `Bearer ${this.hostToken}`,
@@ -67,11 +71,11 @@ var s = new Set(i), c = class i {
67
71
  },
68
72
  body: JSON.stringify({
69
73
  pluginHtml: e,
70
- set: t
74
+ set: n
71
75
  })
72
76
  });
73
- if (!n.ok) throw Error(`Could not upload the game (${n.status}): ${await n.text()}`);
74
- return this.bundleVersion = (await n.json()).version, this.bundleVersion;
77
+ if (!r.ok) throw Error(`Could not upload the game (${r.status}): ${await r.text()}`);
78
+ return this.bundleVersion = (await r.json()).version, this.bundleVersion;
75
79
  }
76
80
  setOpen(e) {
77
81
  this.socket.send({
@@ -100,24 +104,135 @@ var s = new Set(i), c = class i {
100
104
  state: this.state
101
105
  });
102
106
  }
103
- attach(e) {
104
- this.detach(), this.session = e;
105
- let t = new Set(this.players.map((e) => e.id));
106
- for (let t of this.players) !t.connected && e.players.some((e) => e.id === t.id) && e.setConnected(t.id, !1);
107
- for (let n of e.players) t.has(n.id) && this.connectRemote(n.id);
108
- this.stopSession = e.on((e) => this.onSessionEvent(e)), this.setState({
107
+ attach(e, t = {}) {
108
+ this.detach(), this.session = e, this.seatsMode = t.seats === !0;
109
+ let n = (t) => e.players.some((e) => e.id === t && !e.isHost), r = (e) => this.players.some((t) => t.id === e);
110
+ if (this.seatsMode) for (let [e, i] of Object.entries(t.taken ?? {})) r(e) && n(i) && this.relayIdOf(i) === null && this.seatOf.set(e, i);
111
+ else for (let e of this.players) n(e.id) && this.seatOf.set(e.id, e.id);
112
+ for (let t of e.players) {
113
+ if (t.isHost) continue;
114
+ let n = this.relayIdOf(t.id);
115
+ this.players.find((e) => e.id === n)?.connected || e.setConnected(t.id, !1);
116
+ }
117
+ for (let e of this.seatOf.values()) this.connectRemote(e);
118
+ if (this.stopSession = e.on((e) => this.onSessionEvent(e)), this.setState({
109
119
  phase: "loading",
110
120
  appName: e.plugin.manifest.appName,
111
- players: e.players,
121
+ players: [...e.players],
112
122
  authorityConnected: !0,
113
123
  result: void 0,
114
- countdown: void 0
115
- });
124
+ countdown: void 0,
125
+ ...this.seatState()
126
+ }), this.seatsMode) for (let e of this.players) e.connected && this.sendSeat(e.id);
116
127
  }
117
128
  detach() {
118
129
  this.stopSession?.(), this.stopSession = null, this.session = null;
119
130
  for (let [e, t] of this.channels) this.resetChannel(e, t);
120
- this.broadcast({ k: "unmount" });
131
+ this.seatOf.clear(), this.seatsMode && (this.seatsMode = !1, this.state = {
132
+ ...this.state,
133
+ seats: void 0,
134
+ newPlayers: void 0
135
+ }, this.broadcast({
136
+ k: "state",
137
+ state: this.state
138
+ })), this.broadcast({ k: "unmount" });
139
+ }
140
+ get seats() {
141
+ let e = this.session;
142
+ return !this.seatsMode || !e ? [] : e.players.filter((e) => !e.isHost).map((e) => ({
143
+ id: e.id,
144
+ name: e.name,
145
+ roomPlayerId: this.relayIdOf(e.id)
146
+ }));
147
+ }
148
+ get seatAssignments() {
149
+ return Object.fromEntries(this.seatOf);
150
+ }
151
+ assignSeat(e, t) {
152
+ let n = this.session;
153
+ if (!this.seatsMode || !n) throw Error("Places can be assigned only in a continued game.");
154
+ if (!this.players.some((t) => t.id === e)) throw Error(`Unknown room player "${e}".`);
155
+ if (t !== null && !n.players.some((e) => e.id === t && !e.isHost)) throw Error(`Unknown place "${t}".`);
156
+ if ((this.seatOf.get(e) ?? null) !== t) {
157
+ if (this.unseat(e), t !== null) {
158
+ let n = this.relayIdOf(t);
159
+ n !== null && this.unseat(n), this.seat(e, t);
160
+ }
161
+ this.seatsChanged();
162
+ }
163
+ }
164
+ appVersionOf(e) {
165
+ return this.appVersions.get(e) ?? null;
166
+ }
167
+ relayIdOf(e) {
168
+ for (let [t, n] of this.seatOf) if (n === e) return t;
169
+ return null;
170
+ }
171
+ seatState() {
172
+ let e = this.session;
173
+ if (!this.seatsMode || !e) return {
174
+ seats: void 0,
175
+ newPlayers: void 0
176
+ };
177
+ let t = e.plugin.runtime.multiplayer;
178
+ return {
179
+ seats: this.seats.map((e) => ({
180
+ id: e.id,
181
+ name: e.name,
182
+ taken: e.roomPlayerId !== null
183
+ })),
184
+ newPlayers: !e.ended && !!t?.lateJoin && e.players.length < t.players.max
185
+ };
186
+ }
187
+ seatsChanged() {
188
+ this.seatsMode && (this.setState(this.seatState()), this.emit({
189
+ type: "seats",
190
+ seats: this.seats
191
+ }));
192
+ }
193
+ sendSeat(e) {
194
+ let t = this.seatOf.get(e) ?? null, n = t === null ? void 0 : this.session?.players.find((e) => e.id === t)?.name;
195
+ this.sendTo(e, {
196
+ k: "seat",
197
+ seat: t,
198
+ ...n !== void 0 && { name: n }
199
+ });
200
+ }
201
+ seat(e, t) {
202
+ this.seatOf.set(e, t);
203
+ let n = this.players.find((t) => t.id === e)?.connected ?? !1;
204
+ this.session?.setConnected(t, n), n && this.connectRemote(t), this.sendSeat(e);
205
+ }
206
+ unseat(e) {
207
+ let t = this.seatOf.get(e);
208
+ if (t === void 0) return;
209
+ this.seatOf.delete(e);
210
+ let n = this.channels.get(t);
211
+ n && this.resetChannel(t, n), this.channels.delete(t), this.session?.disconnect(t), this.session?.setConnected(t, !1), this.sendTo(e, { k: "unmount" }), this.sendSeat(e);
212
+ }
213
+ claim(e, t) {
214
+ let n = this.session, r = this.players.find((t) => t.id === e);
215
+ if (!this.seatsMode || !n || n.ended || !r || this.seatOf.has(e)) {
216
+ this.sendSeat(e);
217
+ return;
218
+ }
219
+ if (t === null) {
220
+ try {
221
+ n.addPlayer({
222
+ id: e,
223
+ name: r.name
224
+ });
225
+ } catch {
226
+ this.sendSeat(e);
227
+ return;
228
+ }
229
+ this.seat(e, e);
230
+ } else if (typeof t == "string" && n.players.some((e) => e.id === t && !e.isHost) && this.relayIdOf(t) === null) this.seat(e, t);
231
+ else {
232
+ this.sendSeat(e);
233
+ return;
234
+ }
235
+ this.seatsChanged();
121
236
  }
122
237
  onSessionEvent(e) {
123
238
  switch (e.type) {
@@ -134,12 +249,18 @@ var s = new Set(i), c = class i {
134
249
  });
135
250
  break;
136
251
  case "players":
137
- this.setState({ players: e.players });
252
+ this.setState({
253
+ players: e.players,
254
+ ...this.seatState()
255
+ }), this.seatsMode && this.emit({
256
+ type: "seats",
257
+ seats: this.seats
258
+ });
138
259
  break;
139
260
  case "authority":
140
261
  this.setState({ authorityConnected: e.connected });
141
262
  break;
142
- case "ended": this.setState({
263
+ case "result": this.setState({
143
264
  phase: "ended",
144
265
  result: e.result
145
266
  });
@@ -165,34 +286,36 @@ var s = new Set(i), c = class i {
165
286
  });
166
287
  }
167
288
  sendMount(e, t) {
168
- this.sendTo(e, {
289
+ this.sendToSeat(e, {
169
290
  k: "mount",
170
291
  gen: t,
171
292
  bundle: this.bundleVersion,
172
- locale: this.session?.config.locale ?? "cs"
293
+ locale: this.session?.config.locale ?? "en",
294
+ v: 1
173
295
  });
174
296
  }
175
297
  remotePlugin(e, t) {
176
298
  let n = {};
177
- for (let r of a) n[r] = (...n) => this.callRemote(e, t, r, n);
299
+ for (let r of o) n[r] = (...n) => this.callRemote(e, t, r, n);
178
300
  return n;
179
301
  }
180
302
  callRemote(e, t, n, r) {
181
303
  let i = this.channels.get(e);
182
304
  if (!i || i.gen !== t) return Promise.reject(/* @__PURE__ */ Error("[SESSION_ENDED] The player instance was replaced."));
183
- if (!this.players.find((t) => t.id === e)?.connected) return Promise.resolve();
184
- let a = ++this.callCounter;
185
- return new Promise((o, s) => {
305
+ let a = this.relayIdOf(e);
306
+ if (!this.players.find((e) => e.id === a)?.connected) return Promise.resolve();
307
+ let o = ++this.callCounter;
308
+ return new Promise((a, s) => {
186
309
  let c = setTimeout(() => {
187
- i.calls.delete(a), s(/* @__PURE__ */ Error(`[INTERNAL_ERROR] ${n} on ${e} timed out.`));
310
+ i.calls.delete(o), s(/* @__PURE__ */ Error(`[INTERNAL_ERROR] ${n} on ${e} timed out.`));
188
311
  }, this.options.callTimeoutMs ?? 15e3);
189
- i.calls.set(a, {
190
- resolve: o,
312
+ i.calls.set(o, {
313
+ resolve: a,
191
314
  reject: s,
192
315
  timer: c
193
- }), this.sendTo(e, {
316
+ }), this.sendToSeat(e, {
194
317
  k: "call",
195
- id: a,
318
+ id: o,
196
319
  gen: t,
197
320
  method: n,
198
321
  args: r
@@ -250,70 +373,100 @@ var s = new Set(i), c = class i {
250
373
  type: "players",
251
374
  players: this.players
252
375
  });
253
- let n = this.session, r = n?.players.some((t) => t.id === e.id) ?? !1;
376
+ let n = this.session, r = this.seatOf.get(e.id) ?? null;
254
377
  switch (t) {
255
378
  case "join":
256
- if (n && !n.ended && !r) try {
379
+ if (n && !n.ended && r === null && !this.seatsMode) try {
257
380
  n.addPlayer({
258
381
  id: e.id,
259
382
  name: e.name
260
- }), this.connectRemote(e.id);
383
+ }), this.seatOf.set(e.id, e.id), this.connectRemote(e.id);
261
384
  } catch {}
262
385
  break;
263
386
  case "disconnect": {
264
- let t = this.channels.get(e.id);
265
- t && this.settleCalls(t), r && n.setConnected(e.id, !1);
387
+ let e = r === null ? void 0 : this.channels.get(r);
388
+ e && this.settleCalls(e), n && r !== null && n.setConnected(r, !1);
266
389
  break;
267
390
  }
268
391
  case "rename":
269
- r && n.renamePlayer(e.id, e.name);
392
+ n && r !== null && !this.seatsMode && n.renamePlayer(r, e.name);
270
393
  break;
271
- case "leave": if (this.channels.delete(e.id), r) try {
272
- n.removePlayer(e.id);
273
- } catch {}
394
+ case "leave": if (this.appVersions.delete(e.id), r !== null && this.seatsMode) this.unseat(e.id), this.seatsChanged();
395
+ else if (r !== null) {
396
+ this.seatOf.delete(e.id), this.channels.delete(r);
397
+ try {
398
+ n?.removePlayer(r);
399
+ } catch {}
400
+ }
274
401
  }
275
402
  }
276
403
  async onPlayer(e, t) {
277
404
  if (typeof t != "object" || !t) return;
278
- let i = this.channel(e);
279
- switch (t.k) {
280
- case "hello": {
405
+ let a = this.seatOf.get(e) ?? null;
406
+ if (t.k === "hello") this.appVersions.set(e, typeof t.v == "number" && Number.isFinite(t.v) ? t.v : 0), this.sendTo(e, {
407
+ k: "state",
408
+ state: this.state
409
+ }), this.seatsMode && this.sendSeat(e);
410
+ else if (t.k === "claim") {
411
+ this.claim(e, t.seat);
412
+ return;
413
+ }
414
+ if (a === null) {
415
+ if (t.k === "call") {
416
+ let n = u.has(t.method) ? { message: "[SESSION_ENDED] This device has no place in the game." } : i(t.method);
281
417
  this.sendTo(e, {
282
- k: "state",
283
- state: this.state
418
+ k: "result",
419
+ id: t.id,
420
+ ok: !1,
421
+ error: n
284
422
  });
285
- let n = this.session;
286
- if (!n || !n.players.some((t) => t.id === e)) return;
287
- if (i.pendingMount) {
288
- this.sendMount(e, i.pendingMount.gen);
423
+ }
424
+ return;
425
+ }
426
+ let o = this.channel(a);
427
+ switch (t.k) {
428
+ case "hello": {
429
+ let e = this.session;
430
+ if (!e || !e.players.some((e) => e.id === a)) return;
431
+ if (o.pendingMount) {
432
+ this.sendMount(a, o.pendingMount.gen);
289
433
  return;
290
434
  }
291
- n.setConnected(e, !0), (i.gen === 0 || t.gen !== i.gen) && (n.getInstance(e) ? n.reload(e).catch(() => {}) : this.connectRemote(e));
435
+ e.setConnected(a, !0), (o.gen === 0 || t.gen !== o.gen) && (e.getInstance(a) ? e.reload(a).catch(() => {}) : this.connectRemote(a));
292
436
  return;
293
437
  }
294
438
  case "mounted":
295
- if (i.pendingMount?.gen === t.gen) {
296
- clearTimeout(i.pendingMount.timer);
297
- let e = i.pendingMount;
298
- i.pendingMount = null, e.resolve();
439
+ if (o.pendingMount?.gen === t.gen) {
440
+ clearTimeout(o.pendingMount.timer);
441
+ let e = o.pendingMount;
442
+ o.pendingMount = null, e.resolve();
299
443
  }
300
444
  return;
301
445
  case "mount-failed":
302
- if (i.pendingMount?.gen === t.gen) {
303
- clearTimeout(i.pendingMount.timer);
304
- let e = i.pendingMount;
305
- i.pendingMount = null, e.reject(Error(t.error?.message ?? "The game could not be loaded on the device."));
446
+ if (o.pendingMount?.gen === t.gen) {
447
+ clearTimeout(o.pendingMount.timer);
448
+ let e = o.pendingMount;
449
+ o.pendingMount = null, e.reject(Error(t.error?.message ?? "The game could not be loaded on the device."));
306
450
  }
307
451
  return;
308
452
  case "result": {
309
- let e = i.calls.get(t.id);
453
+ let e = o.calls.get(t.id);
310
454
  if (!e) return;
311
- i.calls.delete(t.id), clearTimeout(e.timer), t.ok ? e.resolve(t.value) : e.reject(Error(t.error?.message ?? "Remote call failed"));
455
+ o.calls.delete(t.id), clearTimeout(e.timer), t.ok ? e.resolve(t.value) : e.reject(Error(t.error?.message ?? "Remote call failed"));
312
456
  return;
313
457
  }
314
458
  case "call": {
315
459
  let a = (t) => this.sendTo(e, t);
316
- if (t.gen !== i.gen || !i.hostApi || !s.has(t.method) || !Array.isArray(t.args)) {
460
+ if (!u.has(t.method)) {
461
+ a({
462
+ k: "result",
463
+ id: t.id,
464
+ ok: !1,
465
+ error: i(t.method)
466
+ });
467
+ return;
468
+ }
469
+ if (t.gen !== o.gen || !o.hostApi || !Array.isArray(t.args)) {
317
470
  a({
318
471
  k: "result",
319
472
  id: t.id,
@@ -323,26 +476,41 @@ var s = new Set(i), c = class i {
323
476
  return;
324
477
  }
325
478
  try {
326
- let e = i.hostApi[t.method], r = await e(...t.args);
479
+ let r = o.hostApi[t.method], i = await r(...t.args);
327
480
  if (t.method === "hello") {
328
- r = {
329
- ...r,
481
+ i = {
482
+ ...i,
330
483
  set: null
331
484
  }, a({
332
485
  k: "result",
333
486
  id: t.id,
334
487
  ok: !0,
335
- value: r,
488
+ value: i,
336
489
  hostNow: Date.now()
337
490
  });
338
491
  return;
339
492
  }
340
- t.method === "getAsset" && r instanceof Blob && (r = await n(r)), a({
493
+ t.method === "getAsset" && i instanceof Blob && (i = await n(i));
494
+ let s = {
341
495
  k: "result",
342
496
  id: t.id,
343
497
  ok: !0,
344
- value: r ?? null
345
- });
498
+ value: i ?? null
499
+ };
500
+ if (l({
501
+ t: "to",
502
+ to: e,
503
+ data: s
504
+ }) > c.frameBytes) {
505
+ a({
506
+ k: "result",
507
+ id: t.id,
508
+ ok: !1,
509
+ error: { message: `[DATA_TOO_LARGE] The result of ${t.method} is too large to send to the device.` }
510
+ });
511
+ return;
512
+ }
513
+ a(s);
346
514
  } catch (e) {
347
515
  a({
348
516
  k: "result",
@@ -362,9 +530,13 @@ var s = new Set(i), c = class i {
362
530
  data: t
363
531
  });
364
532
  }
533
+ sendToSeat(e, t) {
534
+ let n = this.relayIdOf(e);
535
+ n !== null && this.sendTo(n, t);
536
+ }
365
537
  broadcast(e) {
366
538
  for (let t of this.players) t.connected && this.sendTo(t.id, e);
367
539
  }
368
540
  };
369
541
  //#endregion
370
- export { c as RelayHost };
542
+ export { d as RelayHost };
@@ -1 +1 @@
1
- function e(e){return{message:e instanceof Error?e.message:String(e)}}async function t(e){let t=new Uint8Array(await e.arrayBuffer()),n=``;for(let e=0;e<t.length;e+=32768)n+=String.fromCharCode(...t.subarray(e,e+32768));return{$blob:btoa(n),type:e.type}}function n(e){let t=atob(e.$blob),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return new Blob([n],{type:e.type})}function r(e){return typeof e==`object`&&!!e&&typeof e.$blob==`string`}exports.blobToJson=t,exports.isSerializedBlob=r,exports.jsonToBlob=n,exports.serializeError=e;
1
+ var e=1;function t(e){let t=e instanceof Error?e.message:String(e);return e?.code===`METHOD_NOT_FOUND`?{message:`[UNKNOWN_METHOD] ${t}`}:{message:t}}function n(e){return{message:`[UNKNOWN_METHOD] This app does not know the method "${String(e).slice(0,64)}".`}}async function r(e){let t=new Uint8Array(await e.arrayBuffer()),n=``;for(let e=0;e<t.length;e+=32768)n+=String.fromCharCode(...t.subarray(e,e+32768));return{$blob:btoa(n),type:e.type}}function i(e){let t=atob(e.$blob),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return new Blob([n],{type:e.type})}function a(e){return typeof e==`object`&&!!e&&typeof e.$blob==`string`}exports.RELAY_APP_VERSION=e,exports.blobToJson=r,exports.isSerializedBlob=a,exports.jsonToBlob=i,exports.serializeError=t,exports.unknownMethod=n;
@@ -1,8 +1,13 @@
1
1
  //#region src/relay/messages.ts
2
- function e(e) {
3
- return { message: e instanceof Error ? e.message : String(e) };
2
+ var e = 1;
3
+ function t(e) {
4
+ let t = e instanceof Error ? e.message : String(e);
5
+ return e?.code === "METHOD_NOT_FOUND" ? { message: `[UNKNOWN_METHOD] ${t}` } : { message: t };
4
6
  }
5
- async function t(e) {
7
+ function n(e) {
8
+ return { message: `[UNKNOWN_METHOD] This app does not know the method "${String(e).slice(0, 64)}".` };
9
+ }
10
+ async function r(e) {
6
11
  let t = new Uint8Array(await e.arrayBuffer()), n = "";
7
12
  for (let e = 0; e < t.length; e += 32768) n += String.fromCharCode(...t.subarray(e, e + 32768));
8
13
  return {
@@ -10,13 +15,13 @@ async function t(e) {
10
15
  type: e.type
11
16
  };
12
17
  }
13
- function n(e) {
18
+ function i(e) {
14
19
  let t = atob(e.$blob), n = new Uint8Array(t.length);
15
20
  for (let e = 0; e < t.length; e++) n[e] = t.charCodeAt(e);
16
21
  return new Blob([n], { type: e.type });
17
22
  }
18
- function r(e) {
23
+ function a(e) {
19
24
  return typeof e == "object" && !!e && typeof e.$blob == "string";
20
25
  }
21
26
  //#endregion
22
- export { t as blobToJson, r as isSerializedBlob, n as jsonToBlob, e as serializeError };
27
+ export { e as RELAY_APP_VERSION, r as blobToJson, a as isSerializedBlob, i as jsonToBlob, t as serializeError, n as unknownMethod };
@@ -1 +1 @@
1
- const e=require("../frame.cjs"),t=require("./socket.cjs"),n=require("./messages.cjs");let r=require("@memizy/protocol");var i=new Set(r.PLUGIN_API_METHODS),a=class{playerId=null;token;name;state=null;hostConnected=!0;options;socket;listeners=new Set;bundle=null;frame=null;plugin=null;gen=0;callCounter=0;pending=new Map;skewMs=0;hostConfig=null;mountQueue=Promise.resolve();constructor(e){this.options=e,this.token=e.token??null,this.name=e.name,this.socket=new t.RelaySocket({url:t.relayWebSocketUrl(e.serverUrl),attach:()=>({t:`join`,protocol:r.RELAY_PROTOCOL,pin:e.pin,name:this.name,token:this.token??void 0}),WebSocket:e.WebSocket}),this.socket.onStatus(e=>this.emit({type:`status`,status:e})),this.socket.onMessage(e=>this.onServer(e))}get status(){return this.socket.status}on(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(e){for(let t of this.listeners)t(e)}updateConfig(){this.plugin&&this.hostConfig&&this.plugin.configChanged(this.localConfig(this.hostConfig)).catch(()=>{})}localConfig(e){return{...e,...this.options.config?.()}}rename(e){this.name=e,this.socket.send({t:`rename`,name:e})}reconnectNow(){this.socket.reconnectNow()}leave(){this.destroyFrame(),this.socket.close()}onServer(e){switch(e.t){case`joined`:this.playerId=e.playerId,this.token=e.token,this.name=e.name,this.hostConnected=e.hostConnected,this.emit({type:`joined`,playerId:e.playerId,token:e.token,name:e.name}),this.up({k:`hello`,gen:this.plugin?this.gen:0});break;case`host-presence`:this.hostConnected=e.connected,this.emit({type:`host`,connected:e.connected}),e.connected&&this.up({k:`hello`,gen:this.plugin?this.gen:0});break;case`data`:this.onHost(e.data);break;case`error`:this.emit({type:`error`,code:e.code,message:e.message,fatal:e.fatal});break;case`closed`:this.destroyFrame(),this.emit({type:`closed`,reason:e.reason})}}up(e){return this.socket.send({t:`up`,data:e})}async onHost(e){if(typeof e==`object`&&e)switch(e.k){case`state`:this.state=e.state,this.emit({type:`state`,state:e.state});return;case`mount`:this.mountQueue=this.mountQueue.then(()=>this.mount(e.gen,e.bundle));return;case`unmount`:this.destroyFrame();return;case`result`:{let t=this.pending.get(e.id);if(!t)return;if(this.pending.delete(e.id),clearTimeout(t.timer),!e.ok){t.reject(Error(e.error?.message??`Host call failed`));return}let r=e.value;if(t.method===`hello`&&this.bundle){if(e.hostNow!==void 0){let n=Date.now();this.skewMs=e.hostNow+(n-t.sentAt)/2-n}let n=r;this.hostConfig=n.config,r={...n,set:structuredClone(this.bundle.data.set),clock:{...n.clock,offsetMs:n.clock.offsetMs+this.skewMs},config:this.localConfig(n.config)}}else n.isSerializedBlob(r)&&(r=n.jsonToBlob(r));t.resolve(r);return}case`call`:{let t=e=>this.up(e);if(e.gen!==this.gen||!this.plugin||!i.has(e.method)){t({k:`result`,id:e.id,ok:!1,error:{message:`[SESSION_ENDED] No active plugin instance.`}});return}let r=e.args;if(e.method===`configChanged`&&(this.hostConfig=r[0],r=[this.localConfig(this.hostConfig)]),e.method===`clockChanged`){let e=r[0];r=[{...e,offsetMs:e.offsetMs+this.skewMs}]}try{let n=this.plugin[e.method],i=await n(...r);t({k:`result`,id:e.id,ok:!0,value:i??null})}catch(r){t({k:`result`,id:e.id,ok:!1,error:n.serializeError(r)})}return}}}async ensureBundle(e){if(this.bundle?.version===e)return this.bundle.data;let t=await(this.options.fetch??fetch)(new URL(`/api/rooms/${this.options.pin}/bundle`,this.options.serverUrl));if(!t.ok)throw Error(`Could not download the game (${t.status}).`);let n=await t.json();return this.bundle={version:Number(t.headers.get(`X-Bundle-Version`)??e),data:n},n}async mount(t,i){this.destroyFrame(),this.gen=t;try{let n=await this.ensureBundle(i);if(this.gen!==t)return;let a=this.options.container();if(!a)throw Error(`No place for the game on this page.`);let o=(0,r.extractManifestFromHtml)(n.pluginHtml),s=o.success?String(o.data.appName??`Game`):`Game`,c=this.options.transformHtml?this.options.transformHtml(n.pluginHtml):n.pluginHtml,l=(this.options.createFrame??e.createPluginFrame)(c,s,this.hostApiFor(t),a.ownerDocument);this.frame=l,a.replaceChildren(l.iframe),this.emit({type:`frame`,active:!0});let u=await l.plugin;if(this.gen!==t)return;this.plugin=u,this.up({k:`mounted`,gen:t})}catch(e){if(this.gen!==t)return;this.emit({type:`error`,code:`MOUNT_FAILED`,message:e.message,fatal:!1}),this.up({k:`mount-failed`,gen:t,error:n.serializeError(e)})}}hostApiFor(e){let t={};for(let n of r.HOST_API_METHODS)t[n]=(...t)=>{if(this.gen!==e)return Promise.reject(Error(`[SESSION_ENDED] This plugin instance was replaced.`));let r=++this.callCounter;return new Promise((i,a)=>{let o=setTimeout(()=>{this.pending.delete(r),a(Error(`[INTERNAL_ERROR] The host did not answer ${n} in time.`))},this.options.callTimeoutMs??15e3);this.pending.set(r,{method:n,sentAt:Date.now(),resolve:i,reject:a,timer:o}),this.up({k:`call`,id:r,gen:e,method:n,args:t})||(clearTimeout(o),this.pending.delete(r),n===`hello`?a(Error(`[INTERNAL_ERROR] Offline.`)):i(void 0))})};return t}destroyFrame(){let e=this.frame!==null;this.frame?.destroy(),this.frame?.iframe.remove(),this.frame=null,this.plugin=null,this.gen=0;for(let e of this.pending.values())clearTimeout(e.timer),e.reject(Error(`[SESSION_ENDED] The plugin instance was closed.`));this.pending.clear(),e&&this.emit({type:`frame`,active:!1})}};exports.RelayPlayer=a;
1
+ const e=require("../frame.cjs"),t=require("./socket.cjs"),n=require("./messages.cjs");let r=require("@memizy/protocol");var i=new Set(r.PLUGIN_API_METHODS),a=1e3,o=class{playerId=null;token;name;state=null;hostConnected=!0;hostAppVersion=null;seat=void 0;options;socket;listeners=new Set;bundle=null;frame=null;plugin=null;gen=0;mountGen=0;callCounter=0;pending=new Map;skewMs=0;hostConfig=null;mountQueue=Promise.resolve();sessionEndedSent=!1;constructor(e){this.options=e,this.token=e.token??null,this.name=e.name,this.socket=new t.RelaySocket({url:t.relayWebSocketUrl(e.serverUrl),attach:()=>({t:`join`,protocol:r.RELAY_PROTOCOL,pin:e.pin,name:this.name,token:this.token??void 0,...e.auth!==void 0&&{auth:e.auth}}),WebSocket:e.WebSocket}),this.socket.onStatus(e=>this.emit({type:`status`,status:e})),this.socket.onMessage(e=>this.onServer(e))}get status(){return this.socket.status}on(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(e){for(let t of this.listeners)t(e)}refreshConfig(){this.plugin&&this.hostConfig&&this.plugin.configChanged(this.localConfig(this.hostConfig)).catch(()=>{})}localConfig(e){return{...e,...this.options.config?.()}}rename(e){this.name=e,this.socket.send({t:`rename`,name:e})}claimSeat(e){this.up({k:`claim`,seat:e})}reconnectNow(){this.socket.reconnectNow()}leave(){this.destroyFrame(),this.socket.close()}onServer(e){switch(e.t){case`joined`:this.playerId=e.playerId,this.token=e.token,this.name=e.name,this.hostConnected=e.hostConnected,this.emit({type:`joined`,playerId:e.playerId,token:e.token,name:e.name}),this.up({k:`hello`,gen:this.plugin?this.gen:0,v:1});break;case`host-presence`:this.hostConnected=e.connected,this.emit({type:`host`,connected:e.connected}),e.connected&&this.up({k:`hello`,gen:this.plugin?this.gen:0,v:1});break;case`data`:this.onHost(e.data);break;case`error`:this.emit({type:`error`,code:e.code,message:e.message,fatal:e.fatal});break;case`closed`:{let t=e.reason===`kicked`?`kicked`:e.reason===`host`?`closed`:`host-left`;this.mountQueue=this.mountQueue.then(async()=>{await this.endGame(t),this.emit({type:`closed`,reason:e.reason})});break}}}up(e){return this.socket.send({t:`up`,data:e})}async onHost(e){if(typeof e==`object`&&e)switch(e.k){case`state`:e.state.seats||(this.seat=void 0),this.state=e.state,this.emit({type:`state`,state:e.state});return;case`mount`:if(this.hostAppVersion=typeof e.v==`number`?e.v:0,e.gen===this.mountGen){this.mountQueue=this.mountQueue.then(()=>{this.plugin&&this.gen===e.gen&&this.up({k:`mounted`,gen:e.gen})});return}this.mountGen=e.gen,this.mountQueue=this.mountQueue.then(()=>this.mount(e.gen,e.bundle));return;case`unmount`:this.mountGen=0,this.mountQueue=this.mountQueue.then(()=>this.endGame(`closed`));return;case`seat`:if(e.seat!==null&&typeof e.seat!=`string`)return;this.seat=e.seat,e.seat!==null&&typeof e.name==`string`&&e.name&&e.name!==this.name&&this.rename(e.name),this.emit({type:`seat`,seat:e.seat});return;case`result`:{let t=this.pending.get(e.id);if(!t)return;if(this.pending.delete(e.id),clearTimeout(t.timer),!e.ok){t.reject(Error(e.error?.message??`Host call failed`));return}let r=e.value;if(t.method===`hello`&&this.bundle){if(e.hostNow!==void 0){let n=Date.now();this.skewMs=e.hostNow+(n-t.sentAt)/2-n}let n=r;this.hostConfig=n.config,r={...n,set:structuredClone(this.bundle.data.set),clock:{...n.clock,offsetMs:n.clock.offsetMs+this.skewMs},config:this.localConfig(n.config)}}else n.isSerializedBlob(r)&&(r=n.jsonToBlob(r));t.resolve(r);return}case`call`:{let t=e=>this.up(e);if(!i.has(e.method)){t({k:`result`,id:e.id,ok:!1,error:n.unknownMethod(e.method)});return}if(e.gen!==this.gen||!this.plugin){t({k:`result`,id:e.id,ok:!1,error:{message:`[SESSION_ENDED] No active plugin instance.`}});return}let r=e.args;if(e.method===`sessionEnded`&&(this.sessionEndedSent=!0),e.method===`configChanged`&&(this.hostConfig=r[0],r=[this.localConfig(this.hostConfig)]),e.method===`clockChanged`){let e=r[0];r=[{...e,offsetMs:e.offsetMs+this.skewMs}]}try{let n=this.plugin[e.method],i=await n(...r);t({k:`result`,id:e.id,ok:!0,value:i??null})}catch(r){t({k:`result`,id:e.id,ok:!1,error:n.serializeError(r)})}return}}}async ensureBundle(e){if(this.bundle?.version===e)return this.bundle.data;let t=await(this.options.fetch??fetch)(new URL(`/api/rooms/${this.options.pin}/bundle`,this.options.serverUrl));if(!t.ok)throw Error(`Could not download the game (${t.status}).`);let n=await t.json();return this.bundle={version:Number(t.headers.get(`X-Bundle-Version`)??e),data:n},n}async endGame(e){let t=this.plugin;if(t&&!this.sessionEndedSent){this.sessionEndedSent=!0;let n;await Promise.race([t.sessionEnded(e).catch(()=>{}),new Promise(e=>n=setTimeout(e,a))]),clearTimeout(n)}this.destroyFrame()}async mount(t,i){this.destroyFrame(),this.sessionEndedSent=!1,this.gen=t;try{let n=await this.ensureBundle(i);if(this.gen!==t)return;let a=this.options.container();if(!a)throw Error(`No place for the game on this page.`);let o=(0,r.extractManifestFromHtml)(n.pluginHtml),s=o.success?String(o.data.appName??`Game`):`Game`,c=this.options.transformHtml?this.options.transformHtml(n.pluginHtml):n.pluginHtml,l=(this.options.createFrame??e.createPluginFrame)(c,s,this.hostApiFor(t),a.ownerDocument);this.frame=l,a.replaceChildren(l.iframe),this.emit({type:`frame`,active:!0});let u=await l.plugin;if(this.gen!==t)return;this.plugin=u,this.up({k:`mounted`,gen:t})}catch(e){if(this.gen!==t)return;this.emit({type:`error`,code:`MOUNT_FAILED`,message:e.message,fatal:!1}),this.up({k:`mount-failed`,gen:t,error:n.serializeError(e)})}}hostApiFor(e){let t={};for(let n of r.HOST_API_METHODS)t[n]=(...t)=>{if(this.gen!==e)return Promise.reject(Error(`[SESSION_ENDED] This plugin instance was replaced.`));let r=++this.callCounter;return new Promise((i,a)=>{let o=setTimeout(()=>{this.pending.delete(r),a(Error(`[INTERNAL_ERROR] The host did not answer ${n} in time.`))},this.options.callTimeoutMs??15e3);this.pending.set(r,{method:n,sentAt:Date.now(),resolve:i,reject:a,timer:o}),this.up({k:`call`,id:r,gen:e,method:n,args:t})||(clearTimeout(o),this.pending.delete(r),n===`hello`?a(Error(`[INTERNAL_ERROR] Offline.`)):i(void 0))})};return t}destroyFrame(){let e=this.frame!==null;this.frame?.destroy(),this.frame?.iframe.remove(),this.frame=null,this.plugin=null,this.gen=0;for(let e of this.pending.values())clearTimeout(e.timer),e.reject(Error(`[SESSION_ENDED] The plugin instance was closed.`));this.pending.clear(),e&&this.emit({type:`frame`,active:!1})}};exports.RelayPlayer=o;