@ai-matrx/meet 0.11.32 → 0.11.34
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/CHANGELOG.md +35 -0
- package/dist/core.cjs +137 -48
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +75 -6
- package/dist/core.d.ts +75 -6
- package/dist/core.js +137 -48
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +137 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +137 -48
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +481 -79
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -0
- package/dist/react.d.ts +6 -0
- package/dist/react.js +481 -79
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +217 -33
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.js +217 -33
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +19 -0
- package/package.json +2 -2
package/dist/react.js
CHANGED
|
@@ -1,6 +1,139 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
+
// src/core/tab-claim.ts
|
|
5
|
+
var TAB_ID_KEY = "mx.meet.tab";
|
|
6
|
+
var TAB_ANSWER_MS = 250;
|
|
7
|
+
function validTabId(value) {
|
|
8
|
+
return value !== null && /^[A-Za-z0-9_-]{4,64}$/.test(value);
|
|
9
|
+
}
|
|
10
|
+
function wait(ms2) {
|
|
11
|
+
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
12
|
+
}
|
|
13
|
+
function createTabClaim(platform = webTabPlatform()) {
|
|
14
|
+
let mine = null;
|
|
15
|
+
let current = null;
|
|
16
|
+
const self = platform.randomId();
|
|
17
|
+
const unsubscribe = platform.bus?.subscribe((message) => {
|
|
18
|
+
if (message.kind === "tab-alive?" && mine !== null && message.tabId === mine && message.from !== self) {
|
|
19
|
+
platform.bus?.post({ kind: "tab-alive", tabId: mine });
|
|
20
|
+
} else if (message.kind === "who-is-in?" && current !== null && mine !== null && message.from !== self && message.room === current.room && message.person === current.person) {
|
|
21
|
+
platform.bus?.post({ kind: "in-room", room: current.room, person: current.person, tabId: mine, to: message.from });
|
|
22
|
+
}
|
|
23
|
+
}) ?? (() => {
|
|
24
|
+
});
|
|
25
|
+
async function someoneAnswersFor(tabId2) {
|
|
26
|
+
const bus = platform.bus;
|
|
27
|
+
if (bus === null) return false;
|
|
28
|
+
let answered = false;
|
|
29
|
+
const stop = bus.subscribe((message) => {
|
|
30
|
+
if (message.kind === "tab-alive" && message.tabId === tabId2) answered = true;
|
|
31
|
+
});
|
|
32
|
+
bus.post({ kind: "tab-alive?", tabId: tabId2, from: self });
|
|
33
|
+
await wait(TAB_ANSWER_MS);
|
|
34
|
+
stop();
|
|
35
|
+
return answered;
|
|
36
|
+
}
|
|
37
|
+
async function claim() {
|
|
38
|
+
const stored = platform.storage?.get(TAB_ID_KEY) ?? null;
|
|
39
|
+
let candidate = validTabId(stored) ? stored : platform.randomId();
|
|
40
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
41
|
+
const held = await platform.tryLock(`${TAB_ID_KEY}:${candidate}`);
|
|
42
|
+
if (held) break;
|
|
43
|
+
if (!await someoneAnswersFor(candidate)) break;
|
|
44
|
+
candidate = platform.randomId();
|
|
45
|
+
}
|
|
46
|
+
mine = candidate;
|
|
47
|
+
platform.storage?.set(TAB_ID_KEY, candidate);
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
const tabId = claim();
|
|
51
|
+
return {
|
|
52
|
+
tabId,
|
|
53
|
+
inRoom(room, person) {
|
|
54
|
+
current = { room, person };
|
|
55
|
+
},
|
|
56
|
+
left() {
|
|
57
|
+
current = null;
|
|
58
|
+
},
|
|
59
|
+
async presentElsewhere(room, person) {
|
|
60
|
+
const bus = platform.bus;
|
|
61
|
+
if (bus === null) return false;
|
|
62
|
+
let seen = false;
|
|
63
|
+
const stop = bus.subscribe((message) => {
|
|
64
|
+
if (message.kind === "in-room" && message.to === self && message.room === room && message.person === person) {
|
|
65
|
+
seen = true;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
bus.post({ kind: "who-is-in?", room, person, from: self });
|
|
69
|
+
await wait(TAB_ANSWER_MS);
|
|
70
|
+
stop();
|
|
71
|
+
return seen;
|
|
72
|
+
},
|
|
73
|
+
dispose() {
|
|
74
|
+
unsubscribe();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function randomTabId() {
|
|
79
|
+
const crypto = globalThis.crypto;
|
|
80
|
+
const raw = crypto?.randomUUID?.() ?? `${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
81
|
+
return raw.replace(/[^A-Za-z0-9]/g, "").slice(0, 16);
|
|
82
|
+
}
|
|
83
|
+
function webTabPlatform() {
|
|
84
|
+
const g = globalThis;
|
|
85
|
+
const session = g.sessionStorage;
|
|
86
|
+
const storage = session === void 0 ? null : {
|
|
87
|
+
get(key) {
|
|
88
|
+
try {
|
|
89
|
+
return session.getItem(key);
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
set(key, value) {
|
|
95
|
+
try {
|
|
96
|
+
session.setItem(key, value);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const channel = g.BroadcastChannel !== void 0 ? new g.BroadcastChannel("mx.meet.tabs") : null;
|
|
102
|
+
const bus = channel === null ? null : {
|
|
103
|
+
post(message) {
|
|
104
|
+
channel.postMessage(message);
|
|
105
|
+
},
|
|
106
|
+
subscribe(listener) {
|
|
107
|
+
const handler = (event) => {
|
|
108
|
+
const data = event.data;
|
|
109
|
+
if (data !== null && typeof data === "object" && typeof data.kind === "string") listener(data);
|
|
110
|
+
};
|
|
111
|
+
channel.addEventListener("message", handler);
|
|
112
|
+
return () => channel.removeEventListener("message", handler);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
storage,
|
|
117
|
+
bus,
|
|
118
|
+
randomId: randomTabId,
|
|
119
|
+
tryLock(name) {
|
|
120
|
+
const locks = g.navigator?.locks;
|
|
121
|
+
if (locks === void 0) return Promise.resolve(false);
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
locks.request(name, { ifAvailable: true }, (lock) => {
|
|
124
|
+
if (lock === null) {
|
|
125
|
+
resolve(false);
|
|
126
|
+
return Promise.resolve();
|
|
127
|
+
}
|
|
128
|
+
resolve(true);
|
|
129
|
+
return new Promise(() => {
|
|
130
|
+
});
|
|
131
|
+
}).catch(() => resolve(false));
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
4
137
|
// src/core/connection-supervisor.ts
|
|
5
138
|
var DEFAULT_CONNECTION_POLICY = {
|
|
6
139
|
bannerDelayMs: 2e3,
|
|
@@ -380,12 +513,18 @@ var MeetError = class extends Error {
|
|
|
380
513
|
* renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
|
|
381
514
|
*/
|
|
382
515
|
reason;
|
|
383
|
-
|
|
516
|
+
/**
|
|
517
|
+
* A refusal that lifts by itself says when: whole seconds from the moment the server answered
|
|
518
|
+
* (a denial's cooldown, `reknock_cooldown_seconds`). Null for every refusal that does not.
|
|
519
|
+
*/
|
|
520
|
+
retryAfterSeconds;
|
|
521
|
+
constructor(code, message, remedy, cause, reason, retryAfterSeconds) {
|
|
384
522
|
super(message);
|
|
385
523
|
this.name = "MeetError";
|
|
386
524
|
this.code = code;
|
|
387
525
|
this.remedy = remedy;
|
|
388
526
|
this.reason = reason ?? null;
|
|
527
|
+
this.retryAfterSeconds = retryAfterSeconds ?? null;
|
|
389
528
|
if (cause !== void 0) this.cause = cause;
|
|
390
529
|
}
|
|
391
530
|
/** True when retrying after the host re-establishes a session is the fix. */
|
|
@@ -719,7 +858,8 @@ function parseRoomToken(body, roomName) {
|
|
|
719
858
|
const serverUrl = readString(payload, "server_url") ?? readString(payload, "serverUrl");
|
|
720
859
|
const identity = readString(payload, "identity");
|
|
721
860
|
const expiresAt = readString(payload, "expires_at") ?? readString(payload, "expiresAt");
|
|
722
|
-
|
|
861
|
+
const secondDeviceChoice = readString(payload, "second_device_choice");
|
|
862
|
+
if (token === null && secondDeviceChoice === null || serverUrl === null || identity === null) {
|
|
723
863
|
throw invalidResponse(
|
|
724
864
|
"meet.token",
|
|
725
865
|
"the response is missing token, server_url, or identity \u2014 joining with a partial token would fail inside LiveKit with an opaque error instead of here"
|
|
@@ -731,11 +871,15 @@ function parseRoomToken(body, roomName) {
|
|
|
731
871
|
"the response carries no expires_at. Without it the package cannot refresh before the token dies, and a long meeting silently loses its ability to reconnect"
|
|
732
872
|
);
|
|
733
873
|
}
|
|
874
|
+
const personKey = readString(payload, "person_key") ?? identity;
|
|
734
875
|
return {
|
|
735
|
-
token,
|
|
876
|
+
token: token ?? "",
|
|
736
877
|
serverUrl,
|
|
737
878
|
roomName,
|
|
738
|
-
identity,
|
|
879
|
+
identity: personKey,
|
|
880
|
+
connectionIdentity: readString(payload, "connection_identity") ?? personKey,
|
|
881
|
+
connectionKind: readString(payload, "connection_kind") === "companion" ? "companion" : "main",
|
|
882
|
+
secondDeviceChoice,
|
|
739
883
|
displayName: readString(payload, "display_name") ?? readString(payload, "displayName") ?? identity,
|
|
740
884
|
expiresAt,
|
|
741
885
|
canPublish: readBoolean(payload, "can_publish", true),
|
|
@@ -810,7 +954,9 @@ function createTokenClient(options) {
|
|
|
810
954
|
organization_id: input.identity.organizationId,
|
|
811
955
|
display_name: input.identity.displayName,
|
|
812
956
|
device_id: input.identity.deviceId,
|
|
813
|
-
guest
|
|
957
|
+
guest,
|
|
958
|
+
...input.tabId !== void 0 ? { tab_id: input.tabId } : {},
|
|
959
|
+
...input.secondDevice !== void 0 ? { second_device: input.secondDevice } : {}
|
|
814
960
|
}
|
|
815
961
|
});
|
|
816
962
|
} catch (error) {
|
|
@@ -827,7 +973,8 @@ function createTokenClient(options) {
|
|
|
827
973
|
said?.message ?? "You can't join this meeting.",
|
|
828
974
|
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again.",
|
|
829
975
|
void 0,
|
|
830
|
-
said?.reason ?? null
|
|
976
|
+
said?.reason ?? null,
|
|
977
|
+
said?.retryAfterSeconds ?? null
|
|
831
978
|
);
|
|
832
979
|
}
|
|
833
980
|
if (response.status === 404) {
|
|
@@ -885,6 +1032,10 @@ function createTokenClient(options) {
|
|
|
885
1032
|
if (!expired(entry)) return Promise.resolve(entry.token);
|
|
886
1033
|
}
|
|
887
1034
|
const pending = request(input, true).then((token) => {
|
|
1035
|
+
if (token.secondDeviceChoice != null) {
|
|
1036
|
+
cache.delete(input.roomName);
|
|
1037
|
+
return token;
|
|
1038
|
+
}
|
|
888
1039
|
cache.set(input.roomName, { token, mintedAt: now(), inFlight: null });
|
|
889
1040
|
remember(token);
|
|
890
1041
|
return token;
|
|
@@ -919,6 +1070,9 @@ function placeholder(roomName) {
|
|
|
919
1070
|
serverUrl: "",
|
|
920
1071
|
roomName,
|
|
921
1072
|
identity: "",
|
|
1073
|
+
connectionIdentity: "",
|
|
1074
|
+
connectionKind: "main",
|
|
1075
|
+
secondDeviceChoice: null,
|
|
922
1076
|
displayName: "",
|
|
923
1077
|
expiresAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
924
1078
|
canPublish: false,
|
|
@@ -943,7 +1097,9 @@ async function serverSentence(response) {
|
|
|
943
1097
|
if (message === null) return null;
|
|
944
1098
|
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
945
1099
|
const reason = typeof source.reason === "string" && source.reason !== "" ? source.reason : null;
|
|
946
|
-
|
|
1100
|
+
const retry = source.retry_after_seconds;
|
|
1101
|
+
const retryAfterSeconds = typeof retry === "number" && Number.isFinite(retry) && retry > 0 ? Math.ceil(retry) : null;
|
|
1102
|
+
return { message, remedy, reason, retryAfterSeconds };
|
|
947
1103
|
} catch {
|
|
948
1104
|
return null;
|
|
949
1105
|
}
|
|
@@ -1394,7 +1550,10 @@ var JOINABLE_FROM = /* @__PURE__ */ new Set([
|
|
|
1394
1550
|
"superseded",
|
|
1395
1551
|
"inactive",
|
|
1396
1552
|
"denied",
|
|
1397
|
-
"knock_expired"
|
|
1553
|
+
"knock_expired",
|
|
1554
|
+
// A refused knock whose rule lets the person ask again (a cooldown that ran out): the machine
|
|
1555
|
+
// allows the press; the server's gate decides whether it knocks or is refused again.
|
|
1556
|
+
"refused:lobby_rejects"
|
|
1398
1557
|
]);
|
|
1399
1558
|
var NOT_ENDABLE = /* @__PURE__ */ new Set([
|
|
1400
1559
|
"ended",
|
|
@@ -1478,6 +1637,8 @@ function nextPhase(from, event) {
|
|
|
1478
1637
|
return WAITING_PHASES.has(from) ? "joining" : null;
|
|
1479
1638
|
case "connected":
|
|
1480
1639
|
return from === "joining" ? "in_call" : null;
|
|
1640
|
+
case "second_device":
|
|
1641
|
+
return from === "joining" ? "prejoin" : null;
|
|
1481
1642
|
case "join_failed":
|
|
1482
1643
|
return from === "joining" ? refusalPhase(event.reason) : null;
|
|
1483
1644
|
case "exit":
|
|
@@ -1495,7 +1656,7 @@ function nextPhase(from, event) {
|
|
|
1495
1656
|
case "call_outcome":
|
|
1496
1657
|
return RINGING.has(from) ? event.outcome : null;
|
|
1497
1658
|
case "reset":
|
|
1498
|
-
return LIVE_PHASES.has(from) ? null : "resolving";
|
|
1659
|
+
return LIVE_PHASES.has(from) && event.teardown !== true ? null : "resolving";
|
|
1499
1660
|
}
|
|
1500
1661
|
}
|
|
1501
1662
|
function resolveExit(input) {
|
|
@@ -1801,8 +1962,8 @@ function createCallCenter(options) {
|
|
|
1801
1962
|
"A late accept must not resurrect a settled call. Close the ringing UI; the caller has already moved on."
|
|
1802
1963
|
);
|
|
1803
1964
|
}
|
|
1804
|
-
if (options.store.snapshot().phase === "ringing_in") options.store.dispatch({ type: "call_answered" });
|
|
1805
1965
|
const settled = await repository.settleCallInvite(id, "connecting", null);
|
|
1966
|
+
if (options.store.snapshot().phase === "ringing_in") options.store.dispatch({ type: "call_answered" });
|
|
1806
1967
|
options.store.patch({ call: { invite: settled, direction: "incoming" } });
|
|
1807
1968
|
settleLocal(settled);
|
|
1808
1969
|
channel?.send(MEET_EVENTS.settled, settled);
|
|
@@ -2013,11 +2174,17 @@ function createDeviceManager(options) {
|
|
|
2013
2174
|
}
|
|
2014
2175
|
|
|
2015
2176
|
// src/core/names.ts
|
|
2177
|
+
var CONNECTION_SEPARATOR = "~";
|
|
2178
|
+
function personOf(identity) {
|
|
2179
|
+
const value = String(identity ?? "").trim();
|
|
2180
|
+
const cut = value.indexOf(CONNECTION_SEPARATOR);
|
|
2181
|
+
return cut === -1 ? value : value.slice(0, cut);
|
|
2182
|
+
}
|
|
2016
2183
|
function isGuestIdentity(identity) {
|
|
2017
|
-
return
|
|
2184
|
+
return personOf(identity).startsWith("guest:");
|
|
2018
2185
|
}
|
|
2019
2186
|
function isAgentIdentity(identity) {
|
|
2020
|
-
return
|
|
2187
|
+
return personOf(identity).startsWith("agent:");
|
|
2021
2188
|
}
|
|
2022
2189
|
var NOTE_TAKER_PRODUCT_NAME = "Matrx note-taker";
|
|
2023
2190
|
var AGENT_PRODUCT_NAME = "Matrx agent";
|
|
@@ -3617,6 +3784,7 @@ var EMPTY = {
|
|
|
3617
3784
|
waiting: null,
|
|
3618
3785
|
meeting: null,
|
|
3619
3786
|
localIdentity: null,
|
|
3787
|
+
secondDevice: null,
|
|
3620
3788
|
grants: NO_GRANTS,
|
|
3621
3789
|
participants: [],
|
|
3622
3790
|
activeSpeakers: [],
|
|
@@ -3867,7 +4035,8 @@ function createMeetStore(initial = {}) {
|
|
|
3867
4035
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
3868
4036
|
},
|
|
3869
4037
|
reset() {
|
|
3870
|
-
|
|
4038
|
+
const phase = nextPhase(state.phase, { type: "reset", teardown: true }) ?? EMPTY.phase;
|
|
4039
|
+
commit({ ...EMPTY, effects: state.effects, phase, reason: reasonOf(phase), phaseNotice: null });
|
|
3871
4040
|
}
|
|
3872
4041
|
};
|
|
3873
4042
|
}
|
|
@@ -3900,6 +4069,28 @@ function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
|
3900
4069
|
decidedBy: null
|
|
3901
4070
|
};
|
|
3902
4071
|
}
|
|
4072
|
+
function deniedEntryFrom(attendee) {
|
|
4073
|
+
if (attendee.admissionState !== "denied") return null;
|
|
4074
|
+
return {
|
|
4075
|
+
identity: attendee.identity,
|
|
4076
|
+
displayName: attendee.displayName,
|
|
4077
|
+
avatarUrl: attendee.avatarUrl,
|
|
4078
|
+
userId: attendee.userId,
|
|
4079
|
+
state: "denied",
|
|
4080
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4081
|
+
knockExpiresAt: null,
|
|
4082
|
+
decidedAt: null,
|
|
4083
|
+
decidedBy: null
|
|
4084
|
+
};
|
|
4085
|
+
}
|
|
4086
|
+
function applyDoorRow(store, attendee) {
|
|
4087
|
+
const entry = lobbyEntryFrom(attendee) ?? deniedEntryFrom(attendee);
|
|
4088
|
+
if (entry !== null) {
|
|
4089
|
+
store.upsertLobby(entry);
|
|
4090
|
+
return;
|
|
4091
|
+
}
|
|
4092
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4093
|
+
}
|
|
3903
4094
|
function decidedState(state) {
|
|
3904
4095
|
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
3905
4096
|
}
|
|
@@ -4034,6 +4225,7 @@ function createMeetingSession(options) {
|
|
|
4034
4225
|
admission,
|
|
4035
4226
|
admittersPresent: read.admitters_present ?? null,
|
|
4036
4227
|
reknockAllowed: read.reknock_allowed ?? true,
|
|
4228
|
+
reknockAt: typeof read.reknock_in_seconds === "number" && read.reknock_in_seconds > 0 ? Date.now() + read.reknock_in_seconds * 1e3 : null,
|
|
4037
4229
|
knockExpiresAt: read.knock_expires_at ?? null,
|
|
4038
4230
|
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
4039
4231
|
liveVersion: read.live_version ?? 0
|
|
@@ -4116,11 +4308,7 @@ function createMeetingSession(options) {
|
|
|
4116
4308
|
try {
|
|
4117
4309
|
const attendees = await repository.attendees(meeting.id);
|
|
4118
4310
|
store.learn(attendees);
|
|
4119
|
-
for (const attendee of attendees)
|
|
4120
|
-
const entry = lobbyEntryFrom(attendee);
|
|
4121
|
-
if (entry !== null) store.upsertLobby(entry);
|
|
4122
|
-
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4123
|
-
}
|
|
4311
|
+
for (const attendee of attendees) applyDoorRow(store, attendee);
|
|
4124
4312
|
} catch {
|
|
4125
4313
|
}
|
|
4126
4314
|
}
|
|
@@ -4202,12 +4390,7 @@ function createMeetingSession(options) {
|
|
|
4202
4390
|
if (row === null) return;
|
|
4203
4391
|
const attendee = repository.projectAttendee(row);
|
|
4204
4392
|
store.learn([attendee]);
|
|
4205
|
-
|
|
4206
|
-
if (entry !== null) {
|
|
4207
|
-
store.upsertLobby(entry);
|
|
4208
|
-
return;
|
|
4209
|
-
}
|
|
4210
|
-
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4393
|
+
applyDoorRow(store, attendee);
|
|
4211
4394
|
}
|
|
4212
4395
|
},
|
|
4213
4396
|
{
|
|
@@ -4254,14 +4437,7 @@ function createMeetingSession(options) {
|
|
|
4254
4437
|
store.setRecording(latest.state, latest.startedBy, "record", latest.id);
|
|
4255
4438
|
}
|
|
4256
4439
|
store.learn(attendees);
|
|
4257
|
-
for (const attendee of attendees)
|
|
4258
|
-
const entry = lobbyEntryFrom(attendee);
|
|
4259
|
-
if (entry !== null) {
|
|
4260
|
-
store.upsertLobby(entry);
|
|
4261
|
-
} else {
|
|
4262
|
-
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4263
|
-
}
|
|
4264
|
-
}
|
|
4440
|
+
for (const attendee of attendees) applyDoorRow(store, attendee);
|
|
4265
4441
|
}
|
|
4266
4442
|
});
|
|
4267
4443
|
meetingChannel.requestBackfill("manual");
|
|
@@ -4313,9 +4489,9 @@ function createMeetingSession(options) {
|
|
|
4313
4489
|
},
|
|
4314
4490
|
stopWaiting,
|
|
4315
4491
|
readState: readState2,
|
|
4316
|
-
async readExit(connection) {
|
|
4492
|
+
async readExit(connection, held) {
|
|
4317
4493
|
const meeting = store.snapshot().meeting;
|
|
4318
|
-
const pass = options.resolvePass?.() ?? null;
|
|
4494
|
+
const pass = held ?? options.resolvePass?.() ?? null;
|
|
4319
4495
|
if (meeting === null || pass === null) return void 0;
|
|
4320
4496
|
try {
|
|
4321
4497
|
const response = await options.api.request({
|
|
@@ -5165,6 +5341,7 @@ function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
|
|
|
5165
5341
|
}
|
|
5166
5342
|
var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
|
|
5167
5343
|
var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
|
|
5344
|
+
var CONNECTION_KIND_ATTRIBUTE = "matrx.participant.connection_kind";
|
|
5168
5345
|
var PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
|
|
5169
5346
|
var KNOWN_STANDINGS = ["member", "invited", "outsider", "guest"];
|
|
5170
5347
|
function parseStanding(value) {
|
|
@@ -5188,7 +5365,8 @@ function projectParticipant(driver, joinedAt) {
|
|
|
5188
5365
|
}
|
|
5189
5366
|
const userId = typeof metadata.user_id === "string" ? asUserId(metadata.user_id) : null;
|
|
5190
5367
|
return {
|
|
5191
|
-
|
|
5368
|
+
// S7: a tile is a PERSON; LiveKit names the tab's connection (`<person>~<tab>`).
|
|
5369
|
+
identity: personOf(driver.identity),
|
|
5192
5370
|
userId,
|
|
5193
5371
|
// 🚨 A RAW LIVEKIT IDENTITY IS NEVER A NAME (MRI-A18). Through 0.4.12 this
|
|
5194
5372
|
// line read `driver.name.length > 0 ? driver.name : driver.identity`, and
|
|
@@ -5235,6 +5413,11 @@ function createRoomEngine(options) {
|
|
|
5235
5413
|
let ghosts = /* @__PURE__ */ new Map();
|
|
5236
5414
|
let ghostTimer = null;
|
|
5237
5415
|
let liveRoster = [];
|
|
5416
|
+
let personConnections = /* @__PURE__ */ new Map();
|
|
5417
|
+
let mainConnection = /* @__PURE__ */ new Map();
|
|
5418
|
+
function connectionsOf(who) {
|
|
5419
|
+
return personConnections.get(personOf(who)) ?? [who];
|
|
5420
|
+
}
|
|
5238
5421
|
const departures = /* @__PURE__ */ new Map();
|
|
5239
5422
|
function requestAsNow() {
|
|
5240
5423
|
if (lastRequest === null) return null;
|
|
@@ -5299,7 +5482,12 @@ function createRoomEngine(options) {
|
|
|
5299
5482
|
function failJoin(error, operation) {
|
|
5300
5483
|
const normalized = normalizeMeetError(error, operation);
|
|
5301
5484
|
store.patch({
|
|
5302
|
-
failure: {
|
|
5485
|
+
failure: {
|
|
5486
|
+
message: normalized.message,
|
|
5487
|
+
remedy: normalized.remedy,
|
|
5488
|
+
reason: normalized.reason,
|
|
5489
|
+
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
|
|
5490
|
+
}
|
|
5303
5491
|
});
|
|
5304
5492
|
store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
|
|
5305
5493
|
report(
|
|
@@ -5371,10 +5559,10 @@ function createRoomEngine(options) {
|
|
|
5371
5559
|
if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
|
|
5372
5560
|
},
|
|
5373
5561
|
activeSpeakers(identities) {
|
|
5374
|
-
store.setSpeaking(identities.map(
|
|
5562
|
+
store.setSpeaking([...new Set(identities.map((who) => personOf(who)))]);
|
|
5375
5563
|
},
|
|
5376
5564
|
quality(who, value) {
|
|
5377
|
-
store.setQuality(
|
|
5565
|
+
store.setQuality(personOf(who), value);
|
|
5378
5566
|
},
|
|
5379
5567
|
connectionState(state) {
|
|
5380
5568
|
supervisor.driverState(state);
|
|
@@ -5394,7 +5582,7 @@ function createRoomEngine(options) {
|
|
|
5394
5582
|
);
|
|
5395
5583
|
return;
|
|
5396
5584
|
}
|
|
5397
|
-
const speakerIdentity =
|
|
5585
|
+
const speakerIdentity = personOf(event.speakerIdentity);
|
|
5398
5586
|
const speaker = snapshot.participants.find(
|
|
5399
5587
|
(participant) => participant.identity === speakerIdentity
|
|
5400
5588
|
);
|
|
@@ -5468,7 +5656,7 @@ function createRoomEngine(options) {
|
|
|
5468
5656
|
);
|
|
5469
5657
|
return;
|
|
5470
5658
|
}
|
|
5471
|
-
const sender =
|
|
5659
|
+
const sender = personOf(from ?? "");
|
|
5472
5660
|
switch (topic) {
|
|
5473
5661
|
case DATA_TOPICS.hand:
|
|
5474
5662
|
store.setHand(
|
|
@@ -5510,7 +5698,8 @@ function createRoomEngine(options) {
|
|
|
5510
5698
|
}
|
|
5511
5699
|
};
|
|
5512
5700
|
async function resolveExitAndApply(hint) {
|
|
5513
|
-
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
5701
|
+
const connection = current?.connectionIdentity ?? current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
5702
|
+
const heldPass = current?.meetingPass ?? null;
|
|
5514
5703
|
lastRequest = requestAsNow();
|
|
5515
5704
|
await dropRoom();
|
|
5516
5705
|
let server;
|
|
@@ -5518,7 +5707,7 @@ function createRoomEngine(options) {
|
|
|
5518
5707
|
server = void 0;
|
|
5519
5708
|
} else {
|
|
5520
5709
|
try {
|
|
5521
|
-
server = await options.readExit(connection);
|
|
5710
|
+
server = await options.readExit(connection, heldPass);
|
|
5522
5711
|
} catch {
|
|
5523
5712
|
server = null;
|
|
5524
5713
|
}
|
|
@@ -5548,14 +5737,29 @@ function createRoomEngine(options) {
|
|
|
5548
5737
|
}
|
|
5549
5738
|
async function connect(request) {
|
|
5550
5739
|
store.patch({ failure: null, waiting: null, roomName: request.roomName, share: NO_SHARE, connection: "stable" });
|
|
5740
|
+
const tabId = options.tabClaim !== void 0 ? await options.tabClaim.tabId : null;
|
|
5551
5741
|
const token = await tokens.get({
|
|
5552
5742
|
roomName: request.roomName,
|
|
5553
5743
|
identity,
|
|
5554
5744
|
...request.meetingId !== void 0 ? { meetingId: request.meetingId } : {},
|
|
5555
5745
|
...request.slug !== void 0 ? { slug: request.slug } : {},
|
|
5556
5746
|
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {},
|
|
5557
|
-
...request.observe === true ? { observe: true } : {}
|
|
5747
|
+
...request.observe === true ? { observe: true } : {},
|
|
5748
|
+
...tabId !== null ? { tabId } : {},
|
|
5749
|
+
...request.secondDevice !== void 0 ? { secondDevice: request.secondDevice } : {}
|
|
5558
5750
|
});
|
|
5751
|
+
if (token.secondDeviceChoice != null) {
|
|
5752
|
+
store.patch({ secondDevice: { rule: token.secondDeviceChoice }, localIdentity: token.identity });
|
|
5753
|
+
store.dispatch({ type: "second_device" });
|
|
5754
|
+
report("info", `meet: already in this meeting elsewhere \u2014 offering the choice (${token.secondDeviceChoice})`);
|
|
5755
|
+
return;
|
|
5756
|
+
}
|
|
5757
|
+
if (store.snapshot().secondDevice !== null) store.patch({ secondDevice: null });
|
|
5758
|
+
if (request.secondDevice === "switch_here" && lastRequest !== null) {
|
|
5759
|
+
const { secondDevice: _answered, ...rest } = lastRequest;
|
|
5760
|
+
lastRequest = rest;
|
|
5761
|
+
}
|
|
5762
|
+
options.tabClaim?.inRoom(String(request.roomName), String(token.identity));
|
|
5559
5763
|
current = token;
|
|
5560
5764
|
lastServerUrl = token.serverUrl;
|
|
5561
5765
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
@@ -5620,7 +5824,7 @@ function createRoomEngine(options) {
|
|
|
5620
5824
|
if (request.mode === "video") {
|
|
5621
5825
|
await enableTrack(created, "camera", true);
|
|
5622
5826
|
}
|
|
5623
|
-
if (request.microphone !== false) {
|
|
5827
|
+
if (request.microphone !== false && current?.connectionKind !== "companion") {
|
|
5624
5828
|
await enableTrack(created, "microphone", true);
|
|
5625
5829
|
}
|
|
5626
5830
|
media.onConnected();
|
|
@@ -5642,7 +5846,8 @@ function createRoomEngine(options) {
|
|
|
5642
5846
|
}
|
|
5643
5847
|
async function publish(topic, body, to) {
|
|
5644
5848
|
if (driver === null) return;
|
|
5645
|
-
|
|
5849
|
+
const addresses = to === void 0 ? void 0 : to.flatMap((who) => connectionsOf(who));
|
|
5850
|
+
await driver.publishData(topic, JSON.stringify(body), addresses);
|
|
5646
5851
|
}
|
|
5647
5852
|
async function applyEffects(target, effects) {
|
|
5648
5853
|
try {
|
|
@@ -5680,8 +5885,19 @@ function createRoomEngine(options) {
|
|
|
5680
5885
|
liveRoster = list;
|
|
5681
5886
|
const merged = mergeGhosts(list, ghosts, at);
|
|
5682
5887
|
ghosts = merged.kept;
|
|
5888
|
+
const people = /* @__PURE__ */ new Map();
|
|
5889
|
+
const connections = /* @__PURE__ */ new Map();
|
|
5890
|
+
for (const entry of merged.roster) {
|
|
5891
|
+
const person = personOf(entry.identity);
|
|
5892
|
+
connections.set(person, [...connections.get(person) ?? [], entry.identity]);
|
|
5893
|
+
const rank = (entry.isLocal ? 8 : 0) + (entry.reconnecting === true ? 0 : 4) + (entry.attributes[CONNECTION_KIND_ATTRIBUTE] === "companion" ? 0 : 2) + (entry.cameraEnabled || entry.micEnabled ? 1 : 0);
|
|
5894
|
+
const held = people.get(person);
|
|
5895
|
+
if (held === void 0 || rank > held.rank) people.set(person, { entry, rank });
|
|
5896
|
+
}
|
|
5897
|
+
personConnections = connections;
|
|
5898
|
+
mainConnection = new Map([...people].map(([person, { entry }]) => [person, entry.identity]));
|
|
5683
5899
|
store.setParticipants(
|
|
5684
|
-
|
|
5900
|
+
[...people.values()].map(({ entry }) => {
|
|
5685
5901
|
const projected = projectParticipant(entry, joinedAt);
|
|
5686
5902
|
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
5687
5903
|
})
|
|
@@ -5858,7 +6074,13 @@ function createRoomEngine(options) {
|
|
|
5858
6074
|
},
|
|
5859
6075
|
async setScreenShareEnabled(enabled) {
|
|
5860
6076
|
if (driver === null) return;
|
|
5861
|
-
|
|
6077
|
+
let unsupported = false;
|
|
6078
|
+
try {
|
|
6079
|
+
unsupported = enabled && driver.supportsScreenShare?.() === false;
|
|
6080
|
+
} catch {
|
|
6081
|
+
unsupported = false;
|
|
6082
|
+
}
|
|
6083
|
+
if (unsupported) {
|
|
5862
6084
|
store.patch({ share: { state: "refused", reason: "unsupported", remedy: null, at: now() } });
|
|
5863
6085
|
report("warn", "meet: this browser cannot share a screen", "Share from a desktop browser (Chrome, Edge, Firefox or Safari).");
|
|
5864
6086
|
return;
|
|
@@ -5957,7 +6179,7 @@ function createRoomEngine(options) {
|
|
|
5957
6179
|
},
|
|
5958
6180
|
attach(who, kind, element) {
|
|
5959
6181
|
if (driver === null) return () => void 0;
|
|
5960
|
-
return driver.attach(who, kind, element);
|
|
6182
|
+
return driver.attach(mainConnection.get(personOf(who)) ?? who, kind, element);
|
|
5961
6183
|
},
|
|
5962
6184
|
token: () => current,
|
|
5963
6185
|
async dispose() {
|
|
@@ -6691,6 +6913,12 @@ function defaultDiagnostics(event) {
|
|
|
6691
6913
|
else if (event.level === "warn") console.warn(line);
|
|
6692
6914
|
else console.info(line);
|
|
6693
6915
|
}
|
|
6916
|
+
var claimedTab = null;
|
|
6917
|
+
function pageTabClaim() {
|
|
6918
|
+
if (typeof window === "undefined") return void 0;
|
|
6919
|
+
claimedTab ??= createTabClaim();
|
|
6920
|
+
return claimedTab;
|
|
6921
|
+
}
|
|
6694
6922
|
function deviceId() {
|
|
6695
6923
|
const storage = globalThis.localStorage;
|
|
6696
6924
|
const key = "mx.meet.device";
|
|
@@ -6808,13 +7036,15 @@ function MeetRuntime(props) {
|
|
|
6808
7036
|
store,
|
|
6809
7037
|
tokens,
|
|
6810
7038
|
identity,
|
|
7039
|
+
// S7: this page's tab id (one per page, claimed at load) — the LiveKit identity's tab half.
|
|
7040
|
+
tabClaim: pageTabClaim(),
|
|
6811
7041
|
resolveMeeting: (slug) => repository.meetingBySlug(slug),
|
|
6812
7042
|
driverFactory: (handlers) => (driverRef.current ?? createLiveKitDriver)(handlers),
|
|
6813
7043
|
onDiagnostic: report,
|
|
6814
7044
|
effectsStorage,
|
|
6815
7045
|
resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null,
|
|
6816
7046
|
// §3.4: on every LiveKit disconnect, ask the server what happened to this connection.
|
|
6817
|
-
readExit: (connection) => meetings.readExit(connection),
|
|
7047
|
+
readExit: (connection, pass) => meetings.readExit(connection, pass),
|
|
6818
7048
|
// S6b: the connection supervisor's ports — the device's network, whether OUR servers (the
|
|
6819
7049
|
// API and the media server) answer, and the per-tab reload-after-offline marker.
|
|
6820
7050
|
connection: {
|
|
@@ -7883,6 +8113,7 @@ function useRoom() {
|
|
|
7883
8113
|
phase: snapshot?.phase ?? "resolving",
|
|
7884
8114
|
participants: snapshot?.participants ?? [],
|
|
7885
8115
|
failure: snapshot?.failure ?? null,
|
|
8116
|
+
secondDevice: snapshot?.secondDevice ?? null,
|
|
7886
8117
|
local,
|
|
7887
8118
|
canShareScreen: engine?.supportsScreenShare() ?? true,
|
|
7888
8119
|
join: useCallback2(
|
|
@@ -8028,6 +8259,7 @@ function useLobby() {
|
|
|
8028
8259
|
const session = host?.meetings ?? null;
|
|
8029
8260
|
return {
|
|
8030
8261
|
waiting: (snapshot?.lobby ?? []).filter((entry) => entry.state === "knocking"),
|
|
8262
|
+
declined: (snapshot?.lobby ?? []).filter((entry) => entry.state === "denied"),
|
|
8031
8263
|
// No runtime means no host powers — `<LobbyPanel>` renders nothing rather
|
|
8032
8264
|
// than an admit button that would throw.
|
|
8033
8265
|
canAdmit: session !== null && isHost,
|
|
@@ -8049,6 +8281,37 @@ function useLobby() {
|
|
|
8049
8281
|
)
|
|
8050
8282
|
};
|
|
8051
8283
|
}
|
|
8284
|
+
function useCountdown(untilMs) {
|
|
8285
|
+
const [now, setNow] = useState2(() => Date.now());
|
|
8286
|
+
const active = typeof untilMs === "number" && untilMs > now;
|
|
8287
|
+
useEffect3(() => {
|
|
8288
|
+
if (!active) return void 0;
|
|
8289
|
+
setNow(Date.now());
|
|
8290
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
8291
|
+
return () => clearInterval(timer);
|
|
8292
|
+
}, [active, untilMs]);
|
|
8293
|
+
if (typeof untilMs !== "number") return null;
|
|
8294
|
+
return Math.max(0, untilMs - now);
|
|
8295
|
+
}
|
|
8296
|
+
function useKnockNotice() {
|
|
8297
|
+
const lobby = useLobby();
|
|
8298
|
+
const [dismissed, setDismissed] = useState2([]);
|
|
8299
|
+
const keyOf = (entry) => `${entry.identity}@${entry.knockedAt}`;
|
|
8300
|
+
const open = lobby.canAdmit ? lobby.waiting.filter((entry) => !dismissed.includes(keyOf(entry))) : [];
|
|
8301
|
+
const knocker = open[0] ?? null;
|
|
8302
|
+
return {
|
|
8303
|
+
knocker,
|
|
8304
|
+
others: Math.max(0, open.length - 1),
|
|
8305
|
+
admit: useCallback2(async () => {
|
|
8306
|
+
if (knocker === null) return;
|
|
8307
|
+
setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8308
|
+
await lobby.admit(knocker);
|
|
8309
|
+
}, [knocker, lobby.admit]),
|
|
8310
|
+
dismiss: useCallback2(() => {
|
|
8311
|
+
if (knocker !== null) setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8312
|
+
}, [knocker])
|
|
8313
|
+
};
|
|
8314
|
+
}
|
|
8052
8315
|
function useWaiting() {
|
|
8053
8316
|
const host = useMeetHost();
|
|
8054
8317
|
const snapshot = useMeetSnapshot();
|
|
@@ -10798,6 +11061,7 @@ function ControlBar({ onLeave }) {
|
|
|
10798
11061
|
}
|
|
10799
11062
|
) }) }) : null,
|
|
10800
11063
|
/* @__PURE__ */ jsx6(BreakoutBanner, {}),
|
|
11064
|
+
panel !== "people" ? /* @__PURE__ */ jsx6(KnockNotice, { onView: () => setPanel("people") }) : null,
|
|
10801
11065
|
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ jsx6(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
10802
11066
|
/* @__PURE__ */ jsxs3("div", { className: "mx-meet__controls", children: [
|
|
10803
11067
|
/* @__PURE__ */ jsx6(
|
|
@@ -11128,10 +11392,21 @@ function MediaNotices() {
|
|
|
11128
11392
|
const name = (kind) => kind === "microphone" ? "Microphone" : "Camera";
|
|
11129
11393
|
if (chips.length === 0 && region.audioOutput !== "blocked" && notice === null && offer === null) return null;
|
|
11130
11394
|
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-notices", children: [
|
|
11131
|
-
region.audioOutput === "blocked" ? /* @__PURE__ */ jsxs3(
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11395
|
+
region.audioOutput === "blocked" ? /* @__PURE__ */ jsxs3(
|
|
11396
|
+
ErrorBox,
|
|
11397
|
+
{
|
|
11398
|
+
as: "div",
|
|
11399
|
+
className: "mx-meet__media-pill",
|
|
11400
|
+
"data-meet-notice": "audio-blocked",
|
|
11401
|
+
message: "Sound is paused in this tab",
|
|
11402
|
+
operation: "Enable meeting sound",
|
|
11403
|
+
origin: "meet/MediaNotices",
|
|
11404
|
+
children: [
|
|
11405
|
+
/* @__PURE__ */ jsx6("span", { children: "Sound is paused in this tab" }),
|
|
11406
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void enableAudio(), children: "Enable sound" })
|
|
11407
|
+
]
|
|
11408
|
+
}
|
|
11409
|
+
) : null,
|
|
11135
11410
|
chips.map(({ kind, reason }) => /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "status", "data-meet-notice": deviceNoticeCode(kind, reason), children: [
|
|
11136
11411
|
/* @__PURE__ */ jsx6("span", { children: deviceChipLabel(kind, reason) }),
|
|
11137
11412
|
/* @__PURE__ */ jsx6(
|
|
@@ -11931,9 +12206,68 @@ function useMicrophoneStream(deviceId2) {
|
|
|
11931
12206
|
}, [deviceId2]);
|
|
11932
12207
|
return stream;
|
|
11933
12208
|
}
|
|
12209
|
+
function KnockNotice({ onView }) {
|
|
12210
|
+
const { knocker, others, admit, dismiss } = useKnockNotice();
|
|
12211
|
+
if (knocker === null) return null;
|
|
12212
|
+
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__notice-bar", role: "status", "data-meet-notice": "knock-received", "data-meet-participant-name": knocker.displayName, children: [
|
|
12213
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
12214
|
+
knocker.displayName,
|
|
12215
|
+
" wants to join",
|
|
12216
|
+
others > 0 ? ` (+${others} more)` : ""
|
|
12217
|
+
] }),
|
|
12218
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__link", onClick: () => void admit().catch(() => void 0), children: "Admit" }),
|
|
12219
|
+
/* @__PURE__ */ jsx6(
|
|
12220
|
+
"button",
|
|
12221
|
+
{
|
|
12222
|
+
type: "button",
|
|
12223
|
+
className: "mx-meet__link",
|
|
12224
|
+
onClick: () => {
|
|
12225
|
+
dismiss();
|
|
12226
|
+
onView();
|
|
12227
|
+
},
|
|
12228
|
+
children: "View"
|
|
12229
|
+
}
|
|
12230
|
+
),
|
|
12231
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
|
|
12232
|
+
] });
|
|
12233
|
+
}
|
|
11934
12234
|
function LobbyPanel() {
|
|
11935
12235
|
const lobby = useLobby();
|
|
11936
|
-
if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
|
|
12236
|
+
if (!lobby.canAdmit || lobby.waiting.length === 0 && lobby.declined.length === 0) return null;
|
|
12237
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
12238
|
+
lobby.waiting.length > 0 ? /* @__PURE__ */ jsx6(WaitingList, { lobby }) : null,
|
|
12239
|
+
lobby.declined.length > 0 ? /* @__PURE__ */ jsx6(DeclinedList, { lobby }) : null
|
|
12240
|
+
] });
|
|
12241
|
+
}
|
|
12242
|
+
function DeclinedList({ lobby }) {
|
|
12243
|
+
return /* @__PURE__ */ jsxs3("section", { className: "mx-meet__lobby", "aria-label": "Declined requests", "data-meet-declined": "", children: [
|
|
12244
|
+
/* @__PURE__ */ jsx6("header", { className: "mx-meet__lobby-head", children: /* @__PURE__ */ jsx6("h3", { className: "mx-meet__lobby-title", children: "Declined" }) }),
|
|
12245
|
+
/* @__PURE__ */ jsx6("ul", { className: "mx-meet__lobby-list", children: lobby.declined.map((entry) => /* @__PURE__ */ jsxs3(
|
|
12246
|
+
"li",
|
|
12247
|
+
{
|
|
12248
|
+
className: "mx-meet__lobby-row",
|
|
12249
|
+
"data-meet-declined-entry": "",
|
|
12250
|
+
"data-meet-participant-name": entry.displayName,
|
|
12251
|
+
children: [
|
|
12252
|
+
/* @__PURE__ */ jsx6(Avatar, { name: entry.displayName, url: entry.avatarUrl, size: 32 }),
|
|
12253
|
+
/* @__PURE__ */ jsx6("span", { className: "mx-meet__person-name", children: entry.displayName }),
|
|
12254
|
+
/* @__PURE__ */ jsx6(
|
|
12255
|
+
"button",
|
|
12256
|
+
{
|
|
12257
|
+
type: "button",
|
|
12258
|
+
className: "mx-meet__link",
|
|
12259
|
+
"aria-label": `Let ${entry.displayName} ask again`,
|
|
12260
|
+
onClick: () => void lobby.undeny(entry),
|
|
12261
|
+
children: "Let them ask again"
|
|
12262
|
+
}
|
|
12263
|
+
)
|
|
12264
|
+
]
|
|
12265
|
+
},
|
|
12266
|
+
entry.identity
|
|
12267
|
+
)) })
|
|
12268
|
+
] });
|
|
12269
|
+
}
|
|
12270
|
+
function WaitingList({ lobby }) {
|
|
11937
12271
|
return /* @__PURE__ */ jsxs3("section", { className: "mx-meet__lobby", "aria-label": "Waiting room", children: [
|
|
11938
12272
|
/* @__PURE__ */ jsxs3("header", { className: "mx-meet__lobby-head", children: [
|
|
11939
12273
|
/* @__PURE__ */ jsx6("h3", { className: "mx-meet__lobby-title", children: lobby.waiting.length === 1 ? "1 person is waiting" : `${lobby.waiting.length} people are waiting` }),
|
|
@@ -12197,6 +12531,21 @@ function PreJoinBody({
|
|
|
12197
12531
|
useEffect6(() => {
|
|
12198
12532
|
void host.devices.refresh();
|
|
12199
12533
|
}, [host]);
|
|
12534
|
+
const joinWith = (secondDevice) => {
|
|
12535
|
+
setJoining(true);
|
|
12536
|
+
preview.release();
|
|
12537
|
+
void room.join({
|
|
12538
|
+
roomName,
|
|
12539
|
+
// An observer publishes nothing: no camera, no microphone (MD-16). A companion
|
|
12540
|
+
// ("join here too") never opens the microphone (S7).
|
|
12541
|
+
mode: observe2 ? "audio" : mode,
|
|
12542
|
+
microphone: observe2 || secondDevice === "join_too" ? false : microphone,
|
|
12543
|
+
...meetingId !== void 0 ? { meetingId } : {},
|
|
12544
|
+
...slug !== void 0 ? { slug } : {},
|
|
12545
|
+
...observe2 ? { observe: true } : {},
|
|
12546
|
+
...secondDevice !== void 0 ? { secondDevice } : {}
|
|
12547
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
12548
|
+
};
|
|
12200
12549
|
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__prejoin", children: [
|
|
12201
12550
|
/* @__PURE__ */ jsxs3("div", { className: "mx-meet__preview", children: [
|
|
12202
12551
|
mode === "video" && preview.preview !== null ? /* @__PURE__ */ jsx6(
|
|
@@ -12281,27 +12630,34 @@ function PreJoinBody({
|
|
|
12281
12630
|
),
|
|
12282
12631
|
/* @__PURE__ */ jsx6(Label, { htmlFor: "mx-meet-join-mic", children: "Join with microphone" })
|
|
12283
12632
|
] }),
|
|
12284
|
-
/* @__PURE__ */ jsx6(
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
className: "mx-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
|
|
12633
|
+
room.secondDevice === null ? /* @__PURE__ */ jsx6(Button, { type: "button", className: "mx-meet__join", disabled: joining, onClick: () => joinWith(), children: joining ? "Joining\u2026" : observe2 ? "Observe" : "Join now" }) : (
|
|
12634
|
+
// S7: ALREADY IN THIS MEETING on another tab or device — the meeting's choice
|
|
12635
|
+
// (Meet: Switch here / Join here too; Teams: Transfer here / Add this device).
|
|
12636
|
+
/* @__PURE__ */ jsxs3("div", { className: "mx-meet__second-device", "data-meet-region": "second-device", children: [
|
|
12637
|
+
/* @__PURE__ */ jsx6("p", { className: "mx-meet__hint", children: "You're in this meeting on another device" }),
|
|
12638
|
+
/* @__PURE__ */ jsx6(
|
|
12639
|
+
Button,
|
|
12640
|
+
{
|
|
12641
|
+
type: "button",
|
|
12642
|
+
className: "mx-meet__join",
|
|
12643
|
+
"data-meet-control": "switch-here",
|
|
12644
|
+
disabled: joining,
|
|
12645
|
+
onClick: () => joinWith("switch_here"),
|
|
12646
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Transfer here" : "Switch here"
|
|
12647
|
+
}
|
|
12648
|
+
),
|
|
12649
|
+
/* @__PURE__ */ jsx6(
|
|
12650
|
+
Button,
|
|
12651
|
+
{
|
|
12652
|
+
type: "button",
|
|
12653
|
+
variant: "outline",
|
|
12654
|
+
"data-meet-control": "join-too",
|
|
12655
|
+
disabled: joining,
|
|
12656
|
+
onClick: () => joinWith("join_too"),
|
|
12657
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Add this device" : "Join here too"
|
|
12658
|
+
}
|
|
12659
|
+
)
|
|
12660
|
+
] })
|
|
12305
12661
|
)
|
|
12306
12662
|
] }),
|
|
12307
12663
|
room.failure !== null ? /* @__PURE__ */ jsxs3("p", { className: "mx-meet__error", children: [
|
|
@@ -12499,16 +12855,21 @@ var REFUSED_TITLES = {
|
|
|
12499
12855
|
not_invited: "You aren't invited to this meeting",
|
|
12500
12856
|
locked: "This meeting is locked",
|
|
12501
12857
|
removed: "You can't rejoin this meeting",
|
|
12502
|
-
lobby_rejects: "
|
|
12858
|
+
lobby_rejects: "The host declined your request",
|
|
12503
12859
|
meeting_full: "This meeting is full",
|
|
12504
12860
|
platform_capacity: "Meetings are busy right now",
|
|
12505
12861
|
verification_required: "Verify your account to join",
|
|
12506
12862
|
registration_required: "Register to join this meeting"
|
|
12507
12863
|
};
|
|
12864
|
+
function clockText(ms2) {
|
|
12865
|
+
const total = Math.ceil(ms2 / 1e3);
|
|
12866
|
+
return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
|
|
12867
|
+
}
|
|
12508
12868
|
function ExitScreen({ onLeave }) {
|
|
12509
12869
|
const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
|
|
12510
12870
|
const [busy, setBusy] = useState4(false);
|
|
12511
12871
|
const failure = snapshot?.failure ?? null;
|
|
12872
|
+
const retryLeft = useCountdown(phase === "refused:lobby_rejects" ? failure?.retryAt ?? null : null);
|
|
12512
12873
|
const rejoin = /* @__PURE__ */ jsx6(
|
|
12513
12874
|
Button,
|
|
12514
12875
|
{
|
|
@@ -12551,6 +12912,33 @@ function ExitScreen({ onLeave }) {
|
|
|
12551
12912
|
/* @__PURE__ */ jsx6(Button, { type: "button", variant: "outline", onClick: actions.retry, children: "Try again" }),
|
|
12552
12913
|
home
|
|
12553
12914
|
] });
|
|
12915
|
+
if (code === "lobby_rejects") {
|
|
12916
|
+
if (retryLeft !== null && retryLeft > 0) {
|
|
12917
|
+
line = `You can ask again in ${clockText(retryLeft)}`;
|
|
12918
|
+
notice = "reknock-cooldown";
|
|
12919
|
+
actionsNode = home;
|
|
12920
|
+
} else if (retryLeft === 0) {
|
|
12921
|
+
line = "You can ask to join again";
|
|
12922
|
+
actionsNode = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
12923
|
+
/* @__PURE__ */ jsx6(
|
|
12924
|
+
Button,
|
|
12925
|
+
{
|
|
12926
|
+
type: "button",
|
|
12927
|
+
disabled: busy,
|
|
12928
|
+
onClick: () => {
|
|
12929
|
+
setBusy(true);
|
|
12930
|
+
void actions.rejoin().catch(() => void 0).finally(() => setBusy(false));
|
|
12931
|
+
},
|
|
12932
|
+
children: "Ask to join again"
|
|
12933
|
+
}
|
|
12934
|
+
),
|
|
12935
|
+
home
|
|
12936
|
+
] });
|
|
12937
|
+
} else {
|
|
12938
|
+
line = "Ask the host to let you in, then open the link again";
|
|
12939
|
+
actionsNode = home;
|
|
12940
|
+
}
|
|
12941
|
+
}
|
|
12554
12942
|
} else {
|
|
12555
12943
|
switch (phase) {
|
|
12556
12944
|
case "left":
|
|
@@ -12611,7 +12999,7 @@ function ExitScreen({ onLeave }) {
|
|
|
12611
12999
|
/* @__PURE__ */ jsx6(Avatar, { name: host?.identity.displayName ?? "", url: host?.identity.avatarUrl ?? null, size: 72 }),
|
|
12612
13000
|
/* @__PURE__ */ jsx6("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12613
13001
|
line !== null ? /* @__PURE__ */ jsx6("p", { className: "mx-meet__empty-body", children: line }) : null,
|
|
12614
|
-
notice !== null ? /* @__PURE__ */ jsx6("span", { role: "status", "data-meet-notice": notice, className: "mx-meet__sr-only", children: title }) : null,
|
|
13002
|
+
notice !== null ? /* @__PURE__ */ jsx6("span", { role: "status", "data-meet-notice": notice, className: "mx-meet__sr-only", children: notice === "reknock-cooldown" && line !== null ? line : title }) : null,
|
|
12615
13003
|
/* @__PURE__ */ jsx6("div", { className: "mx-meet__waiting-actions", children: actionsNode })
|
|
12616
13004
|
] });
|
|
12617
13005
|
}
|
|
@@ -12620,6 +13008,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12620
13008
|
const { phaseNotice } = useMeetCore();
|
|
12621
13009
|
const [busy, setBusy] = useState4(false);
|
|
12622
13010
|
const admission = waiting?.admission ?? "knocking";
|
|
13011
|
+
const reknockLeft = useCountdown(admission === "denied" ? waiting?.reknockAt ?? null : null);
|
|
12623
13012
|
const run = (action) => () => {
|
|
12624
13013
|
setBusy(true);
|
|
12625
13014
|
void action().catch(() => void 0).finally(() => setBusy(false));
|
|
@@ -12627,6 +13016,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12627
13016
|
const home = /* @__PURE__ */ jsx6(Button, { type: "button", variant: "outline", onClick: () => onLeave !== void 0 ? onLeave() : window.location.assign("/"), children: "Back to home" });
|
|
12628
13017
|
let title;
|
|
12629
13018
|
let line = null;
|
|
13019
|
+
let cooldownNotice = false;
|
|
12630
13020
|
let actions;
|
|
12631
13021
|
switch (admission) {
|
|
12632
13022
|
case "waiting_host":
|
|
@@ -12635,8 +13025,12 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12635
13025
|
break;
|
|
12636
13026
|
case "denied":
|
|
12637
13027
|
title = "The host declined your request";
|
|
13028
|
+
if (reknockLeft !== null && reknockLeft > 0) {
|
|
13029
|
+
line = `You can ask again in ${clockText(reknockLeft)}`;
|
|
13030
|
+
cooldownNotice = true;
|
|
13031
|
+
}
|
|
12638
13032
|
actions = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
12639
|
-
waiting?.reknockAllowed === true ? /* @__PURE__ */ jsx6(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
|
|
13033
|
+
waiting?.reknockAllowed === true || waiting?.reknockAt != null && reknockLeft === 0 ? /* @__PURE__ */ jsx6(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
|
|
12640
13034
|
home
|
|
12641
13035
|
] });
|
|
12642
13036
|
break;
|
|
@@ -12667,7 +13061,15 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12667
13061
|
/* @__PURE__ */ jsx6(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
|
|
12668
13062
|
/* @__PURE__ */ jsx6("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12669
13063
|
phaseNotice === "sent-to-waiting" ? /* @__PURE__ */ jsx6("p", { className: "mx-meet__empty-body", role: "status", "data-meet-notice": "sent-to-waiting", children: "The host moved you back to the waiting room" }) : null,
|
|
12670
|
-
line !== null ? /* @__PURE__ */ jsx6(
|
|
13064
|
+
line !== null ? /* @__PURE__ */ jsx6(
|
|
13065
|
+
"p",
|
|
13066
|
+
{
|
|
13067
|
+
className: "mx-meet__empty-body",
|
|
13068
|
+
role: "status",
|
|
13069
|
+
"data-meet-notice": cooldownNotice ? "reknock-cooldown" : "host-absent",
|
|
13070
|
+
children: line
|
|
13071
|
+
}
|
|
13072
|
+
) : null,
|
|
12671
13073
|
/* @__PURE__ */ jsx6("div", { className: "mx-meet__waiting-actions", children: actions })
|
|
12672
13074
|
] });
|
|
12673
13075
|
}
|