@ai-matrx/meet 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/{collab-B6scmSST.d.cts → collab-C2R-kQnV.d.cts} +6 -1
- package/dist/{collab-B6scmSST.d.ts → collab-C2R-kQnV.d.ts} +6 -1
- package/dist/{components-D0TqyeiY.d.ts → components-CMtSekZS.d.ts} +1 -1
- package/dist/{components-BeCQ9h0h.d.cts → components-DKHfkEVB.d.cts} +1 -1
- package/dist/core.cjs +339 -104
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +79 -24
- package/dist/core.d.ts +79 -24
- package/dist/core.js +339 -104
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +339 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +339 -104
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +421 -127
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +5 -4
- package/dist/react.d.ts +5 -4
- package/dist/react.js +421 -127
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +94 -19
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +2 -2
- package/dist/skins/meet.d.ts +2 -2
- package/dist/skins/meet.js +94 -19
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +5 -5
package/dist/core.js
CHANGED
|
@@ -301,7 +301,17 @@ var MEET_ROUTES = {
|
|
|
301
301
|
*/
|
|
302
302
|
collab: "/api/v1/meet/collab",
|
|
303
303
|
/** Meet wave 5: translate finished caption lines into this viewer's language. */
|
|
304
|
-
captionsTranslate: "/api/v1/meet/captions/translate"
|
|
304
|
+
captionsTranslate: "/api/v1/meet/captions/translate",
|
|
305
|
+
/**
|
|
306
|
+
* S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
|
|
307
|
+
* person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
|
|
308
|
+
* phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
|
|
309
|
+
*/
|
|
310
|
+
state: "/api/v1/meet/state",
|
|
311
|
+
/** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
|
|
312
|
+
leave: "/api/v1/meet/leave",
|
|
313
|
+
/** S4: a pass holder (guests included) saw the recording notice, for this run. */
|
|
314
|
+
consent: "/api/v1/meet/consent"
|
|
305
315
|
};
|
|
306
316
|
function meetingPath(template, meetingId) {
|
|
307
317
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -375,6 +385,8 @@ function parseRoomToken(body, roomName) {
|
|
|
375
385
|
meetingPassExpiresAt: readString(payload, "meeting_pass_expires_at"),
|
|
376
386
|
sessionId: readString(payload, "session_id"),
|
|
377
387
|
lobby: readBoolean(payload, "lobby", false),
|
|
388
|
+
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
389
|
+
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
378
390
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null
|
|
379
391
|
};
|
|
380
392
|
}
|
|
@@ -987,14 +999,15 @@ var MEET_EVENTS = {
|
|
|
987
999
|
cancel: "mx.call.cancel",
|
|
988
1000
|
/** You answered (or declined) on another device. Stop ringing here. */
|
|
989
1001
|
settled: "mx.call.settled",
|
|
990
|
-
/**
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1002
|
+
/**
|
|
1003
|
+
* 🚨 S4 (CORE-DESIGN §2.6): the ONE room-channel event — payload-free "something changed,
|
|
1004
|
+
* read again". Nothing a person must see rides it: a waiting person learns admitted /
|
|
1005
|
+
* denied / expired / ended by READING their row (`GET /v1/meet/state`) on a timer and on
|
|
1006
|
+
* every hint. The four payload broadcasts (`mx.lobby.knock`, `mx.lobby.admission`,
|
|
1007
|
+
* `mx.room.lock`, `mx.room.recording`) are gone with their senders and receivers. The
|
|
1008
|
+
* server publishes the same event on the same topic (aidream `admission.room_topic`).
|
|
1009
|
+
*/
|
|
1010
|
+
stateChanged: "mx.state.changed"
|
|
998
1011
|
};
|
|
999
1012
|
|
|
1000
1013
|
// src/core/types.ts
|
|
@@ -1020,6 +1033,7 @@ function isLinkFollower(standing) {
|
|
|
1020
1033
|
function isHostRole(role) {
|
|
1021
1034
|
return role === "host" || role === "cohost";
|
|
1022
1035
|
}
|
|
1036
|
+
var WAITING_ADMISSION_STATES = ["waiting_host", "knocking"];
|
|
1023
1037
|
|
|
1024
1038
|
// src/core/calls.ts
|
|
1025
1039
|
var TRANSITIONS = {
|
|
@@ -1632,7 +1646,14 @@ var RPCS = {
|
|
|
1632
1646
|
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
1633
1647
|
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
1634
1648
|
meetingBySlug: "meet_meeting_by_slug",
|
|
1635
|
-
|
|
1649
|
+
/*
|
|
1650
|
+
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
1651
|
+
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
1652
|
+
*/
|
|
1653
|
+
admit: "meet_admit",
|
|
1654
|
+
deny: "meet_deny",
|
|
1655
|
+
undeny: "meet_undeny",
|
|
1656
|
+
admitAll: "meet_admit_all",
|
|
1636
1657
|
setLock: "meet_set_lock",
|
|
1637
1658
|
recordConsent: "meet_record_consent",
|
|
1638
1659
|
/*
|
|
@@ -1675,13 +1696,13 @@ var RPCS = {
|
|
|
1675
1696
|
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1676
1697
|
search: "meet_search"
|
|
1677
1698
|
};
|
|
1678
|
-
var VOID_RPCS = [RPCS.
|
|
1699
|
+
var VOID_RPCS = [RPCS.recordConsent];
|
|
1679
1700
|
var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
|
|
1680
1701
|
var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsvp_state,responded_at,rsvp_note,last_sent_sequence,last_sent_at";
|
|
1681
1702
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
1682
1703
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
1683
1704
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
1684
|
-
var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,is_agent,joined_at,left_at,sessions:metadata->sessions";
|
|
1705
|
+
var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions";
|
|
1685
1706
|
function projectStays(value) {
|
|
1686
1707
|
if (!Array.isArray(value)) return [];
|
|
1687
1708
|
const stays = [];
|
|
@@ -2048,7 +2069,16 @@ function createMeetRepository(options) {
|
|
|
2048
2069
|
const role = text(row, "role");
|
|
2049
2070
|
const admission = text(row, "admission_state");
|
|
2050
2071
|
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
2051
|
-
const states = [
|
|
2072
|
+
const states = [
|
|
2073
|
+
"waiting_host",
|
|
2074
|
+
"knocking",
|
|
2075
|
+
"admitted",
|
|
2076
|
+
"denied",
|
|
2077
|
+
"expired",
|
|
2078
|
+
"withdrawn",
|
|
2079
|
+
"removed",
|
|
2080
|
+
"ended"
|
|
2081
|
+
];
|
|
2052
2082
|
const userId = text(row, "participant_user_id");
|
|
2053
2083
|
const identity = requireText(row, "identity", operation);
|
|
2054
2084
|
return {
|
|
@@ -2062,6 +2092,8 @@ function createMeetRepository(options) {
|
|
|
2062
2092
|
userId: userId === null ? null : asUserId(userId),
|
|
2063
2093
|
role: known.includes(role) ? role : "participant",
|
|
2064
2094
|
admissionState: states.includes(admission) ? admission : "admitted",
|
|
2095
|
+
knockedAt: text(row, "knocked_at"),
|
|
2096
|
+
knockExpiresAt: text(row, "knock_expires_at"),
|
|
2065
2097
|
isAgent: bool(row, "is_agent", false),
|
|
2066
2098
|
joinedAt: text(row, "joined_at"),
|
|
2067
2099
|
leftAt: text(row, "left_at"),
|
|
@@ -2340,16 +2372,26 @@ function createMeetRepository(options) {
|
|
|
2340
2372
|
hasMore: rows.length > limit
|
|
2341
2373
|
};
|
|
2342
2374
|
},
|
|
2343
|
-
async
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2375
|
+
async decideAdmission({ meetingId, identity, decision, byUserId }) {
|
|
2376
|
+
const fn = decision === "admit" ? RPCS.admit : decision === "deny" ? RPCS.deny : RPCS.undeny;
|
|
2377
|
+
const row = await guard(
|
|
2378
|
+
`meet.${decision}`,
|
|
2379
|
+
() => db.rpc(fn, {
|
|
2347
2380
|
p_meeting_id: meetingId,
|
|
2348
2381
|
p_identity: identity,
|
|
2349
|
-
p_admitted: admitted,
|
|
2350
2382
|
p_by_user_id: byUserId
|
|
2351
2383
|
})
|
|
2352
2384
|
);
|
|
2385
|
+
return projectAttendee(row);
|
|
2386
|
+
},
|
|
2387
|
+
async admitAll(meetingId, byUserId) {
|
|
2388
|
+
const response = await db.rpc(RPCS.admitAll, {
|
|
2389
|
+
p_meeting_id: meetingId,
|
|
2390
|
+
p_by_user_id: byUserId
|
|
2391
|
+
});
|
|
2392
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.admitAll");
|
|
2393
|
+
const rows = Array.isArray(response.data) ? response.data : [];
|
|
2394
|
+
return rows.map(projectAttendee);
|
|
2353
2395
|
},
|
|
2354
2396
|
async setLock(meetingId, locked, byUserId) {
|
|
2355
2397
|
const row = await guard(
|
|
@@ -2697,6 +2739,7 @@ var NO_GRANTS = {
|
|
|
2697
2739
|
};
|
|
2698
2740
|
var EMPTY = {
|
|
2699
2741
|
phase: "idle",
|
|
2742
|
+
waiting: null,
|
|
2700
2743
|
meeting: null,
|
|
2701
2744
|
localIdentity: null,
|
|
2702
2745
|
grants: NO_GRANTS,
|
|
@@ -3974,19 +4017,25 @@ function roleOf(participants, identity) {
|
|
|
3974
4017
|
return participants.find((p) => p.identity === identity)?.role ?? "participant";
|
|
3975
4018
|
}
|
|
3976
4019
|
var END_MEETING_NOT_SERVED = "Ending a meeting is not available on this server yet";
|
|
3977
|
-
function lobbyEntryFrom(attendee) {
|
|
3978
|
-
if (attendee.admissionState
|
|
4020
|
+
function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
4021
|
+
if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;
|
|
4022
|
+
const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;
|
|
4023
|
+
if (!Number.isNaN(expires) && expires <= now) return null;
|
|
3979
4024
|
return {
|
|
3980
4025
|
identity: attendee.identity,
|
|
3981
4026
|
displayName: attendee.displayName,
|
|
3982
4027
|
avatarUrl: attendee.avatarUrl,
|
|
3983
4028
|
userId: attendee.userId,
|
|
3984
4029
|
state: "knocking",
|
|
3985
|
-
knockedAt: attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4030
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4031
|
+
knockExpiresAt: attendee.knockExpiresAt ?? null,
|
|
3986
4032
|
decidedAt: null,
|
|
3987
4033
|
decidedBy: null
|
|
3988
4034
|
};
|
|
3989
4035
|
}
|
|
4036
|
+
function decidedState(state) {
|
|
4037
|
+
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
4038
|
+
}
|
|
3990
4039
|
function createMeetingSession(options) {
|
|
3991
4040
|
const { manager, repository, store, identity } = options;
|
|
3992
4041
|
let channel = null;
|
|
@@ -4010,11 +4059,6 @@ function createMeetingSession(options) {
|
|
|
4010
4059
|
}
|
|
4011
4060
|
return identity.userId;
|
|
4012
4061
|
}
|
|
4013
|
-
function knockIdentity() {
|
|
4014
|
-
return asParticipantIdentity(
|
|
4015
|
-
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
4016
|
-
);
|
|
4017
|
-
}
|
|
4018
4062
|
function requireMeeting(operation) {
|
|
4019
4063
|
const meeting = store.snapshot().meeting;
|
|
4020
4064
|
if (meeting === null) {
|
|
@@ -4026,6 +4070,191 @@ function createMeetingSession(options) {
|
|
|
4026
4070
|
}
|
|
4027
4071
|
return meeting;
|
|
4028
4072
|
}
|
|
4073
|
+
let polling = false;
|
|
4074
|
+
let pollTimer = null;
|
|
4075
|
+
let reading = null;
|
|
4076
|
+
let admittedOnce = false;
|
|
4077
|
+
let unlisten = null;
|
|
4078
|
+
function report(level, message, remedy) {
|
|
4079
|
+
options.onDiagnostic?.(remedy === void 0 ? { level, message } : { level, message, remedy });
|
|
4080
|
+
}
|
|
4081
|
+
function pollMs() {
|
|
4082
|
+
const seconds = store.snapshot().waiting?.pollSeconds ?? 3;
|
|
4083
|
+
return Math.max(1, Math.min(60, seconds)) * 1e3;
|
|
4084
|
+
}
|
|
4085
|
+
function schedule(ms2) {
|
|
4086
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4087
|
+
if (!polling) return;
|
|
4088
|
+
pollTimer = setTimeout(() => {
|
|
4089
|
+
pollTimer = null;
|
|
4090
|
+
void readState2().finally(() => schedule(pollMs()));
|
|
4091
|
+
}, ms2);
|
|
4092
|
+
}
|
|
4093
|
+
function listen() {
|
|
4094
|
+
if (unlisten !== null || typeof window === "undefined") return;
|
|
4095
|
+
const nudge = () => {
|
|
4096
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
4097
|
+
void readState2();
|
|
4098
|
+
};
|
|
4099
|
+
const goodbye = () => {
|
|
4100
|
+
const meeting = store.snapshot().meeting;
|
|
4101
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4102
|
+
const waiting = store.snapshot().waiting;
|
|
4103
|
+
if (meeting === null || pass === null || waiting === null) return;
|
|
4104
|
+
if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;
|
|
4105
|
+
try {
|
|
4106
|
+
navigator.sendBeacon?.(
|
|
4107
|
+
options.api.url(MEET_ROUTES.leave),
|
|
4108
|
+
new Blob([JSON.stringify({ meeting_id: meeting.id, intent: "withdraw", meeting_pass: pass })], {
|
|
4109
|
+
type: "application/json"
|
|
4110
|
+
})
|
|
4111
|
+
);
|
|
4112
|
+
} catch {
|
|
4113
|
+
}
|
|
4114
|
+
};
|
|
4115
|
+
window.addEventListener("online", nudge);
|
|
4116
|
+
window.addEventListener("pagehide", goodbye);
|
|
4117
|
+
document.addEventListener("visibilitychange", nudge);
|
|
4118
|
+
unlisten = () => {
|
|
4119
|
+
window.removeEventListener("online", nudge);
|
|
4120
|
+
window.removeEventListener("pagehide", goodbye);
|
|
4121
|
+
document.removeEventListener("visibilitychange", nudge);
|
|
4122
|
+
};
|
|
4123
|
+
}
|
|
4124
|
+
function stopWaiting() {
|
|
4125
|
+
polling = false;
|
|
4126
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4127
|
+
pollTimer = null;
|
|
4128
|
+
unlisten?.();
|
|
4129
|
+
unlisten = null;
|
|
4130
|
+
}
|
|
4131
|
+
function patchWaiting(next) {
|
|
4132
|
+
const current = store.snapshot().waiting;
|
|
4133
|
+
const base = current ?? {
|
|
4134
|
+
admission: "knocking",
|
|
4135
|
+
admittersPresent: null,
|
|
4136
|
+
reknockAllowed: true,
|
|
4137
|
+
knockExpiresAt: null,
|
|
4138
|
+
pollSeconds: 3,
|
|
4139
|
+
liveVersion: 0
|
|
4140
|
+
};
|
|
4141
|
+
store.patch({ waiting: { ...base, ...next } });
|
|
4142
|
+
}
|
|
4143
|
+
function apply(read) {
|
|
4144
|
+
const snapshot = store.snapshot();
|
|
4145
|
+
if (snapshot.phase !== "lobby") return;
|
|
4146
|
+
const admission = read.admission_state ?? null;
|
|
4147
|
+
const ended = admission === "ended" || read.session_phase === "ended" || read.meeting_phase === "ended";
|
|
4148
|
+
if (admission === "admitted" && !ended) {
|
|
4149
|
+
if (admittedOnce) return;
|
|
4150
|
+
admittedOnce = true;
|
|
4151
|
+
stopWaiting();
|
|
4152
|
+
options.onAdmitted?.();
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
if (ended) {
|
|
4156
|
+
stopWaiting();
|
|
4157
|
+
patchWaiting({ admission: "ended" });
|
|
4158
|
+
const meeting = snapshot.meeting;
|
|
4159
|
+
if (read.meeting_phase === "ended" && meeting !== null && meeting.endedAt === null) {
|
|
4160
|
+
store.patch({ meeting: { ...meeting, endedAt: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
4161
|
+
}
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
if (admission === null) return;
|
|
4165
|
+
patchWaiting({
|
|
4166
|
+
admission,
|
|
4167
|
+
admittersPresent: read.admitters_present ?? null,
|
|
4168
|
+
reknockAllowed: read.reknock_allowed ?? true,
|
|
4169
|
+
knockExpiresAt: read.knock_expires_at ?? null,
|
|
4170
|
+
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
4171
|
+
liveVersion: read.live_version ?? 0
|
|
4172
|
+
});
|
|
4173
|
+
if (admission === "denied" || admission === "expired" || admission === "withdrawn" || admission === "removed") {
|
|
4174
|
+
stopWaiting();
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
async function readState2() {
|
|
4178
|
+
if (reading !== null) return reading;
|
|
4179
|
+
const meeting = store.snapshot().meeting;
|
|
4180
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4181
|
+
if (meeting === null || pass === null) return;
|
|
4182
|
+
reading = (async () => {
|
|
4183
|
+
try {
|
|
4184
|
+
const response = await options.api.request({
|
|
4185
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
4186
|
+
method: "GET",
|
|
4187
|
+
requireAuth: false,
|
|
4188
|
+
operation: "meet.state",
|
|
4189
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
4190
|
+
});
|
|
4191
|
+
if (!response.ok) {
|
|
4192
|
+
let body = null;
|
|
4193
|
+
try {
|
|
4194
|
+
body = await response.json();
|
|
4195
|
+
} catch {
|
|
4196
|
+
body = null;
|
|
4197
|
+
}
|
|
4198
|
+
const refusal2 = readServerRefusal(body);
|
|
4199
|
+
report(
|
|
4200
|
+
"warn",
|
|
4201
|
+
refusal2?.reason ?? `meet.state: the server answered ${response.status}`,
|
|
4202
|
+
refusal2?.remedy ?? "The waiting room keeps asking; nothing on this screen changes until the server answers."
|
|
4203
|
+
);
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
apply(await response.json());
|
|
4207
|
+
} catch (error) {
|
|
4208
|
+
report("info", `meet.state: ${String(error)}`, "Waiting continues; the state is read again shortly.");
|
|
4209
|
+
} finally {
|
|
4210
|
+
reading = null;
|
|
4211
|
+
}
|
|
4212
|
+
})();
|
|
4213
|
+
return reading;
|
|
4214
|
+
}
|
|
4215
|
+
async function postLeave(intent) {
|
|
4216
|
+
const meeting = requireMeeting(`meet.${intent}`);
|
|
4217
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4218
|
+
const response = await options.api.request({
|
|
4219
|
+
path: MEET_ROUTES.leave,
|
|
4220
|
+
method: "POST",
|
|
4221
|
+
requireAuth: false,
|
|
4222
|
+
operation: `meet.${intent}`,
|
|
4223
|
+
body: { meeting_id: meeting.id, intent },
|
|
4224
|
+
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
4225
|
+
});
|
|
4226
|
+
if (!response.ok) {
|
|
4227
|
+
let body = null;
|
|
4228
|
+
try {
|
|
4229
|
+
body = await response.json();
|
|
4230
|
+
} catch {
|
|
4231
|
+
body = null;
|
|
4232
|
+
}
|
|
4233
|
+
const refusal2 = readServerRefusal(body);
|
|
4234
|
+
throw new MeetError(
|
|
4235
|
+
"transport",
|
|
4236
|
+
refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
|
|
4237
|
+
refusal2?.remedy ?? "You are still in the waiting room. Try again."
|
|
4238
|
+
);
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
function hint() {
|
|
4242
|
+
channel?.send(MEET_EVENTS.stateChanged, { v: null });
|
|
4243
|
+
}
|
|
4244
|
+
async function refreshQueue() {
|
|
4245
|
+
const meeting = store.snapshot().meeting;
|
|
4246
|
+
if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;
|
|
4247
|
+
try {
|
|
4248
|
+
const attendees = await repository.attendees(meeting.id);
|
|
4249
|
+
store.learn(attendees);
|
|
4250
|
+
for (const attendee of attendees) {
|
|
4251
|
+
const entry = lobbyEntryFrom(attendee);
|
|
4252
|
+
if (entry !== null) store.upsertLobby(entry);
|
|
4253
|
+
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4254
|
+
}
|
|
4255
|
+
} catch {
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4029
4258
|
return {
|
|
4030
4259
|
watch(roomName) {
|
|
4031
4260
|
channel?.close();
|
|
@@ -4033,58 +4262,22 @@ function createMeetingSession(options) {
|
|
|
4033
4262
|
topic: roomTopic(roomName),
|
|
4034
4263
|
broadcast: [
|
|
4035
4264
|
{
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
{
|
|
4042
|
-
event: MEET_EVENTS.admission,
|
|
4043
|
-
onMessage: (message) => {
|
|
4044
|
-
const verdict = message.data;
|
|
4045
|
-
const decided = asParticipantIdentity(verdict.identity);
|
|
4046
|
-
store.setAdmission(
|
|
4047
|
-
decided,
|
|
4048
|
-
verdict.admitted ? "admitted" : "denied",
|
|
4049
|
-
verdict.by === null ? null : verdict.by
|
|
4050
|
-
);
|
|
4051
|
-
if (decided === knockIdentity()) {
|
|
4052
|
-
if (verdict.admitted) options.onAdmitted?.();
|
|
4053
|
-
else options.onDenied?.();
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
},
|
|
4057
|
-
{
|
|
4058
|
-
event: MEET_EVENTS.lock,
|
|
4059
|
-
onMessage: (message) => {
|
|
4060
|
-
const { locked } = message.data;
|
|
4061
|
-
const meeting = store.snapshot().meeting;
|
|
4062
|
-
store.patch({
|
|
4063
|
-
locked,
|
|
4064
|
-
...meeting !== null ? { meeting: { ...meeting, locked } } : {}
|
|
4065
|
-
});
|
|
4066
|
-
}
|
|
4067
|
-
},
|
|
4068
|
-
{
|
|
4069
|
-
event: MEET_EVENTS.recording,
|
|
4070
|
-
onMessage: (message) => {
|
|
4071
|
-
const payload = message.data;
|
|
4072
|
-
store.setRecording(
|
|
4073
|
-
payload.state,
|
|
4074
|
-
payload.by === null ? null : payload.by,
|
|
4075
|
-
"room"
|
|
4076
|
-
);
|
|
4265
|
+
// 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.
|
|
4266
|
+
event: MEET_EVENTS.stateChanged,
|
|
4267
|
+
onMessage: () => {
|
|
4268
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4269
|
+
void refreshQueue();
|
|
4077
4270
|
}
|
|
4078
4271
|
}
|
|
4079
4272
|
],
|
|
4080
|
-
// THE BACKFILL DOOR. A
|
|
4081
|
-
//
|
|
4082
|
-
// re-announce on their own timer.
|
|
4273
|
+
// THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting
|
|
4274
|
+
// row, and a waiting tab re-reads its own state.
|
|
4083
4275
|
onBackfill: async () => {
|
|
4084
4276
|
const meeting = store.snapshot().meeting;
|
|
4085
4277
|
if (meeting === null) return;
|
|
4086
4278
|
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
4087
4279
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
4280
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4088
4281
|
}
|
|
4089
4282
|
});
|
|
4090
4283
|
},
|
|
@@ -4145,7 +4338,7 @@ function createMeetingSession(options) {
|
|
|
4145
4338
|
store.upsertLobby(entry);
|
|
4146
4339
|
return;
|
|
4147
4340
|
}
|
|
4148
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4341
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4149
4342
|
}
|
|
4150
4343
|
},
|
|
4151
4344
|
{
|
|
@@ -4197,62 +4390,75 @@ function createMeetingSession(options) {
|
|
|
4197
4390
|
if (entry !== null) {
|
|
4198
4391
|
store.upsertLobby(entry);
|
|
4199
4392
|
} else {
|
|
4200
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4393
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4201
4394
|
}
|
|
4202
4395
|
}
|
|
4203
4396
|
}
|
|
4204
4397
|
});
|
|
4205
4398
|
meetingChannel.requestBackfill("manual");
|
|
4206
4399
|
},
|
|
4207
|
-
knock(roomName) {
|
|
4208
|
-
const handle = channel ?? manager.open({ topic: roomTopic(roomName) });
|
|
4209
|
-
channel = handle;
|
|
4210
|
-
const entry = {
|
|
4211
|
-
identity: knockIdentity(),
|
|
4212
|
-
displayName: identity.displayName,
|
|
4213
|
-
avatarUrl: identity.avatarUrl,
|
|
4214
|
-
userId: identity.userId,
|
|
4215
|
-
state: "knocking",
|
|
4216
|
-
knockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4217
|
-
decidedAt: null,
|
|
4218
|
-
decidedBy: null
|
|
4219
|
-
};
|
|
4220
|
-
store.upsertLobby(entry);
|
|
4221
|
-
handle.send(MEET_EVENTS.knock, entry);
|
|
4222
|
-
},
|
|
4223
4400
|
async admit(entry, admitted) {
|
|
4224
4401
|
const by = requireHostUser("meet.admit");
|
|
4225
4402
|
const meeting = requireMeeting("meet.admit");
|
|
4226
|
-
await repository.
|
|
4403
|
+
const row = await repository.decideAdmission({
|
|
4227
4404
|
meetingId: meeting.id,
|
|
4228
4405
|
identity: entry.identity,
|
|
4229
|
-
admitted,
|
|
4406
|
+
decision: admitted ? "admit" : "deny",
|
|
4230
4407
|
byUserId: by
|
|
4231
4408
|
});
|
|
4232
4409
|
store.upsertLobby({
|
|
4233
4410
|
...entry,
|
|
4234
|
-
state:
|
|
4411
|
+
state: decidedState(row.admissionState),
|
|
4235
4412
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4236
4413
|
decidedBy: by
|
|
4237
4414
|
});
|
|
4238
|
-
|
|
4415
|
+
hint();
|
|
4416
|
+
},
|
|
4417
|
+
async undeny(entry) {
|
|
4418
|
+
const by = requireHostUser("meet.undeny");
|
|
4419
|
+
const meeting = requireMeeting("meet.undeny");
|
|
4420
|
+
const row = await repository.decideAdmission({
|
|
4421
|
+
meetingId: meeting.id,
|
|
4239
4422
|
identity: entry.identity,
|
|
4240
|
-
|
|
4241
|
-
by
|
|
4423
|
+
decision: "undeny",
|
|
4424
|
+
byUserId: by
|
|
4242
4425
|
});
|
|
4426
|
+
store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: (/* @__PURE__ */ new Date()).toISOString(), decidedBy: by });
|
|
4427
|
+
hint();
|
|
4243
4428
|
},
|
|
4244
4429
|
async admitAll() {
|
|
4245
|
-
const
|
|
4246
|
-
|
|
4247
|
-
|
|
4430
|
+
const by = requireHostUser("meet.admitAll");
|
|
4431
|
+
const meeting = requireMeeting("meet.admitAll");
|
|
4432
|
+
const rows = await repository.admitAll(meeting.id, by);
|
|
4433
|
+
for (const row of rows) {
|
|
4434
|
+
store.setAdmission(row.identity, row.admissionState, by);
|
|
4248
4435
|
}
|
|
4436
|
+
hint();
|
|
4437
|
+
},
|
|
4438
|
+
startWaiting() {
|
|
4439
|
+
if (polling) return;
|
|
4440
|
+
polling = true;
|
|
4441
|
+
admittedOnce = false;
|
|
4442
|
+
listen();
|
|
4443
|
+
schedule(0);
|
|
4444
|
+
},
|
|
4445
|
+
stopWaiting,
|
|
4446
|
+
readState: readState2,
|
|
4447
|
+
async withdraw() {
|
|
4448
|
+
stopWaiting();
|
|
4449
|
+
await postLeave("withdraw");
|
|
4450
|
+
patchWaiting({ admission: "withdrawn" });
|
|
4451
|
+
hint();
|
|
4452
|
+
},
|
|
4453
|
+
async askAgain() {
|
|
4454
|
+
await options.rejoin?.();
|
|
4249
4455
|
},
|
|
4250
4456
|
async setLock(locked) {
|
|
4251
4457
|
const by = requireHostUser("meet.setLock");
|
|
4252
4458
|
const meeting = requireMeeting("meet.setLock");
|
|
4253
4459
|
const updated = await repository.setLock(meeting.id, locked, by);
|
|
4254
4460
|
store.patch({ meeting: updated, locked: updated.locked });
|
|
4255
|
-
|
|
4461
|
+
hint();
|
|
4256
4462
|
return updated;
|
|
4257
4463
|
},
|
|
4258
4464
|
/**
|
|
@@ -4310,6 +4516,7 @@ function createMeetingSession(options) {
|
|
|
4310
4516
|
return { endedAt, served: true };
|
|
4311
4517
|
},
|
|
4312
4518
|
dispose() {
|
|
4519
|
+
stopWaiting();
|
|
4313
4520
|
channel?.close();
|
|
4314
4521
|
channel = null;
|
|
4315
4522
|
meetingChannel?.close();
|
|
@@ -5170,7 +5377,7 @@ function createRoomEngine(options) {
|
|
|
5170
5377
|
}
|
|
5171
5378
|
};
|
|
5172
5379
|
async function connect(request) {
|
|
5173
|
-
store.patch({ phase: "requesting-token", failure: null });
|
|
5380
|
+
store.patch({ phase: "requesting-token", failure: null, waiting: null });
|
|
5174
5381
|
const token = await tokens.get({
|
|
5175
5382
|
roomName: request.roomName,
|
|
5176
5383
|
identity,
|
|
@@ -5190,11 +5397,21 @@ function createRoomEngine(options) {
|
|
|
5190
5397
|
canSubscribe: token.canSubscribe
|
|
5191
5398
|
}
|
|
5192
5399
|
});
|
|
5193
|
-
if (token.lobby) {
|
|
5194
|
-
store.patch({
|
|
5400
|
+
if (token.lobby || !token.canSubscribe) {
|
|
5401
|
+
store.patch({
|
|
5402
|
+
phase: "lobby",
|
|
5403
|
+
waiting: {
|
|
5404
|
+
admission: token.admissionState !== void 0 && token.admissionState !== "admitted" ? token.admissionState : "knocking",
|
|
5405
|
+
admittersPresent: null,
|
|
5406
|
+
reknockAllowed: true,
|
|
5407
|
+
knockExpiresAt: null,
|
|
5408
|
+
pollSeconds: token.pollSeconds ?? 3,
|
|
5409
|
+
liveVersion: 0
|
|
5410
|
+
}
|
|
5411
|
+
});
|
|
5195
5412
|
return;
|
|
5196
5413
|
}
|
|
5197
|
-
store.patch({ phase: "connecting" });
|
|
5414
|
+
store.patch({ phase: "connecting", waiting: null });
|
|
5198
5415
|
const created = await options.driverFactory(handlers);
|
|
5199
5416
|
if (disposed) {
|
|
5200
5417
|
await created.disconnect();
|
|
@@ -6453,7 +6670,7 @@ function observedPhase(input) {
|
|
|
6453
6670
|
case "left":
|
|
6454
6671
|
return "prejoin";
|
|
6455
6672
|
case "lobby":
|
|
6456
|
-
return
|
|
6673
|
+
return waitingPhase(snapshot);
|
|
6457
6674
|
case "requesting-token":
|
|
6458
6675
|
case "connecting":
|
|
6459
6676
|
return "joining";
|
|
@@ -6462,6 +6679,24 @@ function observedPhase(input) {
|
|
|
6462
6679
|
return "in_call";
|
|
6463
6680
|
}
|
|
6464
6681
|
}
|
|
6682
|
+
function waitingPhase(snapshot) {
|
|
6683
|
+
switch (snapshot.waiting?.admission) {
|
|
6684
|
+
case "waiting_host":
|
|
6685
|
+
return "waiting:host_not_started";
|
|
6686
|
+
case "denied":
|
|
6687
|
+
return "denied";
|
|
6688
|
+
case "expired":
|
|
6689
|
+
return "knock_expired";
|
|
6690
|
+
case "withdrawn":
|
|
6691
|
+
return "left";
|
|
6692
|
+
case "ended":
|
|
6693
|
+
return "ended";
|
|
6694
|
+
case "removed":
|
|
6695
|
+
return "refused:removed";
|
|
6696
|
+
default:
|
|
6697
|
+
return "waiting:knocking";
|
|
6698
|
+
}
|
|
6699
|
+
}
|
|
6465
6700
|
var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
6466
6701
|
function observe(input) {
|
|
6467
6702
|
const { snapshot } = input;
|
|
@@ -6476,7 +6711,7 @@ function observe(input) {
|
|
|
6476
6711
|
reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
|
|
6477
6712
|
role,
|
|
6478
6713
|
connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
|
|
6479
|
-
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
|
|
6714
|
+
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
6480
6715
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
6481
6716
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
6482
6717
|
audioBlocked: null,
|