@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/index.cjs
CHANGED
|
@@ -534,7 +534,17 @@ var MEET_ROUTES = {
|
|
|
534
534
|
*/
|
|
535
535
|
collab: "/api/v1/meet/collab",
|
|
536
536
|
/** Meet wave 5: translate finished caption lines into this viewer's language. */
|
|
537
|
-
captionsTranslate: "/api/v1/meet/captions/translate"
|
|
537
|
+
captionsTranslate: "/api/v1/meet/captions/translate",
|
|
538
|
+
/**
|
|
539
|
+
* S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
|
|
540
|
+
* person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
|
|
541
|
+
* phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
|
|
542
|
+
*/
|
|
543
|
+
state: "/api/v1/meet/state",
|
|
544
|
+
/** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
|
|
545
|
+
leave: "/api/v1/meet/leave",
|
|
546
|
+
/** S4: a pass holder (guests included) saw the recording notice, for this run. */
|
|
547
|
+
consent: "/api/v1/meet/consent"
|
|
538
548
|
};
|
|
539
549
|
function meetingPath(template, meetingId) {
|
|
540
550
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -608,6 +618,8 @@ function parseRoomToken(body, roomName) {
|
|
|
608
618
|
meetingPassExpiresAt: readString(payload, "meeting_pass_expires_at"),
|
|
609
619
|
sessionId: readString(payload, "session_id"),
|
|
610
620
|
lobby: readBoolean(payload, "lobby", false),
|
|
621
|
+
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
622
|
+
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
611
623
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null
|
|
612
624
|
};
|
|
613
625
|
}
|
|
@@ -1220,14 +1232,15 @@ var MEET_EVENTS = {
|
|
|
1220
1232
|
cancel: "mx.call.cancel",
|
|
1221
1233
|
/** You answered (or declined) on another device. Stop ringing here. */
|
|
1222
1234
|
settled: "mx.call.settled",
|
|
1223
|
-
/**
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1235
|
+
/**
|
|
1236
|
+
* 🚨 S4 (CORE-DESIGN §2.6): the ONE room-channel event — payload-free "something changed,
|
|
1237
|
+
* read again". Nothing a person must see rides it: a waiting person learns admitted /
|
|
1238
|
+
* denied / expired / ended by READING their row (`GET /v1/meet/state`) on a timer and on
|
|
1239
|
+
* every hint. The four payload broadcasts (`mx.lobby.knock`, `mx.lobby.admission`,
|
|
1240
|
+
* `mx.room.lock`, `mx.room.recording`) are gone with their senders and receivers. The
|
|
1241
|
+
* server publishes the same event on the same topic (aidream `admission.room_topic`).
|
|
1242
|
+
*/
|
|
1243
|
+
stateChanged: "mx.state.changed"
|
|
1231
1244
|
};
|
|
1232
1245
|
|
|
1233
1246
|
// src/core/types.ts
|
|
@@ -1253,6 +1266,7 @@ function isLinkFollower(standing) {
|
|
|
1253
1266
|
function isHostRole(role) {
|
|
1254
1267
|
return role === "host" || role === "cohost";
|
|
1255
1268
|
}
|
|
1269
|
+
var WAITING_ADMISSION_STATES = ["waiting_host", "knocking"];
|
|
1256
1270
|
|
|
1257
1271
|
// src/core/calls.ts
|
|
1258
1272
|
var TRANSITIONS = {
|
|
@@ -1865,7 +1879,14 @@ var RPCS = {
|
|
|
1865
1879
|
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
1866
1880
|
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
1867
1881
|
meetingBySlug: "meet_meeting_by_slug",
|
|
1868
|
-
|
|
1882
|
+
/*
|
|
1883
|
+
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
1884
|
+
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
1885
|
+
*/
|
|
1886
|
+
admit: "meet_admit",
|
|
1887
|
+
deny: "meet_deny",
|
|
1888
|
+
undeny: "meet_undeny",
|
|
1889
|
+
admitAll: "meet_admit_all",
|
|
1869
1890
|
setLock: "meet_set_lock",
|
|
1870
1891
|
recordConsent: "meet_record_consent",
|
|
1871
1892
|
/*
|
|
@@ -1908,13 +1929,13 @@ var RPCS = {
|
|
|
1908
1929
|
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1909
1930
|
search: "meet_search"
|
|
1910
1931
|
};
|
|
1911
|
-
var VOID_RPCS = [RPCS.
|
|
1932
|
+
var VOID_RPCS = [RPCS.recordConsent];
|
|
1912
1933
|
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";
|
|
1913
1934
|
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";
|
|
1914
1935
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
1915
1936
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
1916
1937
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
1917
|
-
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";
|
|
1938
|
+
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";
|
|
1918
1939
|
function projectStays(value) {
|
|
1919
1940
|
if (!Array.isArray(value)) return [];
|
|
1920
1941
|
const stays = [];
|
|
@@ -2281,7 +2302,16 @@ function createMeetRepository(options) {
|
|
|
2281
2302
|
const role = text(row, "role");
|
|
2282
2303
|
const admission = text(row, "admission_state");
|
|
2283
2304
|
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
2284
|
-
const states = [
|
|
2305
|
+
const states = [
|
|
2306
|
+
"waiting_host",
|
|
2307
|
+
"knocking",
|
|
2308
|
+
"admitted",
|
|
2309
|
+
"denied",
|
|
2310
|
+
"expired",
|
|
2311
|
+
"withdrawn",
|
|
2312
|
+
"removed",
|
|
2313
|
+
"ended"
|
|
2314
|
+
];
|
|
2285
2315
|
const userId = text(row, "participant_user_id");
|
|
2286
2316
|
const identity = requireText(row, "identity", operation);
|
|
2287
2317
|
return {
|
|
@@ -2295,6 +2325,8 @@ function createMeetRepository(options) {
|
|
|
2295
2325
|
userId: userId === null ? null : asUserId(userId),
|
|
2296
2326
|
role: known.includes(role) ? role : "participant",
|
|
2297
2327
|
admissionState: states.includes(admission) ? admission : "admitted",
|
|
2328
|
+
knockedAt: text(row, "knocked_at"),
|
|
2329
|
+
knockExpiresAt: text(row, "knock_expires_at"),
|
|
2298
2330
|
isAgent: bool(row, "is_agent", false),
|
|
2299
2331
|
joinedAt: text(row, "joined_at"),
|
|
2300
2332
|
leftAt: text(row, "left_at"),
|
|
@@ -2573,16 +2605,26 @@ function createMeetRepository(options) {
|
|
|
2573
2605
|
hasMore: rows.length > limit
|
|
2574
2606
|
};
|
|
2575
2607
|
},
|
|
2576
|
-
async
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2608
|
+
async decideAdmission({ meetingId, identity, decision, byUserId }) {
|
|
2609
|
+
const fn = decision === "admit" ? RPCS.admit : decision === "deny" ? RPCS.deny : RPCS.undeny;
|
|
2610
|
+
const row = await guard(
|
|
2611
|
+
`meet.${decision}`,
|
|
2612
|
+
() => db.rpc(fn, {
|
|
2580
2613
|
p_meeting_id: meetingId,
|
|
2581
2614
|
p_identity: identity,
|
|
2582
|
-
p_admitted: admitted,
|
|
2583
2615
|
p_by_user_id: byUserId
|
|
2584
2616
|
})
|
|
2585
2617
|
);
|
|
2618
|
+
return projectAttendee(row);
|
|
2619
|
+
},
|
|
2620
|
+
async admitAll(meetingId, byUserId) {
|
|
2621
|
+
const response = await db.rpc(RPCS.admitAll, {
|
|
2622
|
+
p_meeting_id: meetingId,
|
|
2623
|
+
p_by_user_id: byUserId
|
|
2624
|
+
});
|
|
2625
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.admitAll");
|
|
2626
|
+
const rows = Array.isArray(response.data) ? response.data : [];
|
|
2627
|
+
return rows.map(projectAttendee);
|
|
2586
2628
|
},
|
|
2587
2629
|
async setLock(meetingId, locked, byUserId) {
|
|
2588
2630
|
const row = await guard(
|
|
@@ -2930,6 +2972,7 @@ var NO_GRANTS = {
|
|
|
2930
2972
|
};
|
|
2931
2973
|
var EMPTY = {
|
|
2932
2974
|
phase: "idle",
|
|
2975
|
+
waiting: null,
|
|
2933
2976
|
meeting: null,
|
|
2934
2977
|
localIdentity: null,
|
|
2935
2978
|
grants: NO_GRANTS,
|
|
@@ -4207,19 +4250,25 @@ function roleOf(participants, identity) {
|
|
|
4207
4250
|
return participants.find((p) => p.identity === identity)?.role ?? "participant";
|
|
4208
4251
|
}
|
|
4209
4252
|
var END_MEETING_NOT_SERVED = "Ending a meeting is not available on this server yet";
|
|
4210
|
-
function lobbyEntryFrom(attendee) {
|
|
4211
|
-
if (attendee.admissionState
|
|
4253
|
+
function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
4254
|
+
if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;
|
|
4255
|
+
const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;
|
|
4256
|
+
if (!Number.isNaN(expires) && expires <= now) return null;
|
|
4212
4257
|
return {
|
|
4213
4258
|
identity: attendee.identity,
|
|
4214
4259
|
displayName: attendee.displayName,
|
|
4215
4260
|
avatarUrl: attendee.avatarUrl,
|
|
4216
4261
|
userId: attendee.userId,
|
|
4217
4262
|
state: "knocking",
|
|
4218
|
-
knockedAt: attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4263
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4264
|
+
knockExpiresAt: attendee.knockExpiresAt ?? null,
|
|
4219
4265
|
decidedAt: null,
|
|
4220
4266
|
decidedBy: null
|
|
4221
4267
|
};
|
|
4222
4268
|
}
|
|
4269
|
+
function decidedState(state) {
|
|
4270
|
+
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
4271
|
+
}
|
|
4223
4272
|
function createMeetingSession(options) {
|
|
4224
4273
|
const { manager, repository, store, identity } = options;
|
|
4225
4274
|
let channel = null;
|
|
@@ -4243,11 +4292,6 @@ function createMeetingSession(options) {
|
|
|
4243
4292
|
}
|
|
4244
4293
|
return identity.userId;
|
|
4245
4294
|
}
|
|
4246
|
-
function knockIdentity() {
|
|
4247
|
-
return asParticipantIdentity(
|
|
4248
|
-
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
4249
|
-
);
|
|
4250
|
-
}
|
|
4251
4295
|
function requireMeeting(operation) {
|
|
4252
4296
|
const meeting = store.snapshot().meeting;
|
|
4253
4297
|
if (meeting === null) {
|
|
@@ -4259,6 +4303,191 @@ function createMeetingSession(options) {
|
|
|
4259
4303
|
}
|
|
4260
4304
|
return meeting;
|
|
4261
4305
|
}
|
|
4306
|
+
let polling = false;
|
|
4307
|
+
let pollTimer = null;
|
|
4308
|
+
let reading = null;
|
|
4309
|
+
let admittedOnce = false;
|
|
4310
|
+
let unlisten = null;
|
|
4311
|
+
function report(level, message, remedy) {
|
|
4312
|
+
options.onDiagnostic?.(remedy === void 0 ? { level, message } : { level, message, remedy });
|
|
4313
|
+
}
|
|
4314
|
+
function pollMs() {
|
|
4315
|
+
const seconds = store.snapshot().waiting?.pollSeconds ?? 3;
|
|
4316
|
+
return Math.max(1, Math.min(60, seconds)) * 1e3;
|
|
4317
|
+
}
|
|
4318
|
+
function schedule(ms2) {
|
|
4319
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4320
|
+
if (!polling) return;
|
|
4321
|
+
pollTimer = setTimeout(() => {
|
|
4322
|
+
pollTimer = null;
|
|
4323
|
+
void readState2().finally(() => schedule(pollMs()));
|
|
4324
|
+
}, ms2);
|
|
4325
|
+
}
|
|
4326
|
+
function listen() {
|
|
4327
|
+
if (unlisten !== null || typeof window === "undefined") return;
|
|
4328
|
+
const nudge = () => {
|
|
4329
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
4330
|
+
void readState2();
|
|
4331
|
+
};
|
|
4332
|
+
const goodbye = () => {
|
|
4333
|
+
const meeting = store.snapshot().meeting;
|
|
4334
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4335
|
+
const waiting = store.snapshot().waiting;
|
|
4336
|
+
if (meeting === null || pass === null || waiting === null) return;
|
|
4337
|
+
if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;
|
|
4338
|
+
try {
|
|
4339
|
+
navigator.sendBeacon?.(
|
|
4340
|
+
options.api.url(MEET_ROUTES.leave),
|
|
4341
|
+
new Blob([JSON.stringify({ meeting_id: meeting.id, intent: "withdraw", meeting_pass: pass })], {
|
|
4342
|
+
type: "application/json"
|
|
4343
|
+
})
|
|
4344
|
+
);
|
|
4345
|
+
} catch {
|
|
4346
|
+
}
|
|
4347
|
+
};
|
|
4348
|
+
window.addEventListener("online", nudge);
|
|
4349
|
+
window.addEventListener("pagehide", goodbye);
|
|
4350
|
+
document.addEventListener("visibilitychange", nudge);
|
|
4351
|
+
unlisten = () => {
|
|
4352
|
+
window.removeEventListener("online", nudge);
|
|
4353
|
+
window.removeEventListener("pagehide", goodbye);
|
|
4354
|
+
document.removeEventListener("visibilitychange", nudge);
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
function stopWaiting() {
|
|
4358
|
+
polling = false;
|
|
4359
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4360
|
+
pollTimer = null;
|
|
4361
|
+
unlisten?.();
|
|
4362
|
+
unlisten = null;
|
|
4363
|
+
}
|
|
4364
|
+
function patchWaiting(next) {
|
|
4365
|
+
const current = store.snapshot().waiting;
|
|
4366
|
+
const base = current ?? {
|
|
4367
|
+
admission: "knocking",
|
|
4368
|
+
admittersPresent: null,
|
|
4369
|
+
reknockAllowed: true,
|
|
4370
|
+
knockExpiresAt: null,
|
|
4371
|
+
pollSeconds: 3,
|
|
4372
|
+
liveVersion: 0
|
|
4373
|
+
};
|
|
4374
|
+
store.patch({ waiting: { ...base, ...next } });
|
|
4375
|
+
}
|
|
4376
|
+
function apply(read) {
|
|
4377
|
+
const snapshot = store.snapshot();
|
|
4378
|
+
if (snapshot.phase !== "lobby") return;
|
|
4379
|
+
const admission = read.admission_state ?? null;
|
|
4380
|
+
const ended = admission === "ended" || read.session_phase === "ended" || read.meeting_phase === "ended";
|
|
4381
|
+
if (admission === "admitted" && !ended) {
|
|
4382
|
+
if (admittedOnce) return;
|
|
4383
|
+
admittedOnce = true;
|
|
4384
|
+
stopWaiting();
|
|
4385
|
+
options.onAdmitted?.();
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
if (ended) {
|
|
4389
|
+
stopWaiting();
|
|
4390
|
+
patchWaiting({ admission: "ended" });
|
|
4391
|
+
const meeting = snapshot.meeting;
|
|
4392
|
+
if (read.meeting_phase === "ended" && meeting !== null && meeting.endedAt === null) {
|
|
4393
|
+
store.patch({ meeting: { ...meeting, endedAt: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
4394
|
+
}
|
|
4395
|
+
return;
|
|
4396
|
+
}
|
|
4397
|
+
if (admission === null) return;
|
|
4398
|
+
patchWaiting({
|
|
4399
|
+
admission,
|
|
4400
|
+
admittersPresent: read.admitters_present ?? null,
|
|
4401
|
+
reknockAllowed: read.reknock_allowed ?? true,
|
|
4402
|
+
knockExpiresAt: read.knock_expires_at ?? null,
|
|
4403
|
+
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
4404
|
+
liveVersion: read.live_version ?? 0
|
|
4405
|
+
});
|
|
4406
|
+
if (admission === "denied" || admission === "expired" || admission === "withdrawn" || admission === "removed") {
|
|
4407
|
+
stopWaiting();
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
async function readState2() {
|
|
4411
|
+
if (reading !== null) return reading;
|
|
4412
|
+
const meeting = store.snapshot().meeting;
|
|
4413
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4414
|
+
if (meeting === null || pass === null) return;
|
|
4415
|
+
reading = (async () => {
|
|
4416
|
+
try {
|
|
4417
|
+
const response = await options.api.request({
|
|
4418
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
4419
|
+
method: "GET",
|
|
4420
|
+
requireAuth: false,
|
|
4421
|
+
operation: "meet.state",
|
|
4422
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
4423
|
+
});
|
|
4424
|
+
if (!response.ok) {
|
|
4425
|
+
let body = null;
|
|
4426
|
+
try {
|
|
4427
|
+
body = await response.json();
|
|
4428
|
+
} catch {
|
|
4429
|
+
body = null;
|
|
4430
|
+
}
|
|
4431
|
+
const refusal2 = readServerRefusal(body);
|
|
4432
|
+
report(
|
|
4433
|
+
"warn",
|
|
4434
|
+
refusal2?.reason ?? `meet.state: the server answered ${response.status}`,
|
|
4435
|
+
refusal2?.remedy ?? "The waiting room keeps asking; nothing on this screen changes until the server answers."
|
|
4436
|
+
);
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
apply(await response.json());
|
|
4440
|
+
} catch (error) {
|
|
4441
|
+
report("info", `meet.state: ${String(error)}`, "Waiting continues; the state is read again shortly.");
|
|
4442
|
+
} finally {
|
|
4443
|
+
reading = null;
|
|
4444
|
+
}
|
|
4445
|
+
})();
|
|
4446
|
+
return reading;
|
|
4447
|
+
}
|
|
4448
|
+
async function postLeave(intent) {
|
|
4449
|
+
const meeting = requireMeeting(`meet.${intent}`);
|
|
4450
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4451
|
+
const response = await options.api.request({
|
|
4452
|
+
path: MEET_ROUTES.leave,
|
|
4453
|
+
method: "POST",
|
|
4454
|
+
requireAuth: false,
|
|
4455
|
+
operation: `meet.${intent}`,
|
|
4456
|
+
body: { meeting_id: meeting.id, intent },
|
|
4457
|
+
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
4458
|
+
});
|
|
4459
|
+
if (!response.ok) {
|
|
4460
|
+
let body = null;
|
|
4461
|
+
try {
|
|
4462
|
+
body = await response.json();
|
|
4463
|
+
} catch {
|
|
4464
|
+
body = null;
|
|
4465
|
+
}
|
|
4466
|
+
const refusal2 = readServerRefusal(body);
|
|
4467
|
+
throw new MeetError(
|
|
4468
|
+
"transport",
|
|
4469
|
+
refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
|
|
4470
|
+
refusal2?.remedy ?? "You are still in the waiting room. Try again."
|
|
4471
|
+
);
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
function hint() {
|
|
4475
|
+
channel?.send(MEET_EVENTS.stateChanged, { v: null });
|
|
4476
|
+
}
|
|
4477
|
+
async function refreshQueue() {
|
|
4478
|
+
const meeting = store.snapshot().meeting;
|
|
4479
|
+
if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;
|
|
4480
|
+
try {
|
|
4481
|
+
const attendees = await repository.attendees(meeting.id);
|
|
4482
|
+
store.learn(attendees);
|
|
4483
|
+
for (const attendee of attendees) {
|
|
4484
|
+
const entry = lobbyEntryFrom(attendee);
|
|
4485
|
+
if (entry !== null) store.upsertLobby(entry);
|
|
4486
|
+
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4487
|
+
}
|
|
4488
|
+
} catch {
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4262
4491
|
return {
|
|
4263
4492
|
watch(roomName) {
|
|
4264
4493
|
channel?.close();
|
|
@@ -4266,58 +4495,22 @@ function createMeetingSession(options) {
|
|
|
4266
4495
|
topic: roomTopic(roomName),
|
|
4267
4496
|
broadcast: [
|
|
4268
4497
|
{
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
{
|
|
4275
|
-
event: MEET_EVENTS.admission,
|
|
4276
|
-
onMessage: (message) => {
|
|
4277
|
-
const verdict = message.data;
|
|
4278
|
-
const decided = asParticipantIdentity(verdict.identity);
|
|
4279
|
-
store.setAdmission(
|
|
4280
|
-
decided,
|
|
4281
|
-
verdict.admitted ? "admitted" : "denied",
|
|
4282
|
-
verdict.by === null ? null : verdict.by
|
|
4283
|
-
);
|
|
4284
|
-
if (decided === knockIdentity()) {
|
|
4285
|
-
if (verdict.admitted) options.onAdmitted?.();
|
|
4286
|
-
else options.onDenied?.();
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
},
|
|
4290
|
-
{
|
|
4291
|
-
event: MEET_EVENTS.lock,
|
|
4292
|
-
onMessage: (message) => {
|
|
4293
|
-
const { locked } = message.data;
|
|
4294
|
-
const meeting = store.snapshot().meeting;
|
|
4295
|
-
store.patch({
|
|
4296
|
-
locked,
|
|
4297
|
-
...meeting !== null ? { meeting: { ...meeting, locked } } : {}
|
|
4298
|
-
});
|
|
4299
|
-
}
|
|
4300
|
-
},
|
|
4301
|
-
{
|
|
4302
|
-
event: MEET_EVENTS.recording,
|
|
4303
|
-
onMessage: (message) => {
|
|
4304
|
-
const payload = message.data;
|
|
4305
|
-
store.setRecording(
|
|
4306
|
-
payload.state,
|
|
4307
|
-
payload.by === null ? null : payload.by,
|
|
4308
|
-
"room"
|
|
4309
|
-
);
|
|
4498
|
+
// 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.
|
|
4499
|
+
event: MEET_EVENTS.stateChanged,
|
|
4500
|
+
onMessage: () => {
|
|
4501
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4502
|
+
void refreshQueue();
|
|
4310
4503
|
}
|
|
4311
4504
|
}
|
|
4312
4505
|
],
|
|
4313
|
-
// THE BACKFILL DOOR. A
|
|
4314
|
-
//
|
|
4315
|
-
// re-announce on their own timer.
|
|
4506
|
+
// THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting
|
|
4507
|
+
// row, and a waiting tab re-reads its own state.
|
|
4316
4508
|
onBackfill: async () => {
|
|
4317
4509
|
const meeting = store.snapshot().meeting;
|
|
4318
4510
|
if (meeting === null) return;
|
|
4319
4511
|
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
4320
4512
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
4513
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4321
4514
|
}
|
|
4322
4515
|
});
|
|
4323
4516
|
},
|
|
@@ -4378,7 +4571,7 @@ function createMeetingSession(options) {
|
|
|
4378
4571
|
store.upsertLobby(entry);
|
|
4379
4572
|
return;
|
|
4380
4573
|
}
|
|
4381
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4574
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4382
4575
|
}
|
|
4383
4576
|
},
|
|
4384
4577
|
{
|
|
@@ -4430,62 +4623,75 @@ function createMeetingSession(options) {
|
|
|
4430
4623
|
if (entry !== null) {
|
|
4431
4624
|
store.upsertLobby(entry);
|
|
4432
4625
|
} else {
|
|
4433
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4626
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4434
4627
|
}
|
|
4435
4628
|
}
|
|
4436
4629
|
}
|
|
4437
4630
|
});
|
|
4438
4631
|
meetingChannel.requestBackfill("manual");
|
|
4439
4632
|
},
|
|
4440
|
-
knock(roomName) {
|
|
4441
|
-
const handle = channel ?? manager.open({ topic: roomTopic(roomName) });
|
|
4442
|
-
channel = handle;
|
|
4443
|
-
const entry = {
|
|
4444
|
-
identity: knockIdentity(),
|
|
4445
|
-
displayName: identity.displayName,
|
|
4446
|
-
avatarUrl: identity.avatarUrl,
|
|
4447
|
-
userId: identity.userId,
|
|
4448
|
-
state: "knocking",
|
|
4449
|
-
knockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4450
|
-
decidedAt: null,
|
|
4451
|
-
decidedBy: null
|
|
4452
|
-
};
|
|
4453
|
-
store.upsertLobby(entry);
|
|
4454
|
-
handle.send(MEET_EVENTS.knock, entry);
|
|
4455
|
-
},
|
|
4456
4633
|
async admit(entry, admitted) {
|
|
4457
4634
|
const by = requireHostUser("meet.admit");
|
|
4458
4635
|
const meeting = requireMeeting("meet.admit");
|
|
4459
|
-
await repository.
|
|
4636
|
+
const row = await repository.decideAdmission({
|
|
4460
4637
|
meetingId: meeting.id,
|
|
4461
4638
|
identity: entry.identity,
|
|
4462
|
-
admitted,
|
|
4639
|
+
decision: admitted ? "admit" : "deny",
|
|
4463
4640
|
byUserId: by
|
|
4464
4641
|
});
|
|
4465
4642
|
store.upsertLobby({
|
|
4466
4643
|
...entry,
|
|
4467
|
-
state:
|
|
4644
|
+
state: decidedState(row.admissionState),
|
|
4468
4645
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4469
4646
|
decidedBy: by
|
|
4470
4647
|
});
|
|
4471
|
-
|
|
4648
|
+
hint();
|
|
4649
|
+
},
|
|
4650
|
+
async undeny(entry) {
|
|
4651
|
+
const by = requireHostUser("meet.undeny");
|
|
4652
|
+
const meeting = requireMeeting("meet.undeny");
|
|
4653
|
+
const row = await repository.decideAdmission({
|
|
4654
|
+
meetingId: meeting.id,
|
|
4472
4655
|
identity: entry.identity,
|
|
4473
|
-
|
|
4474
|
-
by
|
|
4656
|
+
decision: "undeny",
|
|
4657
|
+
byUserId: by
|
|
4475
4658
|
});
|
|
4659
|
+
store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: (/* @__PURE__ */ new Date()).toISOString(), decidedBy: by });
|
|
4660
|
+
hint();
|
|
4476
4661
|
},
|
|
4477
4662
|
async admitAll() {
|
|
4478
|
-
const
|
|
4479
|
-
|
|
4480
|
-
|
|
4663
|
+
const by = requireHostUser("meet.admitAll");
|
|
4664
|
+
const meeting = requireMeeting("meet.admitAll");
|
|
4665
|
+
const rows = await repository.admitAll(meeting.id, by);
|
|
4666
|
+
for (const row of rows) {
|
|
4667
|
+
store.setAdmission(row.identity, row.admissionState, by);
|
|
4481
4668
|
}
|
|
4669
|
+
hint();
|
|
4670
|
+
},
|
|
4671
|
+
startWaiting() {
|
|
4672
|
+
if (polling) return;
|
|
4673
|
+
polling = true;
|
|
4674
|
+
admittedOnce = false;
|
|
4675
|
+
listen();
|
|
4676
|
+
schedule(0);
|
|
4677
|
+
},
|
|
4678
|
+
stopWaiting,
|
|
4679
|
+
readState: readState2,
|
|
4680
|
+
async withdraw() {
|
|
4681
|
+
stopWaiting();
|
|
4682
|
+
await postLeave("withdraw");
|
|
4683
|
+
patchWaiting({ admission: "withdrawn" });
|
|
4684
|
+
hint();
|
|
4685
|
+
},
|
|
4686
|
+
async askAgain() {
|
|
4687
|
+
await options.rejoin?.();
|
|
4482
4688
|
},
|
|
4483
4689
|
async setLock(locked) {
|
|
4484
4690
|
const by = requireHostUser("meet.setLock");
|
|
4485
4691
|
const meeting = requireMeeting("meet.setLock");
|
|
4486
4692
|
const updated = await repository.setLock(meeting.id, locked, by);
|
|
4487
4693
|
store.patch({ meeting: updated, locked: updated.locked });
|
|
4488
|
-
|
|
4694
|
+
hint();
|
|
4489
4695
|
return updated;
|
|
4490
4696
|
},
|
|
4491
4697
|
/**
|
|
@@ -4543,6 +4749,7 @@ function createMeetingSession(options) {
|
|
|
4543
4749
|
return { endedAt, served: true };
|
|
4544
4750
|
},
|
|
4545
4751
|
dispose() {
|
|
4752
|
+
stopWaiting();
|
|
4546
4753
|
channel?.close();
|
|
4547
4754
|
channel = null;
|
|
4548
4755
|
meetingChannel?.close();
|
|
@@ -5403,7 +5610,7 @@ function createRoomEngine(options) {
|
|
|
5403
5610
|
}
|
|
5404
5611
|
};
|
|
5405
5612
|
async function connect(request) {
|
|
5406
|
-
store.patch({ phase: "requesting-token", failure: null });
|
|
5613
|
+
store.patch({ phase: "requesting-token", failure: null, waiting: null });
|
|
5407
5614
|
const token = await tokens.get({
|
|
5408
5615
|
roomName: request.roomName,
|
|
5409
5616
|
identity,
|
|
@@ -5423,11 +5630,21 @@ function createRoomEngine(options) {
|
|
|
5423
5630
|
canSubscribe: token.canSubscribe
|
|
5424
5631
|
}
|
|
5425
5632
|
});
|
|
5426
|
-
if (token.lobby) {
|
|
5427
|
-
store.patch({
|
|
5633
|
+
if (token.lobby || !token.canSubscribe) {
|
|
5634
|
+
store.patch({
|
|
5635
|
+
phase: "lobby",
|
|
5636
|
+
waiting: {
|
|
5637
|
+
admission: token.admissionState !== void 0 && token.admissionState !== "admitted" ? token.admissionState : "knocking",
|
|
5638
|
+
admittersPresent: null,
|
|
5639
|
+
reknockAllowed: true,
|
|
5640
|
+
knockExpiresAt: null,
|
|
5641
|
+
pollSeconds: token.pollSeconds ?? 3,
|
|
5642
|
+
liveVersion: 0
|
|
5643
|
+
}
|
|
5644
|
+
});
|
|
5428
5645
|
return;
|
|
5429
5646
|
}
|
|
5430
|
-
store.patch({ phase: "connecting" });
|
|
5647
|
+
store.patch({ phase: "connecting", waiting: null });
|
|
5431
5648
|
const created = await options.driverFactory(handlers);
|
|
5432
5649
|
if (disposed) {
|
|
5433
5650
|
await created.disconnect();
|
|
@@ -6686,7 +6903,7 @@ function observedPhase(input) {
|
|
|
6686
6903
|
case "left":
|
|
6687
6904
|
return "prejoin";
|
|
6688
6905
|
case "lobby":
|
|
6689
|
-
return
|
|
6906
|
+
return waitingPhase(snapshot);
|
|
6690
6907
|
case "requesting-token":
|
|
6691
6908
|
case "connecting":
|
|
6692
6909
|
return "joining";
|
|
@@ -6695,6 +6912,24 @@ function observedPhase(input) {
|
|
|
6695
6912
|
return "in_call";
|
|
6696
6913
|
}
|
|
6697
6914
|
}
|
|
6915
|
+
function waitingPhase(snapshot) {
|
|
6916
|
+
switch (snapshot.waiting?.admission) {
|
|
6917
|
+
case "waiting_host":
|
|
6918
|
+
return "waiting:host_not_started";
|
|
6919
|
+
case "denied":
|
|
6920
|
+
return "denied";
|
|
6921
|
+
case "expired":
|
|
6922
|
+
return "knock_expired";
|
|
6923
|
+
case "withdrawn":
|
|
6924
|
+
return "left";
|
|
6925
|
+
case "ended":
|
|
6926
|
+
return "ended";
|
|
6927
|
+
case "removed":
|
|
6928
|
+
return "refused:removed";
|
|
6929
|
+
default:
|
|
6930
|
+
return "waiting:knocking";
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6698
6933
|
var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
6699
6934
|
function observe(input) {
|
|
6700
6935
|
const { snapshot } = input;
|
|
@@ -6709,7 +6944,7 @@ function observe(input) {
|
|
|
6709
6944
|
reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
|
|
6710
6945
|
role,
|
|
6711
6946
|
connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
|
|
6712
|
-
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
|
|
6947
|
+
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
6713
6948
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
6714
6949
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
6715
6950
|
audioBlocked: null,
|