@ai-matrx/meet 0.11.31 → 0.11.33
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 +27 -0
- package/dist/core.cjs +133 -45
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +73 -4
- package/dist/core.d.ts +73 -4
- package/dist/core.js +133 -45
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +133 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +133 -45
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +476 -75
- 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 +476 -75
- 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/package.json +3 -3
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");
|
|
@@ -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,7 @@ 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;
|
|
5514
5702
|
lastRequest = requestAsNow();
|
|
5515
5703
|
await dropRoom();
|
|
5516
5704
|
let server;
|
|
@@ -5548,14 +5736,29 @@ function createRoomEngine(options) {
|
|
|
5548
5736
|
}
|
|
5549
5737
|
async function connect(request) {
|
|
5550
5738
|
store.patch({ failure: null, waiting: null, roomName: request.roomName, share: NO_SHARE, connection: "stable" });
|
|
5739
|
+
const tabId = options.tabClaim !== void 0 ? await options.tabClaim.tabId : null;
|
|
5551
5740
|
const token = await tokens.get({
|
|
5552
5741
|
roomName: request.roomName,
|
|
5553
5742
|
identity,
|
|
5554
5743
|
...request.meetingId !== void 0 ? { meetingId: request.meetingId } : {},
|
|
5555
5744
|
...request.slug !== void 0 ? { slug: request.slug } : {},
|
|
5556
5745
|
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {},
|
|
5557
|
-
...request.observe === true ? { observe: true } : {}
|
|
5746
|
+
...request.observe === true ? { observe: true } : {},
|
|
5747
|
+
...tabId !== null ? { tabId } : {},
|
|
5748
|
+
...request.secondDevice !== void 0 ? { secondDevice: request.secondDevice } : {}
|
|
5558
5749
|
});
|
|
5750
|
+
if (token.secondDeviceChoice != null) {
|
|
5751
|
+
store.patch({ secondDevice: { rule: token.secondDeviceChoice }, localIdentity: token.identity });
|
|
5752
|
+
store.dispatch({ type: "second_device" });
|
|
5753
|
+
report("info", `meet: already in this meeting elsewhere \u2014 offering the choice (${token.secondDeviceChoice})`);
|
|
5754
|
+
return;
|
|
5755
|
+
}
|
|
5756
|
+
if (store.snapshot().secondDevice !== null) store.patch({ secondDevice: null });
|
|
5757
|
+
if (request.secondDevice === "switch_here" && lastRequest !== null) {
|
|
5758
|
+
const { secondDevice: _answered, ...rest } = lastRequest;
|
|
5759
|
+
lastRequest = rest;
|
|
5760
|
+
}
|
|
5761
|
+
options.tabClaim?.inRoom(String(request.roomName), String(token.identity));
|
|
5559
5762
|
current = token;
|
|
5560
5763
|
lastServerUrl = token.serverUrl;
|
|
5561
5764
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
@@ -5620,7 +5823,7 @@ function createRoomEngine(options) {
|
|
|
5620
5823
|
if (request.mode === "video") {
|
|
5621
5824
|
await enableTrack(created, "camera", true);
|
|
5622
5825
|
}
|
|
5623
|
-
if (request.microphone !== false) {
|
|
5826
|
+
if (request.microphone !== false && current?.connectionKind !== "companion") {
|
|
5624
5827
|
await enableTrack(created, "microphone", true);
|
|
5625
5828
|
}
|
|
5626
5829
|
media.onConnected();
|
|
@@ -5642,7 +5845,8 @@ function createRoomEngine(options) {
|
|
|
5642
5845
|
}
|
|
5643
5846
|
async function publish(topic, body, to) {
|
|
5644
5847
|
if (driver === null) return;
|
|
5645
|
-
|
|
5848
|
+
const addresses = to === void 0 ? void 0 : to.flatMap((who) => connectionsOf(who));
|
|
5849
|
+
await driver.publishData(topic, JSON.stringify(body), addresses);
|
|
5646
5850
|
}
|
|
5647
5851
|
async function applyEffects(target, effects) {
|
|
5648
5852
|
try {
|
|
@@ -5680,8 +5884,19 @@ function createRoomEngine(options) {
|
|
|
5680
5884
|
liveRoster = list;
|
|
5681
5885
|
const merged = mergeGhosts(list, ghosts, at);
|
|
5682
5886
|
ghosts = merged.kept;
|
|
5887
|
+
const people = /* @__PURE__ */ new Map();
|
|
5888
|
+
const connections = /* @__PURE__ */ new Map();
|
|
5889
|
+
for (const entry of merged.roster) {
|
|
5890
|
+
const person = personOf(entry.identity);
|
|
5891
|
+
connections.set(person, [...connections.get(person) ?? [], entry.identity]);
|
|
5892
|
+
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);
|
|
5893
|
+
const held = people.get(person);
|
|
5894
|
+
if (held === void 0 || rank > held.rank) people.set(person, { entry, rank });
|
|
5895
|
+
}
|
|
5896
|
+
personConnections = connections;
|
|
5897
|
+
mainConnection = new Map([...people].map(([person, { entry }]) => [person, entry.identity]));
|
|
5683
5898
|
store.setParticipants(
|
|
5684
|
-
|
|
5899
|
+
[...people.values()].map(({ entry }) => {
|
|
5685
5900
|
const projected = projectParticipant(entry, joinedAt);
|
|
5686
5901
|
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
5687
5902
|
})
|
|
@@ -5858,7 +6073,13 @@ function createRoomEngine(options) {
|
|
|
5858
6073
|
},
|
|
5859
6074
|
async setScreenShareEnabled(enabled) {
|
|
5860
6075
|
if (driver === null) return;
|
|
5861
|
-
|
|
6076
|
+
let unsupported = false;
|
|
6077
|
+
try {
|
|
6078
|
+
unsupported = enabled && driver.supportsScreenShare?.() === false;
|
|
6079
|
+
} catch {
|
|
6080
|
+
unsupported = false;
|
|
6081
|
+
}
|
|
6082
|
+
if (unsupported) {
|
|
5862
6083
|
store.patch({ share: { state: "refused", reason: "unsupported", remedy: null, at: now() } });
|
|
5863
6084
|
report("warn", "meet: this browser cannot share a screen", "Share from a desktop browser (Chrome, Edge, Firefox or Safari).");
|
|
5864
6085
|
return;
|
|
@@ -5957,7 +6178,7 @@ function createRoomEngine(options) {
|
|
|
5957
6178
|
},
|
|
5958
6179
|
attach(who, kind, element) {
|
|
5959
6180
|
if (driver === null) return () => void 0;
|
|
5960
|
-
return driver.attach(who, kind, element);
|
|
6181
|
+
return driver.attach(mainConnection.get(personOf(who)) ?? who, kind, element);
|
|
5961
6182
|
},
|
|
5962
6183
|
token: () => current,
|
|
5963
6184
|
async dispose() {
|
|
@@ -6691,6 +6912,12 @@ function defaultDiagnostics(event) {
|
|
|
6691
6912
|
else if (event.level === "warn") console.warn(line);
|
|
6692
6913
|
else console.info(line);
|
|
6693
6914
|
}
|
|
6915
|
+
var claimedTab = null;
|
|
6916
|
+
function pageTabClaim() {
|
|
6917
|
+
if (typeof window === "undefined") return void 0;
|
|
6918
|
+
claimedTab ??= createTabClaim();
|
|
6919
|
+
return claimedTab;
|
|
6920
|
+
}
|
|
6694
6921
|
function deviceId() {
|
|
6695
6922
|
const storage = globalThis.localStorage;
|
|
6696
6923
|
const key = "mx.meet.device";
|
|
@@ -6808,6 +7035,8 @@ function MeetRuntime(props) {
|
|
|
6808
7035
|
store,
|
|
6809
7036
|
tokens,
|
|
6810
7037
|
identity,
|
|
7038
|
+
// S7: this page's tab id (one per page, claimed at load) — the LiveKit identity's tab half.
|
|
7039
|
+
tabClaim: pageTabClaim(),
|
|
6811
7040
|
resolveMeeting: (slug) => repository.meetingBySlug(slug),
|
|
6812
7041
|
driverFactory: (handlers) => (driverRef.current ?? createLiveKitDriver)(handlers),
|
|
6813
7042
|
onDiagnostic: report,
|
|
@@ -7883,6 +8112,7 @@ function useRoom() {
|
|
|
7883
8112
|
phase: snapshot?.phase ?? "resolving",
|
|
7884
8113
|
participants: snapshot?.participants ?? [],
|
|
7885
8114
|
failure: snapshot?.failure ?? null,
|
|
8115
|
+
secondDevice: snapshot?.secondDevice ?? null,
|
|
7886
8116
|
local,
|
|
7887
8117
|
canShareScreen: engine?.supportsScreenShare() ?? true,
|
|
7888
8118
|
join: useCallback2(
|
|
@@ -8028,6 +8258,7 @@ function useLobby() {
|
|
|
8028
8258
|
const session = host?.meetings ?? null;
|
|
8029
8259
|
return {
|
|
8030
8260
|
waiting: (snapshot?.lobby ?? []).filter((entry) => entry.state === "knocking"),
|
|
8261
|
+
declined: (snapshot?.lobby ?? []).filter((entry) => entry.state === "denied"),
|
|
8031
8262
|
// No runtime means no host powers — `<LobbyPanel>` renders nothing rather
|
|
8032
8263
|
// than an admit button that would throw.
|
|
8033
8264
|
canAdmit: session !== null && isHost,
|
|
@@ -8049,6 +8280,37 @@ function useLobby() {
|
|
|
8049
8280
|
)
|
|
8050
8281
|
};
|
|
8051
8282
|
}
|
|
8283
|
+
function useCountdown(untilMs) {
|
|
8284
|
+
const [now, setNow] = useState2(() => Date.now());
|
|
8285
|
+
const active = typeof untilMs === "number" && untilMs > now;
|
|
8286
|
+
useEffect3(() => {
|
|
8287
|
+
if (!active) return void 0;
|
|
8288
|
+
setNow(Date.now());
|
|
8289
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
8290
|
+
return () => clearInterval(timer);
|
|
8291
|
+
}, [active, untilMs]);
|
|
8292
|
+
if (typeof untilMs !== "number") return null;
|
|
8293
|
+
return Math.max(0, untilMs - now);
|
|
8294
|
+
}
|
|
8295
|
+
function useKnockNotice() {
|
|
8296
|
+
const lobby = useLobby();
|
|
8297
|
+
const [dismissed, setDismissed] = useState2([]);
|
|
8298
|
+
const keyOf = (entry) => `${entry.identity}@${entry.knockedAt}`;
|
|
8299
|
+
const open = lobby.canAdmit ? lobby.waiting.filter((entry) => !dismissed.includes(keyOf(entry))) : [];
|
|
8300
|
+
const knocker = open[0] ?? null;
|
|
8301
|
+
return {
|
|
8302
|
+
knocker,
|
|
8303
|
+
others: Math.max(0, open.length - 1),
|
|
8304
|
+
admit: useCallback2(async () => {
|
|
8305
|
+
if (knocker === null) return;
|
|
8306
|
+
setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8307
|
+
await lobby.admit(knocker);
|
|
8308
|
+
}, [knocker, lobby.admit]),
|
|
8309
|
+
dismiss: useCallback2(() => {
|
|
8310
|
+
if (knocker !== null) setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8311
|
+
}, [knocker])
|
|
8312
|
+
};
|
|
8313
|
+
}
|
|
8052
8314
|
function useWaiting() {
|
|
8053
8315
|
const host = useMeetHost();
|
|
8054
8316
|
const snapshot = useMeetSnapshot();
|
|
@@ -10798,6 +11060,7 @@ function ControlBar({ onLeave }) {
|
|
|
10798
11060
|
}
|
|
10799
11061
|
) }) }) : null,
|
|
10800
11062
|
/* @__PURE__ */ jsx6(BreakoutBanner, {}),
|
|
11063
|
+
panel !== "people" ? /* @__PURE__ */ jsx6(KnockNotice, { onView: () => setPanel("people") }) : null,
|
|
10801
11064
|
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ jsx6(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
10802
11065
|
/* @__PURE__ */ jsxs3("div", { className: "mx-meet__controls", children: [
|
|
10803
11066
|
/* @__PURE__ */ jsx6(
|
|
@@ -11128,10 +11391,21 @@ function MediaNotices() {
|
|
|
11128
11391
|
const name = (kind) => kind === "microphone" ? "Microphone" : "Camera";
|
|
11129
11392
|
if (chips.length === 0 && region.audioOutput !== "blocked" && notice === null && offer === null) return null;
|
|
11130
11393
|
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-notices", children: [
|
|
11131
|
-
region.audioOutput === "blocked" ? /* @__PURE__ */ jsxs3(
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11394
|
+
region.audioOutput === "blocked" ? /* @__PURE__ */ jsxs3(
|
|
11395
|
+
ErrorBox,
|
|
11396
|
+
{
|
|
11397
|
+
as: "div",
|
|
11398
|
+
className: "mx-meet__media-pill",
|
|
11399
|
+
"data-meet-notice": "audio-blocked",
|
|
11400
|
+
message: "Sound is paused in this tab",
|
|
11401
|
+
operation: "Enable meeting sound",
|
|
11402
|
+
origin: "meet/MediaNotices",
|
|
11403
|
+
children: [
|
|
11404
|
+
/* @__PURE__ */ jsx6("span", { children: "Sound is paused in this tab" }),
|
|
11405
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void enableAudio(), children: "Enable sound" })
|
|
11406
|
+
]
|
|
11407
|
+
}
|
|
11408
|
+
) : null,
|
|
11135
11409
|
chips.map(({ kind, reason }) => /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "status", "data-meet-notice": deviceNoticeCode(kind, reason), children: [
|
|
11136
11410
|
/* @__PURE__ */ jsx6("span", { children: deviceChipLabel(kind, reason) }),
|
|
11137
11411
|
/* @__PURE__ */ jsx6(
|
|
@@ -11931,9 +12205,68 @@ function useMicrophoneStream(deviceId2) {
|
|
|
11931
12205
|
}, [deviceId2]);
|
|
11932
12206
|
return stream;
|
|
11933
12207
|
}
|
|
12208
|
+
function KnockNotice({ onView }) {
|
|
12209
|
+
const { knocker, others, admit, dismiss } = useKnockNotice();
|
|
12210
|
+
if (knocker === null) return null;
|
|
12211
|
+
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__notice-bar", role: "status", "data-meet-notice": "knock-received", "data-meet-participant-name": knocker.displayName, children: [
|
|
12212
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
12213
|
+
knocker.displayName,
|
|
12214
|
+
" wants to join",
|
|
12215
|
+
others > 0 ? ` (+${others} more)` : ""
|
|
12216
|
+
] }),
|
|
12217
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__link", onClick: () => void admit().catch(() => void 0), children: "Admit" }),
|
|
12218
|
+
/* @__PURE__ */ jsx6(
|
|
12219
|
+
"button",
|
|
12220
|
+
{
|
|
12221
|
+
type: "button",
|
|
12222
|
+
className: "mx-meet__link",
|
|
12223
|
+
onClick: () => {
|
|
12224
|
+
dismiss();
|
|
12225
|
+
onView();
|
|
12226
|
+
},
|
|
12227
|
+
children: "View"
|
|
12228
|
+
}
|
|
12229
|
+
),
|
|
12230
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
|
|
12231
|
+
] });
|
|
12232
|
+
}
|
|
11934
12233
|
function LobbyPanel() {
|
|
11935
12234
|
const lobby = useLobby();
|
|
11936
|
-
if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
|
|
12235
|
+
if (!lobby.canAdmit || lobby.waiting.length === 0 && lobby.declined.length === 0) return null;
|
|
12236
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
12237
|
+
lobby.waiting.length > 0 ? /* @__PURE__ */ jsx6(WaitingList, { lobby }) : null,
|
|
12238
|
+
lobby.declined.length > 0 ? /* @__PURE__ */ jsx6(DeclinedList, { lobby }) : null
|
|
12239
|
+
] });
|
|
12240
|
+
}
|
|
12241
|
+
function DeclinedList({ lobby }) {
|
|
12242
|
+
return /* @__PURE__ */ jsxs3("section", { className: "mx-meet__lobby", "aria-label": "Declined requests", "data-meet-declined": "", children: [
|
|
12243
|
+
/* @__PURE__ */ jsx6("header", { className: "mx-meet__lobby-head", children: /* @__PURE__ */ jsx6("h3", { className: "mx-meet__lobby-title", children: "Declined" }) }),
|
|
12244
|
+
/* @__PURE__ */ jsx6("ul", { className: "mx-meet__lobby-list", children: lobby.declined.map((entry) => /* @__PURE__ */ jsxs3(
|
|
12245
|
+
"li",
|
|
12246
|
+
{
|
|
12247
|
+
className: "mx-meet__lobby-row",
|
|
12248
|
+
"data-meet-declined-entry": "",
|
|
12249
|
+
"data-meet-participant-name": entry.displayName,
|
|
12250
|
+
children: [
|
|
12251
|
+
/* @__PURE__ */ jsx6(Avatar, { name: entry.displayName, url: entry.avatarUrl, size: 32 }),
|
|
12252
|
+
/* @__PURE__ */ jsx6("span", { className: "mx-meet__person-name", children: entry.displayName }),
|
|
12253
|
+
/* @__PURE__ */ jsx6(
|
|
12254
|
+
"button",
|
|
12255
|
+
{
|
|
12256
|
+
type: "button",
|
|
12257
|
+
className: "mx-meet__link",
|
|
12258
|
+
"aria-label": `Let ${entry.displayName} ask again`,
|
|
12259
|
+
onClick: () => void lobby.undeny(entry),
|
|
12260
|
+
children: "Let them ask again"
|
|
12261
|
+
}
|
|
12262
|
+
)
|
|
12263
|
+
]
|
|
12264
|
+
},
|
|
12265
|
+
entry.identity
|
|
12266
|
+
)) })
|
|
12267
|
+
] });
|
|
12268
|
+
}
|
|
12269
|
+
function WaitingList({ lobby }) {
|
|
11937
12270
|
return /* @__PURE__ */ jsxs3("section", { className: "mx-meet__lobby", "aria-label": "Waiting room", children: [
|
|
11938
12271
|
/* @__PURE__ */ jsxs3("header", { className: "mx-meet__lobby-head", children: [
|
|
11939
12272
|
/* @__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 +12530,21 @@ function PreJoinBody({
|
|
|
12197
12530
|
useEffect6(() => {
|
|
12198
12531
|
void host.devices.refresh();
|
|
12199
12532
|
}, [host]);
|
|
12533
|
+
const joinWith = (secondDevice) => {
|
|
12534
|
+
setJoining(true);
|
|
12535
|
+
preview.release();
|
|
12536
|
+
void room.join({
|
|
12537
|
+
roomName,
|
|
12538
|
+
// An observer publishes nothing: no camera, no microphone (MD-16). A companion
|
|
12539
|
+
// ("join here too") never opens the microphone (S7).
|
|
12540
|
+
mode: observe2 ? "audio" : mode,
|
|
12541
|
+
microphone: observe2 || secondDevice === "join_too" ? false : microphone,
|
|
12542
|
+
...meetingId !== void 0 ? { meetingId } : {},
|
|
12543
|
+
...slug !== void 0 ? { slug } : {},
|
|
12544
|
+
...observe2 ? { observe: true } : {},
|
|
12545
|
+
...secondDevice !== void 0 ? { secondDevice } : {}
|
|
12546
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
12547
|
+
};
|
|
12200
12548
|
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__prejoin", children: [
|
|
12201
12549
|
/* @__PURE__ */ jsxs3("div", { className: "mx-meet__preview", children: [
|
|
12202
12550
|
mode === "video" && preview.preview !== null ? /* @__PURE__ */ jsx6(
|
|
@@ -12281,27 +12629,34 @@ function PreJoinBody({
|
|
|
12281
12629
|
),
|
|
12282
12630
|
/* @__PURE__ */ jsx6(Label, { htmlFor: "mx-meet-join-mic", children: "Join with microphone" })
|
|
12283
12631
|
] }),
|
|
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
|
-
|
|
12632
|
+
room.secondDevice === null ? /* @__PURE__ */ jsx6(Button, { type: "button", className: "mx-meet__join", disabled: joining, onClick: () => joinWith(), children: joining ? "Joining\u2026" : observe2 ? "Observe" : "Join now" }) : (
|
|
12633
|
+
// S7: ALREADY IN THIS MEETING on another tab or device — the meeting's choice
|
|
12634
|
+
// (Meet: Switch here / Join here too; Teams: Transfer here / Add this device).
|
|
12635
|
+
/* @__PURE__ */ jsxs3("div", { className: "mx-meet__second-device", "data-meet-region": "second-device", children: [
|
|
12636
|
+
/* @__PURE__ */ jsx6("p", { className: "mx-meet__hint", children: "You're in this meeting on another device" }),
|
|
12637
|
+
/* @__PURE__ */ jsx6(
|
|
12638
|
+
Button,
|
|
12639
|
+
{
|
|
12640
|
+
type: "button",
|
|
12641
|
+
className: "mx-meet__join",
|
|
12642
|
+
"data-meet-control": "switch-here",
|
|
12643
|
+
disabled: joining,
|
|
12644
|
+
onClick: () => joinWith("switch_here"),
|
|
12645
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Transfer here" : "Switch here"
|
|
12646
|
+
}
|
|
12647
|
+
),
|
|
12648
|
+
/* @__PURE__ */ jsx6(
|
|
12649
|
+
Button,
|
|
12650
|
+
{
|
|
12651
|
+
type: "button",
|
|
12652
|
+
variant: "outline",
|
|
12653
|
+
"data-meet-control": "join-too",
|
|
12654
|
+
disabled: joining,
|
|
12655
|
+
onClick: () => joinWith("join_too"),
|
|
12656
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Add this device" : "Join here too"
|
|
12657
|
+
}
|
|
12658
|
+
)
|
|
12659
|
+
] })
|
|
12305
12660
|
)
|
|
12306
12661
|
] }),
|
|
12307
12662
|
room.failure !== null ? /* @__PURE__ */ jsxs3("p", { className: "mx-meet__error", children: [
|
|
@@ -12499,16 +12854,21 @@ var REFUSED_TITLES = {
|
|
|
12499
12854
|
not_invited: "You aren't invited to this meeting",
|
|
12500
12855
|
locked: "This meeting is locked",
|
|
12501
12856
|
removed: "You can't rejoin this meeting",
|
|
12502
|
-
lobby_rejects: "
|
|
12857
|
+
lobby_rejects: "The host declined your request",
|
|
12503
12858
|
meeting_full: "This meeting is full",
|
|
12504
12859
|
platform_capacity: "Meetings are busy right now",
|
|
12505
12860
|
verification_required: "Verify your account to join",
|
|
12506
12861
|
registration_required: "Register to join this meeting"
|
|
12507
12862
|
};
|
|
12863
|
+
function clockText(ms2) {
|
|
12864
|
+
const total = Math.ceil(ms2 / 1e3);
|
|
12865
|
+
return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
|
|
12866
|
+
}
|
|
12508
12867
|
function ExitScreen({ onLeave }) {
|
|
12509
12868
|
const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
|
|
12510
12869
|
const [busy, setBusy] = useState4(false);
|
|
12511
12870
|
const failure = snapshot?.failure ?? null;
|
|
12871
|
+
const retryLeft = useCountdown(phase === "refused:lobby_rejects" ? failure?.retryAt ?? null : null);
|
|
12512
12872
|
const rejoin = /* @__PURE__ */ jsx6(
|
|
12513
12873
|
Button,
|
|
12514
12874
|
{
|
|
@@ -12551,6 +12911,33 @@ function ExitScreen({ onLeave }) {
|
|
|
12551
12911
|
/* @__PURE__ */ jsx6(Button, { type: "button", variant: "outline", onClick: actions.retry, children: "Try again" }),
|
|
12552
12912
|
home
|
|
12553
12913
|
] });
|
|
12914
|
+
if (code === "lobby_rejects") {
|
|
12915
|
+
if (retryLeft !== null && retryLeft > 0) {
|
|
12916
|
+
line = `You can ask again in ${clockText(retryLeft)}`;
|
|
12917
|
+
notice = "reknock-cooldown";
|
|
12918
|
+
actionsNode = home;
|
|
12919
|
+
} else if (retryLeft === 0) {
|
|
12920
|
+
line = "You can ask to join again";
|
|
12921
|
+
actionsNode = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
12922
|
+
/* @__PURE__ */ jsx6(
|
|
12923
|
+
Button,
|
|
12924
|
+
{
|
|
12925
|
+
type: "button",
|
|
12926
|
+
disabled: busy,
|
|
12927
|
+
onClick: () => {
|
|
12928
|
+
setBusy(true);
|
|
12929
|
+
void actions.rejoin().catch(() => void 0).finally(() => setBusy(false));
|
|
12930
|
+
},
|
|
12931
|
+
children: "Ask to join again"
|
|
12932
|
+
}
|
|
12933
|
+
),
|
|
12934
|
+
home
|
|
12935
|
+
] });
|
|
12936
|
+
} else {
|
|
12937
|
+
line = "Ask the host to let you in, then open the link again";
|
|
12938
|
+
actionsNode = home;
|
|
12939
|
+
}
|
|
12940
|
+
}
|
|
12554
12941
|
} else {
|
|
12555
12942
|
switch (phase) {
|
|
12556
12943
|
case "left":
|
|
@@ -12611,7 +12998,7 @@ function ExitScreen({ onLeave }) {
|
|
|
12611
12998
|
/* @__PURE__ */ jsx6(Avatar, { name: host?.identity.displayName ?? "", url: host?.identity.avatarUrl ?? null, size: 72 }),
|
|
12612
12999
|
/* @__PURE__ */ jsx6("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12613
13000
|
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,
|
|
13001
|
+
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
13002
|
/* @__PURE__ */ jsx6("div", { className: "mx-meet__waiting-actions", children: actionsNode })
|
|
12616
13003
|
] });
|
|
12617
13004
|
}
|
|
@@ -12620,6 +13007,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12620
13007
|
const { phaseNotice } = useMeetCore();
|
|
12621
13008
|
const [busy, setBusy] = useState4(false);
|
|
12622
13009
|
const admission = waiting?.admission ?? "knocking";
|
|
13010
|
+
const reknockLeft = useCountdown(admission === "denied" ? waiting?.reknockAt ?? null : null);
|
|
12623
13011
|
const run = (action) => () => {
|
|
12624
13012
|
setBusy(true);
|
|
12625
13013
|
void action().catch(() => void 0).finally(() => setBusy(false));
|
|
@@ -12627,6 +13015,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12627
13015
|
const home = /* @__PURE__ */ jsx6(Button, { type: "button", variant: "outline", onClick: () => onLeave !== void 0 ? onLeave() : window.location.assign("/"), children: "Back to home" });
|
|
12628
13016
|
let title;
|
|
12629
13017
|
let line = null;
|
|
13018
|
+
let cooldownNotice = false;
|
|
12630
13019
|
let actions;
|
|
12631
13020
|
switch (admission) {
|
|
12632
13021
|
case "waiting_host":
|
|
@@ -12635,8 +13024,12 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12635
13024
|
break;
|
|
12636
13025
|
case "denied":
|
|
12637
13026
|
title = "The host declined your request";
|
|
13027
|
+
if (reknockLeft !== null && reknockLeft > 0) {
|
|
13028
|
+
line = `You can ask again in ${clockText(reknockLeft)}`;
|
|
13029
|
+
cooldownNotice = true;
|
|
13030
|
+
}
|
|
12638
13031
|
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,
|
|
13032
|
+
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
13033
|
home
|
|
12641
13034
|
] });
|
|
12642
13035
|
break;
|
|
@@ -12667,7 +13060,15 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12667
13060
|
/* @__PURE__ */ jsx6(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
|
|
12668
13061
|
/* @__PURE__ */ jsx6("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12669
13062
|
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(
|
|
13063
|
+
line !== null ? /* @__PURE__ */ jsx6(
|
|
13064
|
+
"p",
|
|
13065
|
+
{
|
|
13066
|
+
className: "mx-meet__empty-body",
|
|
13067
|
+
role: "status",
|
|
13068
|
+
"data-meet-notice": cooldownNotice ? "reknock-cooldown" : "host-absent",
|
|
13069
|
+
children: line
|
|
13070
|
+
}
|
|
13071
|
+
) : null,
|
|
12671
13072
|
/* @__PURE__ */ jsx6("div", { className: "mx-meet__waiting-actions", children: actions })
|
|
12672
13073
|
] });
|
|
12673
13074
|
}
|