@irtio/lobby 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +157 -5
- package/dist/index.js +500 -19
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `<irt-lobby>` — the whole share story in one tag: room code, share link, QR,
|
|
3
|
-
* connection status, an optional name box, and the badge.
|
|
3
|
+
* connection status, player count, an optional name box, and the badge.
|
|
4
4
|
*
|
|
5
5
|
* Two deliberate constraints shape this file. It is a **plain `HTMLElement`** with shadow DOM and
|
|
6
6
|
* inline styles, so a consumer needs no build step, no CSS import and no framework. And it takes
|
|
7
7
|
* its room **structurally** (`AttachableRoom`) rather than importing `@irtio/client`, so the
|
|
8
8
|
* package keeps zero dependencies and a React/Svelte user can drive it with attributes instead.
|
|
9
|
+
*
|
|
10
|
+
* ### Modal mode
|
|
11
|
+
*
|
|
12
|
+
* Set the boolean `modal` attribute to render the panel inside a fixed, full-viewport backdrop
|
|
13
|
+
* instead of inline. The element supplies no trigger of its own — a dev wires their own button to
|
|
14
|
+
* call `.open()`, and the element handles the rest: it renders hidden until the `open` boolean
|
|
15
|
+
* attribute is present, closes on a backdrop click, Escape, or its own × button, and toggling
|
|
16
|
+
* either the attribute or the `open()`/`close()`/`toggle()` methods keeps the other in sync.
|
|
17
|
+
* Leaving `modal` off keeps the element's non-modal rendering byte-for-byte unchanged.
|
|
9
18
|
*/
|
|
10
19
|
/**
|
|
11
20
|
* The part of `@irtio/client`'s `Room` the lobby actually uses. Typed structurally on purpose:
|
|
12
21
|
* `lobby.attach(room)` accepts a real room without this package depending on the client.
|
|
22
|
+
*
|
|
23
|
+
* `clients`, `maxClients` and `rtt` are all optional, so a room without them still satisfies this
|
|
24
|
+
* type and still attaches; the element just leaves the player count unset. A stand-in whose `on`
|
|
25
|
+
* takes a plain `string` event (rather than the exact three-way overload below) is still
|
|
26
|
+
* structurally compatible — TypeScript checks methods bivariantly — so an older or hand-rolled
|
|
27
|
+
* room needs no changes to attach.
|
|
13
28
|
*/
|
|
14
29
|
interface AttachableRoom {
|
|
15
30
|
readonly id: string;
|
|
16
31
|
readonly link: string;
|
|
17
32
|
readonly status: string;
|
|
33
|
+
readonly clients?: readonly {
|
|
34
|
+
connected: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
readonly maxClients?: number;
|
|
37
|
+
readonly rtt?: number;
|
|
18
38
|
on(event: 'status', cb: (status: string) => void): () => void;
|
|
39
|
+
on(event: 'clients', cb: (clients: readonly {
|
|
40
|
+
connected: boolean;
|
|
41
|
+
}[]) => void): () => void;
|
|
42
|
+
on(event: 'rtt', cb: (rtt: number) => void): () => void;
|
|
19
43
|
}
|
|
20
44
|
/**
|
|
21
45
|
* The custom element class. Registered as `irt-lobby` by `defineLobby()`, which this package
|
|
@@ -37,14 +61,36 @@ declare class IrtLobbyElement extends HTMLElement {
|
|
|
37
61
|
/** Whether the name box is shown. Mirrors the `name-entry` boolean attribute. */
|
|
38
62
|
get nameEntry(): boolean;
|
|
39
63
|
set nameEntry(value: boolean);
|
|
64
|
+
/**
|
|
65
|
+
* The connected player count. `undefined` (the attribute absent) hides the whole row; any other
|
|
66
|
+
* value, including `0`, shows it. Mirrors the `players` attribute.
|
|
67
|
+
*/
|
|
68
|
+
get players(): number | undefined;
|
|
69
|
+
set players(value: number | undefined);
|
|
70
|
+
/** The room's capacity, if known. `0` or the attribute absent both mean "unknown". */
|
|
71
|
+
get maxPlayers(): number;
|
|
72
|
+
set maxPlayers(value: number);
|
|
73
|
+
/** Whether the panel renders inside a fixed full-viewport backdrop. Mirrors `modal`. */
|
|
74
|
+
get modal(): boolean;
|
|
75
|
+
set modal(value: boolean);
|
|
76
|
+
/** Whether a modal panel is currently shown. Meaningless outside `modal` mode. */
|
|
77
|
+
get isOpen(): boolean;
|
|
78
|
+
/** Shows the modal panel and starts listening for Escape. No-op outside `modal` mode. */
|
|
79
|
+
open(): void;
|
|
80
|
+
/** Hides the modal panel and stops listening for Escape. */
|
|
81
|
+
close(): void;
|
|
82
|
+
/** Convenience for a single trigger button that both opens and closes the modal. */
|
|
83
|
+
toggle(): void;
|
|
40
84
|
connectedCallback(): void;
|
|
41
|
-
attributeChangedCallback(): void;
|
|
85
|
+
attributeChangedCallback(name: string): void;
|
|
42
86
|
/** Unsubscribing here is why `attach` needs no matching `detach` call in app code. */
|
|
43
87
|
disconnectedCallback(): void;
|
|
44
88
|
/**
|
|
45
89
|
* Drives the element from a live room: copies `id`/`link`/`status` in and follows `status`
|
|
46
|
-
* afterwards.
|
|
47
|
-
*
|
|
90
|
+
* afterwards. When the room reports `clients` or `maxClients`, seeds and follows the player
|
|
91
|
+
* count too; a room without them just leaves `players` unset. Attaching twice replaces the
|
|
92
|
+
* previous subscription. Returns the unsubscribe so a framework can tie it to its own teardown;
|
|
93
|
+
* `disconnectedCallback` calls it regardless.
|
|
48
94
|
*/
|
|
49
95
|
attach(room: AttachableRoom): () => void;
|
|
50
96
|
/** The room passed to the last `attach`, if it is still attached. */
|
|
@@ -60,6 +106,112 @@ declare const LOBBY_TAG = "irt-lobby";
|
|
|
60
106
|
*/
|
|
61
107
|
declare function defineLobby(tag?: string): void;
|
|
62
108
|
|
|
109
|
+
/**
|
|
110
|
+
* `<irt-status>` — a compact inline status dot for when you already have your own share UI and
|
|
111
|
+
* just want the connection indicator: a coloured dot, and optionally the latest ping next to it.
|
|
112
|
+
*
|
|
113
|
+
* Same constraints as `<irt-lobby>`: plain `HTMLElement`, shadow DOM, inline styles, zero
|
|
114
|
+
* dependencies, and a structural `AttachableRoom` rather than an import of `@irtio/client`.
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The custom element class. Registered as `irt-status` by `defineStatus()`, which this package
|
|
119
|
+
* calls for you on import when a `customElements` registry exists.
|
|
120
|
+
*/
|
|
121
|
+
declare class IrtStatusElement extends HTMLElement {
|
|
122
|
+
#private;
|
|
123
|
+
static get observedAttributes(): readonly string[];
|
|
124
|
+
constructor();
|
|
125
|
+
/** One of `@irtio/client`'s statuses. Anything else renders as a grey dot with that text. */
|
|
126
|
+
get status(): string;
|
|
127
|
+
set status(value: string);
|
|
128
|
+
/** The latest round-trip time in milliseconds, if known. */
|
|
129
|
+
get ping(): number | undefined;
|
|
130
|
+
set ping(value: number | undefined);
|
|
131
|
+
/** Whether the ping is rendered next to the dot. Mirrors the `show-ping` boolean attribute. */
|
|
132
|
+
get showPing(): boolean;
|
|
133
|
+
set showPing(value: boolean);
|
|
134
|
+
connectedCallback(): void;
|
|
135
|
+
attributeChangedCallback(): void;
|
|
136
|
+
disconnectedCallback(): void;
|
|
137
|
+
/**
|
|
138
|
+
* Drives the element from a live room: seeds `status` and `ping` and follows `status`
|
|
139
|
+
* afterwards. For the ping, it follows the room's `'rtt'` event when the room emits one;
|
|
140
|
+
* otherwise it polls `room.rtt` every two seconds, since not every room reports RTT as an
|
|
141
|
+
* event. Attaching twice replaces the previous subscription and polling loop.
|
|
142
|
+
*/
|
|
143
|
+
attach(room: AttachableRoom): () => void;
|
|
144
|
+
render(): void;
|
|
145
|
+
}
|
|
146
|
+
/** The tag name the element registers under. */
|
|
147
|
+
declare const STATUS_TAG = "irt-status";
|
|
148
|
+
/**
|
|
149
|
+
* Registers `<irt-status>`. Idempotent and safe to call anywhere — importing this package already
|
|
150
|
+
* calls it once, and a second registration (HMR, two bundled copies) would otherwise throw.
|
|
151
|
+
*/
|
|
152
|
+
declare function defineStatus(tag?: string): void;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* `<irt-profile>` — a dev-mode bandwidth overlay: where this client's bytes are going, by
|
|
156
|
+
* collection and field, once a second (D65).
|
|
157
|
+
*
|
|
158
|
+
* Same constraints as the other two elements: plain `HTMLElement`, shadow DOM, inline styles,
|
|
159
|
+
* zero dependencies, and a structural room type rather than an import of `@irtio/client`. It is a
|
|
160
|
+
* development surface — mount it behind a flag rather than shipping it on.
|
|
161
|
+
*/
|
|
162
|
+
/**
|
|
163
|
+
* The part of `@irtio/client`'s `Room` this element uses, typed structurally so the package keeps
|
|
164
|
+
* its zero dependencies. `profile` is what `joinRoom(schema, { profile: true })` adds; a room
|
|
165
|
+
* without it attaches fine and the overlay says the profiler is off.
|
|
166
|
+
*/
|
|
167
|
+
interface ProfilableRoom {
|
|
168
|
+
readonly profile?: {
|
|
169
|
+
perSecond(): ProfileReading;
|
|
170
|
+
total(): ProfileReading;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** The shape `@irtio/protocol`'s `ProfileSnapshot` has, structurally. */
|
|
174
|
+
interface ProfileReading {
|
|
175
|
+
readonly rows: readonly {
|
|
176
|
+
readonly kind: string;
|
|
177
|
+
readonly key: string;
|
|
178
|
+
readonly out: number;
|
|
179
|
+
readonly in: number;
|
|
180
|
+
}[];
|
|
181
|
+
readonly bytesIn: number;
|
|
182
|
+
readonly bytesOut: number;
|
|
183
|
+
}
|
|
184
|
+
declare class IrtProfileElement extends HTMLElement {
|
|
185
|
+
#private;
|
|
186
|
+
static get observedAttributes(): readonly string[];
|
|
187
|
+
constructor();
|
|
188
|
+
/** How many rows to show. Mirrors the `rows` attribute. */
|
|
189
|
+
get rows(): number;
|
|
190
|
+
set rows(value: number);
|
|
191
|
+
/** The heading above the table. Mirrors the `title-text` attribute. */
|
|
192
|
+
get titleText(): string;
|
|
193
|
+
set titleText(value: string);
|
|
194
|
+
/**
|
|
195
|
+
* The room to read. Assigning it starts the 1 Hz refresh; assigning `undefined` stops it. A
|
|
196
|
+
* room joined without `profile: true` has no ledger, and the overlay says so rather than
|
|
197
|
+
* rendering zeros.
|
|
198
|
+
*/
|
|
199
|
+
get room(): ProfilableRoom | undefined;
|
|
200
|
+
set room(value: ProfilableRoom | undefined);
|
|
201
|
+
/** Symmetry with `<irt-lobby>` and `<irt-status>`: `attach(room)` returns a detach function. */
|
|
202
|
+
attach(room: ProfilableRoom): () => void;
|
|
203
|
+
connectedCallback(): void;
|
|
204
|
+
attributeChangedCallback(): void;
|
|
205
|
+
disconnectedCallback(): void;
|
|
206
|
+
/** Reads the ledger once. Exposed so a test drives it without waiting for the interval. */
|
|
207
|
+
refresh(): void;
|
|
208
|
+
render(): void;
|
|
209
|
+
}
|
|
210
|
+
/** The tag name the element registers under. */
|
|
211
|
+
declare const PROFILE_TAG = "irt-profile";
|
|
212
|
+
/** Registers `<irt-profile>`. Idempotent, like `defineLobby()` and `defineStatus()`. */
|
|
213
|
+
declare function defineProfile(tag?: string): void;
|
|
214
|
+
|
|
63
215
|
/**
|
|
64
216
|
* A QR encoder small enough to inline: byte mode, error-correction level L, versions 1–40,
|
|
65
217
|
* automatic version selection and automatic mask selection.
|
|
@@ -87,4 +239,4 @@ interface QrMatrix {
|
|
|
87
239
|
*/
|
|
88
240
|
declare function encodeQr(text: string): QrMatrix;
|
|
89
241
|
|
|
90
|
-
export { type AttachableRoom, IrtLobbyElement, LOBBY_TAG, type QrMatrix, defineLobby, encodeQr };
|
|
242
|
+
export { type AttachableRoom, IrtLobbyElement, IrtProfileElement, IrtStatusElement, LOBBY_TAG, PROFILE_TAG, type ProfilableRoom, type ProfileReading, type QrMatrix, STATUS_TAG, defineLobby, defineProfile, defineStatus, encodeQr };
|
package/dist/index.js
CHANGED
|
@@ -420,7 +420,7 @@ function encodeQr(text) {
|
|
|
420
420
|
return { size: grid.size, version, mask: bestMask, modules: grid.modules };
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
// src/
|
|
423
|
+
// src/status-colors.ts
|
|
424
424
|
var STATUS_LABELS = {
|
|
425
425
|
connecting: "connecting",
|
|
426
426
|
starting: "waking the server",
|
|
@@ -435,7 +435,22 @@ var STATUS_COLORS = {
|
|
|
435
435
|
reconnecting: "#e0a33a",
|
|
436
436
|
closed: "#d1495b"
|
|
437
437
|
};
|
|
438
|
-
var
|
|
438
|
+
var UNKNOWN_STATUS_COLOR = "#9aa1ad";
|
|
439
|
+
|
|
440
|
+
// src/element.ts
|
|
441
|
+
function countConnected(clients) {
|
|
442
|
+
return clients.filter((c) => c.connected !== false).length;
|
|
443
|
+
}
|
|
444
|
+
var OBSERVED = [
|
|
445
|
+
"room-code",
|
|
446
|
+
"link",
|
|
447
|
+
"status",
|
|
448
|
+
"name-entry",
|
|
449
|
+
"players",
|
|
450
|
+
"max-players",
|
|
451
|
+
"modal",
|
|
452
|
+
"open"
|
|
453
|
+
];
|
|
439
454
|
var STYLE = `
|
|
440
455
|
:host {
|
|
441
456
|
display: block;
|
|
@@ -449,8 +464,10 @@ var STYLE = `
|
|
|
449
464
|
max-width: 320px;
|
|
450
465
|
}
|
|
451
466
|
:host([hidden]) { display: none; }
|
|
467
|
+
:host([modal]) { display: contents; }
|
|
452
468
|
* { box-sizing: border-box; }
|
|
453
469
|
.status { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #9aa1ad; }
|
|
470
|
+
.status .players { margin-left: auto; }
|
|
454
471
|
.dot { width: 8px; height: 8px; border-radius: 50%; background: #9aa1ad; flex: none; }
|
|
455
472
|
.code {
|
|
456
473
|
display: block; width: 100%; margin: 10px 0 4px;
|
|
@@ -480,6 +497,29 @@ var STYLE = `
|
|
|
480
497
|
.badge { margin-top: 12px; font-size: 11px; color: #6c7280; text-align: center; }
|
|
481
498
|
.badge a { color: inherit; }
|
|
482
499
|
.toast { min-height: 14px; font-size: 11px; color: #3ac177; text-align: center; }
|
|
500
|
+
.backdrop {
|
|
501
|
+
position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
502
|
+
background: rgba(8, 9, 12, 0.6); z-index: 999;
|
|
503
|
+
}
|
|
504
|
+
.backdrop[hidden] { display: none; }
|
|
505
|
+
.panel {
|
|
506
|
+
position: relative;
|
|
507
|
+
box-sizing: border-box;
|
|
508
|
+
font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
509
|
+
color: #f2f3f5;
|
|
510
|
+
background: #16181d;
|
|
511
|
+
border: 1px solid #2a2e37;
|
|
512
|
+
border-radius: 12px;
|
|
513
|
+
padding: 16px;
|
|
514
|
+
max-width: 320px;
|
|
515
|
+
width: calc(100vw - 32px);
|
|
516
|
+
}
|
|
517
|
+
.close {
|
|
518
|
+
position: absolute; top: -10px; right: -10px; width: 26px; height: 26px; border-radius: 50%;
|
|
519
|
+
border: 1px solid #2a2e37; background: #16181d; color: #f2f3f5; font-size: 14px; line-height: 1;
|
|
520
|
+
cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0;
|
|
521
|
+
}
|
|
522
|
+
.close:hover { background: #1e2129; }
|
|
483
523
|
`;
|
|
484
524
|
var IrtLobbyElement = class extends HTMLElement {
|
|
485
525
|
static get observedAttributes() {
|
|
@@ -490,6 +530,7 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
490
530
|
/** Set by `attach`; re-read on every render so a reconnect that changes the code is picked up. */
|
|
491
531
|
#room;
|
|
492
532
|
#toastTimer;
|
|
533
|
+
#escapeListening = false;
|
|
493
534
|
constructor() {
|
|
494
535
|
super();
|
|
495
536
|
this.#root = this.attachShadow({ mode: "open" });
|
|
@@ -524,11 +565,61 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
524
565
|
if (value) this.setAttribute("name-entry", "");
|
|
525
566
|
else this.removeAttribute("name-entry");
|
|
526
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* The connected player count. `undefined` (the attribute absent) hides the whole row; any other
|
|
570
|
+
* value, including `0`, shows it. Mirrors the `players` attribute.
|
|
571
|
+
*/
|
|
572
|
+
get players() {
|
|
573
|
+
if (!this.hasAttribute("players")) return void 0;
|
|
574
|
+
const value = Number(this.getAttribute("players"));
|
|
575
|
+
return Number.isFinite(value) ? value : void 0;
|
|
576
|
+
}
|
|
577
|
+
set players(value) {
|
|
578
|
+
if (value === void 0) this.removeAttribute("players");
|
|
579
|
+
else this.setAttribute("players", String(value));
|
|
580
|
+
}
|
|
581
|
+
/** The room's capacity, if known. `0` or the attribute absent both mean "unknown". */
|
|
582
|
+
get maxPlayers() {
|
|
583
|
+
const value = Number(this.getAttribute("max-players"));
|
|
584
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
585
|
+
}
|
|
586
|
+
set maxPlayers(value) {
|
|
587
|
+
this.setAttribute("max-players", String(value));
|
|
588
|
+
}
|
|
589
|
+
/** Whether the panel renders inside a fixed full-viewport backdrop. Mirrors `modal`. */
|
|
590
|
+
get modal() {
|
|
591
|
+
return this.hasAttribute("modal");
|
|
592
|
+
}
|
|
593
|
+
set modal(value) {
|
|
594
|
+
if (value) this.setAttribute("modal", "");
|
|
595
|
+
else this.removeAttribute("modal");
|
|
596
|
+
}
|
|
597
|
+
/** Whether a modal panel is currently shown. Meaningless outside `modal` mode. */
|
|
598
|
+
get isOpen() {
|
|
599
|
+
return this.hasAttribute("open");
|
|
600
|
+
}
|
|
601
|
+
/** Shows the modal panel and starts listening for Escape. No-op outside `modal` mode. */
|
|
602
|
+
open() {
|
|
603
|
+
if (!this.modal || this.hasAttribute("open")) return;
|
|
604
|
+
this.setAttribute("open", "");
|
|
605
|
+
}
|
|
606
|
+
/** Hides the modal panel and stops listening for Escape. */
|
|
607
|
+
close() {
|
|
608
|
+
if (!this.hasAttribute("open")) return;
|
|
609
|
+
this.removeAttribute("open");
|
|
610
|
+
}
|
|
611
|
+
/** Convenience for a single trigger button that both opens and closes the modal. */
|
|
612
|
+
toggle() {
|
|
613
|
+
if (this.hasAttribute("open")) this.close();
|
|
614
|
+
else this.open();
|
|
615
|
+
}
|
|
527
616
|
// -- lifecycle -------------------------------------------------------------
|
|
528
617
|
connectedCallback() {
|
|
529
618
|
this.render();
|
|
619
|
+
this.#syncEscapeListener();
|
|
530
620
|
}
|
|
531
|
-
attributeChangedCallback() {
|
|
621
|
+
attributeChangedCallback(name) {
|
|
622
|
+
if (name === "open" || name === "modal") this.#syncEscapeListener();
|
|
532
623
|
if (this.#root.childElementCount > 0) this.render();
|
|
533
624
|
}
|
|
534
625
|
/** Unsubscribing here is why `attach` needs no matching `detach` call in app code. */
|
|
@@ -536,12 +627,28 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
536
627
|
this.#detach?.();
|
|
537
628
|
this.#detach = void 0;
|
|
538
629
|
if (this.#toastTimer !== void 0) clearTimeout(this.#toastTimer);
|
|
630
|
+
this.#setEscapeListening(false);
|
|
631
|
+
}
|
|
632
|
+
/** Adds or removes the document-level Escape listener to match whether the modal is open. */
|
|
633
|
+
#syncEscapeListener() {
|
|
634
|
+
this.#setEscapeListening(this.modal && this.hasAttribute("open") && this.isConnected);
|
|
539
635
|
}
|
|
636
|
+
#setEscapeListening(on) {
|
|
637
|
+
if (on === this.#escapeListening) return;
|
|
638
|
+
this.#escapeListening = on;
|
|
639
|
+
if (on) this.ownerDocument.addEventListener("keydown", this.#onKeydown);
|
|
640
|
+
else this.ownerDocument.removeEventListener("keydown", this.#onKeydown);
|
|
641
|
+
}
|
|
642
|
+
#onKeydown = (event) => {
|
|
643
|
+
if (event.key === "Escape") this.close();
|
|
644
|
+
};
|
|
540
645
|
// -- the room path ---------------------------------------------------------
|
|
541
646
|
/**
|
|
542
647
|
* Drives the element from a live room: copies `id`/`link`/`status` in and follows `status`
|
|
543
|
-
* afterwards.
|
|
544
|
-
*
|
|
648
|
+
* afterwards. When the room reports `clients` or `maxClients`, seeds and follows the player
|
|
649
|
+
* count too; a room without them just leaves `players` unset. Attaching twice replaces the
|
|
650
|
+
* previous subscription. Returns the unsubscribe so a framework can tie it to its own teardown;
|
|
651
|
+
* `disconnectedCallback` calls it regardless.
|
|
545
652
|
*/
|
|
546
653
|
attach(room) {
|
|
547
654
|
this.#detach?.();
|
|
@@ -549,13 +656,23 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
549
656
|
this.roomCode = room.id;
|
|
550
657
|
this.link = room.link;
|
|
551
658
|
this.status = room.status;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
659
|
+
if (room.maxClients !== void 0) this.maxPlayers = room.maxClients;
|
|
660
|
+
if (room.clients !== void 0) this.players = countConnected(room.clients);
|
|
661
|
+
const offs = [];
|
|
662
|
+
offs.push(
|
|
663
|
+
room.on("status", (status) => {
|
|
664
|
+
this.roomCode = room.id;
|
|
665
|
+
this.link = room.link;
|
|
666
|
+
this.status = status;
|
|
667
|
+
})
|
|
668
|
+
);
|
|
669
|
+
offs.push(
|
|
670
|
+
room.on("clients", (clients) => {
|
|
671
|
+
this.players = countConnected(clients);
|
|
672
|
+
})
|
|
673
|
+
);
|
|
557
674
|
const detach = () => {
|
|
558
|
-
off();
|
|
675
|
+
for (const off of offs) off();
|
|
559
676
|
if (this.#detach === detach) {
|
|
560
677
|
this.#detach = void 0;
|
|
561
678
|
this.#room = void 0;
|
|
@@ -577,17 +694,54 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
577
694
|
const style = doc.createElement("style");
|
|
578
695
|
style.textContent = STYLE;
|
|
579
696
|
this.#root.append(style);
|
|
697
|
+
const modal = this.modal;
|
|
698
|
+
let host;
|
|
699
|
+
if (modal) {
|
|
700
|
+
const backdrop = doc.createElement("div");
|
|
701
|
+
backdrop.className = "backdrop";
|
|
702
|
+
backdrop.setAttribute("part", "backdrop");
|
|
703
|
+
backdrop.hidden = !this.isOpen;
|
|
704
|
+
backdrop.addEventListener("click", (event) => {
|
|
705
|
+
if (event.target === backdrop) this.close();
|
|
706
|
+
});
|
|
707
|
+
const panel = doc.createElement("div");
|
|
708
|
+
panel.className = "panel";
|
|
709
|
+
panel.setAttribute("part", "panel");
|
|
710
|
+
const closeButton = doc.createElement("button");
|
|
711
|
+
closeButton.type = "button";
|
|
712
|
+
closeButton.className = "close";
|
|
713
|
+
closeButton.setAttribute("part", "close");
|
|
714
|
+
closeButton.setAttribute("aria-label", "close");
|
|
715
|
+
closeButton.textContent = "\xD7";
|
|
716
|
+
closeButton.addEventListener("click", () => this.close());
|
|
717
|
+
panel.append(closeButton);
|
|
718
|
+
backdrop.append(panel);
|
|
719
|
+
this.#root.append(backdrop);
|
|
720
|
+
host = panel;
|
|
721
|
+
} else {
|
|
722
|
+
host = this.#root;
|
|
723
|
+
}
|
|
580
724
|
const status = this.status;
|
|
581
725
|
const statusRow = doc.createElement("div");
|
|
582
726
|
statusRow.className = "status";
|
|
583
727
|
statusRow.setAttribute("part", "status");
|
|
584
728
|
const dot = doc.createElement("span");
|
|
585
729
|
dot.className = "dot";
|
|
586
|
-
dot.style.background = STATUS_COLORS[status] ??
|
|
730
|
+
dot.style.background = STATUS_COLORS[status] ?? UNKNOWN_STATUS_COLOR;
|
|
587
731
|
const statusText = doc.createElement("span");
|
|
588
732
|
statusText.textContent = STATUS_LABELS[status] ?? status;
|
|
589
733
|
statusRow.append(dot, statusText);
|
|
590
|
-
this
|
|
734
|
+
const players = this.players;
|
|
735
|
+
if (players !== void 0) {
|
|
736
|
+
const playersSpan = doc.createElement("span");
|
|
737
|
+
playersSpan.className = "players";
|
|
738
|
+
playersSpan.setAttribute("part", "players");
|
|
739
|
+
const max = this.maxPlayers;
|
|
740
|
+
const noun = max > 0 ? "players" : players === 1 ? "player" : "players";
|
|
741
|
+
playersSpan.textContent = max > 0 ? `${players}/${max} ${noun}` : `${players} ${noun}`;
|
|
742
|
+
statusRow.append(playersSpan);
|
|
743
|
+
}
|
|
744
|
+
host.append(statusRow);
|
|
591
745
|
const code = doc.createElement("button");
|
|
592
746
|
code.type = "button";
|
|
593
747
|
code.className = "code";
|
|
@@ -597,7 +751,7 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
597
751
|
code.addEventListener("click", () => {
|
|
598
752
|
void this.#copy(this.roomCode, "room code copied");
|
|
599
753
|
});
|
|
600
|
-
|
|
754
|
+
host.append(code);
|
|
601
755
|
const link = doc.createElement("button");
|
|
602
756
|
link.type = "button";
|
|
603
757
|
link.className = "link";
|
|
@@ -607,16 +761,16 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
607
761
|
link.addEventListener("click", () => {
|
|
608
762
|
void this.#copy(this.link, "link copied");
|
|
609
763
|
});
|
|
610
|
-
|
|
764
|
+
host.append(link);
|
|
611
765
|
const toast = doc.createElement("div");
|
|
612
766
|
toast.className = "toast";
|
|
613
|
-
|
|
767
|
+
host.append(toast);
|
|
614
768
|
const canvas = doc.createElement("canvas");
|
|
615
769
|
canvas.setAttribute("part", "qr");
|
|
616
|
-
|
|
770
|
+
host.append(canvas);
|
|
617
771
|
if (this.link) this.#drawQr(canvas, this.link);
|
|
618
772
|
else canvas.hidden = true;
|
|
619
|
-
if (this.nameEntry)
|
|
773
|
+
if (this.nameEntry) host.append(this.#nameForm(doc));
|
|
620
774
|
const badge = doc.createElement("div");
|
|
621
775
|
badge.className = "badge";
|
|
622
776
|
badge.setAttribute("part", "badge");
|
|
@@ -626,7 +780,7 @@ var IrtLobbyElement = class extends HTMLElement {
|
|
|
626
780
|
anchor.rel = "noreferrer";
|
|
627
781
|
anchor.textContent = "multiplayer by irt.io";
|
|
628
782
|
badge.append(anchor);
|
|
629
|
-
|
|
783
|
+
host.append(badge);
|
|
630
784
|
}
|
|
631
785
|
/** The optional name box. Submitting emits `name` **before** the app joins, which is the point. */
|
|
632
786
|
#nameForm(doc) {
|
|
@@ -712,11 +866,338 @@ function defineLobby(tag = LOBBY_TAG) {
|
|
|
712
866
|
registry.define(tag, IrtLobbyElement);
|
|
713
867
|
}
|
|
714
868
|
|
|
869
|
+
// src/profile.ts
|
|
870
|
+
var OBSERVED2 = ["rows", "title-text"];
|
|
871
|
+
var REFRESH_MS = 1e3;
|
|
872
|
+
var DEFAULT_ROWS = 8;
|
|
873
|
+
var STYLE2 = `
|
|
874
|
+
:host {
|
|
875
|
+
display: block;
|
|
876
|
+
box-sizing: border-box;
|
|
877
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
878
|
+
font-size: 11px;
|
|
879
|
+
line-height: 1.5;
|
|
880
|
+
color: #c9d1e1;
|
|
881
|
+
background: rgba(11, 14, 20, 0.86);
|
|
882
|
+
border: 1px solid #232838;
|
|
883
|
+
border-radius: 6px;
|
|
884
|
+
padding: 8px 10px;
|
|
885
|
+
min-width: 260px;
|
|
886
|
+
}
|
|
887
|
+
* { box-sizing: border-box; }
|
|
888
|
+
h4 { margin: 0 0 4px; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
|
|
889
|
+
font-weight: 600; color: #6b7590; }
|
|
890
|
+
table { border-collapse: collapse; width: 100%; }
|
|
891
|
+
td { padding: 1px 0; white-space: nowrap; }
|
|
892
|
+
td.kind { color: #6b7590; padding-right: 8px; }
|
|
893
|
+
td.key { padding-right: 10px; }
|
|
894
|
+
td.num { text-align: right; font-variant-numeric: tabular-nums; padding-left: 8px; }
|
|
895
|
+
tr.total td { border-top: 1px solid #232838; padding-top: 3px; color: #7dd3fc; }
|
|
896
|
+
p.off { margin: 0; color: #6b7590; }
|
|
897
|
+
`;
|
|
898
|
+
function rate(bytesPerSecond) {
|
|
899
|
+
if (bytesPerSecond >= 1e6) return `${(bytesPerSecond / 1e6).toFixed(1)} MB/s`;
|
|
900
|
+
if (bytesPerSecond >= 1e3) return `${(bytesPerSecond / 1e3).toFixed(1)} kB/s`;
|
|
901
|
+
return `${Math.round(bytesPerSecond)} B/s`;
|
|
902
|
+
}
|
|
903
|
+
function labelFor(kind, key) {
|
|
904
|
+
return kind === "churn" ? `${key} enter/leave (incl. spawns)` : key;
|
|
905
|
+
}
|
|
906
|
+
var IrtProfileElement = class extends HTMLElement {
|
|
907
|
+
static get observedAttributes() {
|
|
908
|
+
return OBSERVED2;
|
|
909
|
+
}
|
|
910
|
+
#shadow;
|
|
911
|
+
#room;
|
|
912
|
+
#timer;
|
|
913
|
+
#reading;
|
|
914
|
+
constructor() {
|
|
915
|
+
super();
|
|
916
|
+
this.#shadow = this.attachShadow({ mode: "open" });
|
|
917
|
+
}
|
|
918
|
+
/** How many rows to show. Mirrors the `rows` attribute. */
|
|
919
|
+
get rows() {
|
|
920
|
+
const value = Number(this.getAttribute("rows"));
|
|
921
|
+
return Number.isInteger(value) && value > 0 ? value : DEFAULT_ROWS;
|
|
922
|
+
}
|
|
923
|
+
set rows(value) {
|
|
924
|
+
this.setAttribute("rows", String(value));
|
|
925
|
+
}
|
|
926
|
+
/** The heading above the table. Mirrors the `title-text` attribute. */
|
|
927
|
+
get titleText() {
|
|
928
|
+
return this.getAttribute("title-text") ?? "bandwidth";
|
|
929
|
+
}
|
|
930
|
+
set titleText(value) {
|
|
931
|
+
this.setAttribute("title-text", value);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* The room to read. Assigning it starts the 1 Hz refresh; assigning `undefined` stops it. A
|
|
935
|
+
* room joined without `profile: true` has no ledger, and the overlay says so rather than
|
|
936
|
+
* rendering zeros.
|
|
937
|
+
*/
|
|
938
|
+
get room() {
|
|
939
|
+
return this.#room;
|
|
940
|
+
}
|
|
941
|
+
set room(value) {
|
|
942
|
+
this.#room = value;
|
|
943
|
+
this.#reading = void 0;
|
|
944
|
+
this.#stop();
|
|
945
|
+
if (value?.profile) {
|
|
946
|
+
this.#tick();
|
|
947
|
+
this.#timer = setInterval(() => this.#tick(), REFRESH_MS);
|
|
948
|
+
}
|
|
949
|
+
this.render();
|
|
950
|
+
}
|
|
951
|
+
/** Symmetry with `<irt-lobby>` and `<irt-status>`: `attach(room)` returns a detach function. */
|
|
952
|
+
attach(room) {
|
|
953
|
+
this.room = room;
|
|
954
|
+
return () => {
|
|
955
|
+
if (this.#room === room) this.room = void 0;
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
connectedCallback() {
|
|
959
|
+
this.render();
|
|
960
|
+
}
|
|
961
|
+
attributeChangedCallback() {
|
|
962
|
+
if (this.#shadow.childElementCount > 0) this.render();
|
|
963
|
+
}
|
|
964
|
+
disconnectedCallback() {
|
|
965
|
+
this.#stop();
|
|
966
|
+
}
|
|
967
|
+
/** Reads the ledger once. Exposed so a test drives it without waiting for the interval. */
|
|
968
|
+
refresh() {
|
|
969
|
+
this.#tick();
|
|
970
|
+
}
|
|
971
|
+
#tick() {
|
|
972
|
+
const profile = this.#room?.profile;
|
|
973
|
+
if (!profile) return;
|
|
974
|
+
this.#reading = profile.perSecond();
|
|
975
|
+
this.render();
|
|
976
|
+
}
|
|
977
|
+
#stop() {
|
|
978
|
+
if (this.#timer !== void 0) {
|
|
979
|
+
clearInterval(this.#timer);
|
|
980
|
+
this.#timer = void 0;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
render() {
|
|
984
|
+
const doc = this.ownerDocument;
|
|
985
|
+
this.#shadow.textContent = "";
|
|
986
|
+
const style = doc.createElement("style");
|
|
987
|
+
style.textContent = STYLE2;
|
|
988
|
+
this.#shadow.append(style);
|
|
989
|
+
const heading = doc.createElement("h4");
|
|
990
|
+
heading.textContent = this.titleText;
|
|
991
|
+
this.#shadow.append(heading);
|
|
992
|
+
if (!this.#room?.profile) {
|
|
993
|
+
const off = doc.createElement("p");
|
|
994
|
+
off.className = "off";
|
|
995
|
+
off.setAttribute("part", "off");
|
|
996
|
+
off.textContent = "join with { profile: true } to see this";
|
|
997
|
+
this.#shadow.append(off);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const reading = this.#reading;
|
|
1001
|
+
const table = doc.createElement("table");
|
|
1002
|
+
table.setAttribute("part", "table");
|
|
1003
|
+
const shown = [...reading?.rows ?? []].filter((r) => r.out !== 0 || r.in !== 0).sort((a, b) => b.out + b.in - (a.out + a.in)).slice(0, this.rows);
|
|
1004
|
+
for (const row of shown) {
|
|
1005
|
+
const tr = doc.createElement("tr");
|
|
1006
|
+
const kind = doc.createElement("td");
|
|
1007
|
+
kind.className = "kind";
|
|
1008
|
+
kind.textContent = row.kind;
|
|
1009
|
+
const key = doc.createElement("td");
|
|
1010
|
+
key.className = "key";
|
|
1011
|
+
key.textContent = labelFor(row.kind, row.key);
|
|
1012
|
+
const inCell = doc.createElement("td");
|
|
1013
|
+
inCell.className = "num";
|
|
1014
|
+
inCell.textContent = rate(row.in);
|
|
1015
|
+
const outCell = doc.createElement("td");
|
|
1016
|
+
outCell.className = "num";
|
|
1017
|
+
outCell.textContent = rate(row.out);
|
|
1018
|
+
tr.append(kind, key, inCell, outCell);
|
|
1019
|
+
table.append(tr);
|
|
1020
|
+
}
|
|
1021
|
+
const total = doc.createElement("tr");
|
|
1022
|
+
total.className = "total";
|
|
1023
|
+
total.setAttribute("part", "total");
|
|
1024
|
+
const label = doc.createElement("td");
|
|
1025
|
+
label.className = "kind";
|
|
1026
|
+
label.textContent = "total";
|
|
1027
|
+
const spacer = doc.createElement("td");
|
|
1028
|
+
const totalIn = doc.createElement("td");
|
|
1029
|
+
totalIn.className = "num";
|
|
1030
|
+
totalIn.textContent = `in ${rate(reading?.bytesIn ?? 0)}`;
|
|
1031
|
+
const totalOut = doc.createElement("td");
|
|
1032
|
+
totalOut.className = "num";
|
|
1033
|
+
totalOut.textContent = `out ${rate(reading?.bytesOut ?? 0)}`;
|
|
1034
|
+
total.append(label, spacer, totalIn, totalOut);
|
|
1035
|
+
table.append(total);
|
|
1036
|
+
this.#shadow.append(table);
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
var PROFILE_TAG = "irt-profile";
|
|
1040
|
+
function defineProfile(tag = PROFILE_TAG) {
|
|
1041
|
+
const registry = globalThis.customElements;
|
|
1042
|
+
if (!registry || registry.get(tag)) return;
|
|
1043
|
+
registry.define(tag, IrtProfileElement);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// src/status.ts
|
|
1047
|
+
var OBSERVED3 = ["status", "ping", "show-ping"];
|
|
1048
|
+
var PING_POLL_MS = 2e3;
|
|
1049
|
+
var STYLE3 = `
|
|
1050
|
+
:host {
|
|
1051
|
+
display: inline-flex;
|
|
1052
|
+
align-items: center;
|
|
1053
|
+
gap: 6px;
|
|
1054
|
+
box-sizing: border-box;
|
|
1055
|
+
font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
1056
|
+
font-size: 12px;
|
|
1057
|
+
color: #9aa1ad;
|
|
1058
|
+
}
|
|
1059
|
+
* { box-sizing: border-box; }
|
|
1060
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; background: #9aa1ad; flex: none; }
|
|
1061
|
+
.ping { font-variant-numeric: tabular-nums; }
|
|
1062
|
+
`;
|
|
1063
|
+
var IrtStatusElement = class extends HTMLElement {
|
|
1064
|
+
static get observedAttributes() {
|
|
1065
|
+
return OBSERVED3;
|
|
1066
|
+
}
|
|
1067
|
+
#root;
|
|
1068
|
+
#detach;
|
|
1069
|
+
#pingTimer;
|
|
1070
|
+
constructor() {
|
|
1071
|
+
super();
|
|
1072
|
+
this.#root = this.attachShadow({ mode: "open" });
|
|
1073
|
+
}
|
|
1074
|
+
/** One of `@irtio/client`'s statuses. Anything else renders as a grey dot with that text. */
|
|
1075
|
+
get status() {
|
|
1076
|
+
return this.getAttribute("status") ?? "connecting";
|
|
1077
|
+
}
|
|
1078
|
+
set status(value) {
|
|
1079
|
+
this.setAttribute("status", value);
|
|
1080
|
+
}
|
|
1081
|
+
/** The latest round-trip time in milliseconds, if known. */
|
|
1082
|
+
get ping() {
|
|
1083
|
+
if (!this.hasAttribute("ping")) return void 0;
|
|
1084
|
+
const value = Number(this.getAttribute("ping"));
|
|
1085
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1086
|
+
}
|
|
1087
|
+
set ping(value) {
|
|
1088
|
+
if (value === void 0) this.removeAttribute("ping");
|
|
1089
|
+
else this.setAttribute("ping", String(value));
|
|
1090
|
+
}
|
|
1091
|
+
/** Whether the ping is rendered next to the dot. Mirrors the `show-ping` boolean attribute. */
|
|
1092
|
+
get showPing() {
|
|
1093
|
+
return this.hasAttribute("show-ping");
|
|
1094
|
+
}
|
|
1095
|
+
set showPing(value) {
|
|
1096
|
+
if (value) this.setAttribute("show-ping", "");
|
|
1097
|
+
else this.removeAttribute("show-ping");
|
|
1098
|
+
}
|
|
1099
|
+
connectedCallback() {
|
|
1100
|
+
this.render();
|
|
1101
|
+
}
|
|
1102
|
+
attributeChangedCallback() {
|
|
1103
|
+
if (this.#root.childElementCount > 0) this.render();
|
|
1104
|
+
}
|
|
1105
|
+
disconnectedCallback() {
|
|
1106
|
+
this.#detach?.();
|
|
1107
|
+
this.#detach = void 0;
|
|
1108
|
+
this.#stopPolling();
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Drives the element from a live room: seeds `status` and `ping` and follows `status`
|
|
1112
|
+
* afterwards. For the ping, it follows the room's `'rtt'` event when the room emits one;
|
|
1113
|
+
* otherwise it polls `room.rtt` every two seconds, since not every room reports RTT as an
|
|
1114
|
+
* event. Attaching twice replaces the previous subscription and polling loop.
|
|
1115
|
+
*/
|
|
1116
|
+
attach(room) {
|
|
1117
|
+
this.#detach?.();
|
|
1118
|
+
this.#stopPolling();
|
|
1119
|
+
this.status = room.status;
|
|
1120
|
+
if (room.rtt !== void 0) this.ping = room.rtt;
|
|
1121
|
+
const offs = [];
|
|
1122
|
+
offs.push(
|
|
1123
|
+
room.on("status", (status) => {
|
|
1124
|
+
this.status = status;
|
|
1125
|
+
})
|
|
1126
|
+
);
|
|
1127
|
+
let usesRttEvent = false;
|
|
1128
|
+
try {
|
|
1129
|
+
offs.push(
|
|
1130
|
+
room.on("rtt", (rtt) => {
|
|
1131
|
+
usesRttEvent = true;
|
|
1132
|
+
this.ping = rtt;
|
|
1133
|
+
})
|
|
1134
|
+
);
|
|
1135
|
+
} catch {
|
|
1136
|
+
}
|
|
1137
|
+
this.#pingTimer = setInterval(() => {
|
|
1138
|
+
if (usesRttEvent) return;
|
|
1139
|
+
if (room.rtt !== void 0) this.ping = room.rtt;
|
|
1140
|
+
}, PING_POLL_MS);
|
|
1141
|
+
const detach = () => {
|
|
1142
|
+
for (const off of offs) off();
|
|
1143
|
+
this.#stopPolling();
|
|
1144
|
+
if (this.#detach === detach) this.#detach = void 0;
|
|
1145
|
+
};
|
|
1146
|
+
this.#detach = detach;
|
|
1147
|
+
this.render();
|
|
1148
|
+
return detach;
|
|
1149
|
+
}
|
|
1150
|
+
#stopPolling() {
|
|
1151
|
+
if (this.#pingTimer !== void 0) {
|
|
1152
|
+
clearInterval(this.#pingTimer);
|
|
1153
|
+
this.#pingTimer = void 0;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
render() {
|
|
1157
|
+
const doc = this.ownerDocument;
|
|
1158
|
+
this.#root.textContent = "";
|
|
1159
|
+
const style = doc.createElement("style");
|
|
1160
|
+
style.textContent = STYLE3;
|
|
1161
|
+
this.#root.append(style);
|
|
1162
|
+
const status = this.status;
|
|
1163
|
+
const label = STATUS_LABELS[status] ?? status;
|
|
1164
|
+
this.title = label;
|
|
1165
|
+
this.setAttribute("aria-label", label);
|
|
1166
|
+
const dot = doc.createElement("span");
|
|
1167
|
+
dot.className = "dot";
|
|
1168
|
+
dot.setAttribute("part", "dot");
|
|
1169
|
+
dot.style.background = STATUS_COLORS[status] ?? UNKNOWN_STATUS_COLOR;
|
|
1170
|
+
this.#root.append(dot);
|
|
1171
|
+
if (this.showPing) {
|
|
1172
|
+
const ping = this.ping;
|
|
1173
|
+
const pingSpan = doc.createElement("span");
|
|
1174
|
+
pingSpan.className = "ping";
|
|
1175
|
+
pingSpan.setAttribute("part", "ping");
|
|
1176
|
+
pingSpan.textContent = ping === void 0 || ping <= 0 ? "" : `${Math.round(ping)}ms`;
|
|
1177
|
+
this.#root.append(pingSpan);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
var STATUS_TAG = "irt-status";
|
|
1182
|
+
function defineStatus(tag = STATUS_TAG) {
|
|
1183
|
+
const registry = globalThis.customElements;
|
|
1184
|
+
if (!registry || registry.get(tag)) return;
|
|
1185
|
+
registry.define(tag, IrtStatusElement);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
715
1188
|
// src/index.ts
|
|
716
1189
|
defineLobby();
|
|
1190
|
+
defineStatus();
|
|
1191
|
+
defineProfile();
|
|
717
1192
|
export {
|
|
718
1193
|
IrtLobbyElement,
|
|
1194
|
+
IrtProfileElement,
|
|
1195
|
+
IrtStatusElement,
|
|
719
1196
|
LOBBY_TAG,
|
|
1197
|
+
PROFILE_TAG,
|
|
1198
|
+
STATUS_TAG,
|
|
720
1199
|
defineLobby,
|
|
1200
|
+
defineProfile,
|
|
1201
|
+
defineStatus,
|
|
721
1202
|
encodeQr
|
|
722
1203
|
};
|