sabha-meetings-react 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,1468 @@
1
1
  'use strict';
2
2
 
3
+ var socket_ioClient = require('socket.io-client');
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/api/auth.ts
8
+ function asUser(data) {
9
+ if (!data?.userId && !data.id) {
10
+ throw new Error("Auth response missing userId");
11
+ }
12
+ return {
13
+ ...data,
14
+ userId: data.userId || String(data.id)
15
+ };
16
+ }
17
+ function sessionFrom(data, cookie) {
18
+ return {
19
+ user: asUser(data),
20
+ accessToken: data.token,
21
+ refreshToken: data.refreshToken,
22
+ sessionCookie: cookie ?? void 0
23
+ };
24
+ }
25
+ var AuthApi = class {
26
+ constructor(http, getSessionCookie) {
27
+ this.http = http;
28
+ this.getSessionCookie = getSessionCookie;
29
+ }
30
+ async register(input) {
31
+ return this.http.request("/api/auth/register", {
32
+ method: "POST",
33
+ auth: false,
34
+ body: input
35
+ });
36
+ }
37
+ async verifyEmail(input) {
38
+ const data = await this.http.request("/api/auth/verify", {
39
+ method: "POST",
40
+ auth: false,
41
+ body: input
42
+ });
43
+ return sessionFrom(data, this.getSessionCookie());
44
+ }
45
+ async login(input) {
46
+ const data = await this.http.request("/api/auth/login", {
47
+ method: "POST",
48
+ auth: false,
49
+ body: input
50
+ });
51
+ return sessionFrom(data, this.getSessionCookie());
52
+ }
53
+ async refresh(refreshToken) {
54
+ const data = await this.http.request("/api/auth/refresh", {
55
+ method: "POST",
56
+ auth: false,
57
+ body: { refreshToken }
58
+ });
59
+ return {
60
+ accessToken: data.token,
61
+ refreshToken: data.refreshToken ?? refreshToken,
62
+ sessionCookie: this.getSessionCookie()
63
+ };
64
+ }
65
+ async me() {
66
+ const data = await this.http.request("/api/auth/me", { method: "GET" });
67
+ return asUser(data);
68
+ }
69
+ async logout(refreshToken) {
70
+ await this.http.request("/api/auth/logout", {
71
+ method: "POST",
72
+ body: refreshToken ? { refreshToken } : {}
73
+ });
74
+ }
75
+ };
76
+
77
+ // src/api/meetings.ts
78
+ function asRecord(value) {
79
+ return value && typeof value === "object" ? value : null;
80
+ }
81
+ function normalizeMeeting(raw) {
82
+ const record = asRecord(raw) ?? {};
83
+ const nested = asRecord(record.meeting) ?? record;
84
+ const id = nested.id || record.id;
85
+ if (!id || typeof id !== "string") {
86
+ throw new Error("Meeting response is missing an id");
87
+ }
88
+ return {
89
+ ...nested,
90
+ id,
91
+ title: typeof nested.title === "string" ? nested.title : "Untitled meeting",
92
+ notes: typeof nested.notes === "string" ? nested.notes : nested.notes ?? "",
93
+ code: typeof nested.code === "string" ? nested.code : void 0,
94
+ startAt: typeof nested.startAt === "string" ? nested.startAt : (/* @__PURE__ */ new Date()).toISOString(),
95
+ durationMin: typeof nested.durationMin === "number" ? nested.durationMin : 30,
96
+ timeZone: typeof nested.timeZone === "string" ? nested.timeZone : void 0,
97
+ groupId: typeof nested.groupId === "string" ? nested.groupId : "",
98
+ owner: Boolean(nested.owner),
99
+ canEdit: nested.canEdit !== false,
100
+ canCancel: nested.canCancel !== false,
101
+ organizer: nested.organizer,
102
+ invitees: Array.isArray(nested.invitees) ? nested.invitees : []
103
+ };
104
+ }
105
+ var MeetingsApi = class {
106
+ constructor(http) {
107
+ this.http = http;
108
+ }
109
+ async list() {
110
+ const data = await this.http.request("/api/meetings", {
111
+ method: "GET"
112
+ });
113
+ return (data.meetings ?? []).map(normalizeMeeting);
114
+ }
115
+ async create(input) {
116
+ const body = {
117
+ title: input.title,
118
+ startAt: input.startAt ?? (/* @__PURE__ */ new Date()).toISOString(),
119
+ durationMin: input.durationMin ?? 30,
120
+ timeZone: input.timeZone ?? (typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "UTC"),
121
+ inviteeIds: input.inviteeIds ?? [],
122
+ ...input.notes !== void 0 ? { notes: input.notes } : {},
123
+ ...input.groupId !== void 0 ? { groupId: input.groupId } : {}
124
+ };
125
+ const data = await this.http.request("/api/meetings", {
126
+ method: "POST",
127
+ body
128
+ });
129
+ return normalizeMeeting(data.meeting ?? data);
130
+ }
131
+ async update(meetingId, input) {
132
+ const data = await this.http.request(`/api/meetings/${meetingId}`, {
133
+ method: "PATCH",
134
+ body: input
135
+ });
136
+ return normalizeMeeting(data.meeting ?? data);
137
+ }
138
+ async cancel(meetingId) {
139
+ await this.http.request(`/api/meetings/${meetingId}`, {
140
+ method: "DELETE"
141
+ });
142
+ }
143
+ };
144
+
145
+ // src/errors.ts
146
+ var SabhaError = class extends Error {
147
+ constructor(message, options) {
148
+ super(message);
149
+ this.name = "SabhaError";
150
+ this.status = options?.status;
151
+ this.code = options?.code;
152
+ this.details = options?.details;
153
+ if (options?.cause !== void 0) {
154
+ this.cause = options.cause;
155
+ }
156
+ }
157
+ };
158
+ function toSabhaError(error, fallback = "Sabha request failed") {
159
+ if (error instanceof SabhaError) return error;
160
+ if (error instanceof Error) {
161
+ return new SabhaError(error.message || fallback, { cause: error });
162
+ }
163
+ return new SabhaError(fallback, { details: error });
164
+ }
165
+
166
+ // src/http/client.ts
167
+ function joinUrl(baseUrl, path) {
168
+ const base = baseUrl.replace(/\/+$/, "");
169
+ const suffix = path.startsWith("/") ? path : `/${path}`;
170
+ return `${base}${suffix}`;
171
+ }
172
+ function buildQuery(query) {
173
+ if (!query) return "";
174
+ const params = new URLSearchParams();
175
+ for (const [key, value] of Object.entries(query)) {
176
+ if (value === void 0 || value === null) continue;
177
+ params.set(key, String(value));
178
+ }
179
+ const qs = params.toString();
180
+ return qs ? `?${qs}` : "";
181
+ }
182
+ async function parseBody(response) {
183
+ if (response.status === 204) return null;
184
+ const text = await response.text();
185
+ if (!text) return null;
186
+ try {
187
+ return JSON.parse(text);
188
+ } catch {
189
+ return text;
190
+ }
191
+ }
192
+ function messageFromBody(body, fallback) {
193
+ if (!body || typeof body !== "object") return fallback;
194
+ const record = body;
195
+ if (typeof record.message === "string") return record.message;
196
+ if (typeof record.error === "string") return record.error;
197
+ return fallback;
198
+ }
199
+ function readSetCookie(response) {
200
+ const anyHeaders = response.headers;
201
+ const list = typeof anyHeaders.getSetCookie === "function" ? anyHeaders.getSetCookie() : (() => {
202
+ const single = response.headers.get("set-cookie");
203
+ return single ? [single] : [];
204
+ })();
205
+ for (const entry of list) {
206
+ const match = /(?:^|,\s*)vm_session=([^;]+)/i.exec(entry);
207
+ if (match?.[1]) return match[1];
208
+ }
209
+ return null;
210
+ }
211
+ var HttpClient = class {
212
+ constructor(options) {
213
+ this.options = options;
214
+ }
215
+ async request(path, options = {}) {
216
+ const run = async (retried) => {
217
+ const headers = {
218
+ Accept: "application/json",
219
+ ...options.headers
220
+ };
221
+ const runtime = globalThis;
222
+ const isBrowser = typeof runtime.window !== "undefined" && typeof runtime.document !== "undefined";
223
+ const origin = this.options.getOrigin();
224
+ if (!isBrowser && origin) headers.Origin = origin;
225
+ const tokens = this.options.getTokens();
226
+ const needsAuth = options.auth !== false;
227
+ if (tokens?.accessToken) {
228
+ headers.Authorization = `Bearer ${tokens.accessToken}`;
229
+ } else if (needsAuth && !this.options.withCredentials && !tokens?.sessionCookie) {
230
+ throw new SabhaError(
231
+ "Not authenticated. Call login() or pass accessToken/sessionCookie.",
232
+ { code: "UNAUTHENTICATED" }
233
+ );
234
+ }
235
+ if (tokens?.sessionCookie) {
236
+ headers.Cookie = `vm_session=${tokens.sessionCookie}`;
237
+ }
238
+ if (options.body !== void 0) {
239
+ headers["Content-Type"] = "application/json";
240
+ }
241
+ const response = await fetch(
242
+ `${joinUrl(this.options.baseUrl, path)}${buildQuery(options.query)}`,
243
+ {
244
+ method: options.method ?? (options.body !== void 0 ? "POST" : "GET"),
245
+ headers,
246
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
247
+ credentials: this.options.withCredentials ? "include" : "same-origin"
248
+ }
249
+ );
250
+ const session = readSetCookie(response);
251
+ if (session) this.options.onSessionCookie?.(session);
252
+ const body = await parseBody(response);
253
+ if (response.status === 401 && needsAuth && !retried && this.options.onUnauthorized) {
254
+ const refreshed = await this.options.onUnauthorized();
255
+ if (refreshed) return run(true);
256
+ }
257
+ if (!response.ok) {
258
+ throw new SabhaError(messageFromBody(body, `HTTP ${response.status}`), {
259
+ status: response.status,
260
+ details: body
261
+ });
262
+ }
263
+ return body;
264
+ };
265
+ return run(false);
266
+ }
267
+ };
268
+
269
+ // src/media/MediaController.ts
270
+ var MediaController = class {
271
+ constructor(policy) {
272
+ this.policy = policy;
273
+ this.micStream = null;
274
+ this.camStream = null;
275
+ this.screenStream = null;
276
+ this.listeners = /* @__PURE__ */ new Set();
277
+ this.state = {
278
+ audioEnabled: false,
279
+ videoEnabled: false,
280
+ screenSharing: false,
281
+ screenOff: policy.screenOff
282
+ };
283
+ }
284
+ getMediaState() {
285
+ return { ...this.state };
286
+ }
287
+ getMicrophoneStream() {
288
+ return this.micStream;
289
+ }
290
+ getCameraStream() {
291
+ return this.camStream;
292
+ }
293
+ getScreenStream() {
294
+ return this.screenStream;
295
+ }
296
+ /** Combined local A/V tracks for attaching to a peer connection / video element. */
297
+ getLocalStream() {
298
+ const stream = new MediaStream();
299
+ this.micStream?.getAudioTracks().forEach((t) => stream.addTrack(t));
300
+ if (!this.state.screenOff) {
301
+ this.camStream?.getVideoTracks().forEach((t) => stream.addTrack(t));
302
+ }
303
+ return stream;
304
+ }
305
+ onChange(listener) {
306
+ this.listeners.add(listener);
307
+ return () => this.listeners.delete(listener);
308
+ }
309
+ async start(policy) {
310
+ if (policy) this.policy = { ...this.policy, ...policy };
311
+ this.state.screenOff = this.policy.screenOff;
312
+ if (this.policy.audio) await this.setAudioEnabled(true);
313
+ if (this.policy.video && !this.policy.screenOff) await this.setVideoEnabled(true);
314
+ return this.getMediaState();
315
+ }
316
+ async setAudioEnabled(enabled) {
317
+ this.ensureMediaDevices();
318
+ if (!enabled) {
319
+ this.micStream?.getTracks().forEach((t) => {
320
+ t.enabled = false;
321
+ t.stop();
322
+ });
323
+ this.micStream = null;
324
+ return this.publish({ audioEnabled: false });
325
+ }
326
+ if (!this.micStream) {
327
+ this.micStream = await navigator.mediaDevices.getUserMedia({
328
+ audio: true,
329
+ video: false
330
+ });
331
+ } else {
332
+ this.micStream.getAudioTracks().forEach((t) => {
333
+ t.enabled = true;
334
+ });
335
+ }
336
+ return this.publish({ audioEnabled: true });
337
+ }
338
+ async setVideoEnabled(enabled) {
339
+ this.ensureMediaDevices();
340
+ if (!enabled) {
341
+ this.camStream?.getTracks().forEach((t) => {
342
+ t.enabled = false;
343
+ t.stop();
344
+ });
345
+ this.camStream = null;
346
+ return this.publish({ videoEnabled: false });
347
+ }
348
+ if (this.state.screenOff) {
349
+ throw new SabhaError("Camera is unavailable while screen is off", {
350
+ code: "SCREEN_OFF"
351
+ });
352
+ }
353
+ if (!this.camStream) {
354
+ this.camStream = await navigator.mediaDevices.getUserMedia({
355
+ audio: false,
356
+ video: { facingMode: "user" }
357
+ });
358
+ } else {
359
+ this.camStream.getVideoTracks().forEach((t) => {
360
+ t.enabled = true;
361
+ });
362
+ }
363
+ return this.publish({ videoEnabled: true });
364
+ }
365
+ async setScreenOff(off) {
366
+ if (off) {
367
+ await this.setVideoEnabled(false);
368
+ await this.stopScreenShare();
369
+ return this.publish({ screenOff: true, videoEnabled: false, screenSharing: false });
370
+ }
371
+ this.publish({ screenOff: false });
372
+ if (this.policy.video) await this.setVideoEnabled(true);
373
+ return this.getMediaState();
374
+ }
375
+ async startScreenShare() {
376
+ this.ensureMediaDevices();
377
+ if (!this.policy.allowScreenShare) {
378
+ throw new SabhaError("Screen sharing is disabled by media policy", {
379
+ code: "SCREEN_SHARE_BLOCKED"
380
+ });
381
+ }
382
+ if (this.state.screenOff) {
383
+ throw new SabhaError("Cannot share screen while screen is off", {
384
+ code: "SCREEN_OFF"
385
+ });
386
+ }
387
+ this.screenStream = await navigator.mediaDevices.getDisplayMedia({
388
+ video: true,
389
+ audio: false
390
+ });
391
+ const [track] = this.screenStream.getVideoTracks();
392
+ track?.addEventListener("ended", () => {
393
+ void this.stopScreenShare();
394
+ });
395
+ return this.publish({ screenSharing: true });
396
+ }
397
+ async stopScreenShare() {
398
+ this.screenStream?.getTracks().forEach((t) => t.stop());
399
+ this.screenStream = null;
400
+ return this.publish({ screenSharing: false });
401
+ }
402
+ async toggleAudio() {
403
+ return this.setAudioEnabled(!this.state.audioEnabled);
404
+ }
405
+ async toggleVideo() {
406
+ return this.setVideoEnabled(!this.state.videoEnabled);
407
+ }
408
+ stopAll() {
409
+ this.micStream?.getTracks().forEach((t) => t.stop());
410
+ this.camStream?.getTracks().forEach((t) => t.stop());
411
+ this.screenStream?.getTracks().forEach((t) => t.stop());
412
+ this.micStream = null;
413
+ this.camStream = null;
414
+ this.screenStream = null;
415
+ this.publish({
416
+ audioEnabled: false,
417
+ videoEnabled: false,
418
+ screenSharing: false
419
+ });
420
+ }
421
+ publish(patch) {
422
+ this.state = { ...this.state, ...patch };
423
+ const snapshot = this.getMediaState();
424
+ for (const listener of this.listeners) listener(snapshot);
425
+ return snapshot;
426
+ }
427
+ ensureMediaDevices() {
428
+ if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
429
+ throw new SabhaError("Media devices are not available in this environment", {
430
+ code: "MEDIA_UNAVAILABLE"
431
+ });
432
+ }
433
+ }
434
+ };
435
+
436
+ // src/security/ContentProtection.ts
437
+ var ContentProtection = class {
438
+ constructor(policy) {
439
+ this.policy = policy;
440
+ this.enabled = false;
441
+ this.listeners = /* @__PURE__ */ new Set();
442
+ this.cleanups = [];
443
+ this.styleEl = null;
444
+ }
445
+ onCaptureAttempt(listener) {
446
+ this.listeners.add(listener);
447
+ return () => this.listeners.delete(listener);
448
+ }
449
+ setPolicy(policy) {
450
+ this.policy = policy;
451
+ if (this.enabled) {
452
+ this.disable();
453
+ this.enable();
454
+ }
455
+ }
456
+ enable() {
457
+ if (typeof document === "undefined" || this.enabled) return;
458
+ if (!this.policy.blockScreenshots && !this.policy.blockScreenRecording) return;
459
+ this.enabled = true;
460
+ this.injectStyles();
461
+ this.bindGuards();
462
+ }
463
+ disable() {
464
+ if (!this.enabled) return;
465
+ this.enabled = false;
466
+ for (const off of this.cleanups) off();
467
+ this.cleanups = [];
468
+ this.styleEl?.remove();
469
+ this.styleEl = null;
470
+ document.documentElement.classList.remove("sabha-content-protected");
471
+ }
472
+ injectStyles() {
473
+ const style = document.createElement("style");
474
+ style.setAttribute("data-sabha-content-protection", "true");
475
+ style.textContent = `
476
+ html.sabha-content-protected,
477
+ html.sabha-content-protected body {
478
+ -webkit-user-select: none !important;
479
+ user-select: none !important;
480
+ }
481
+ html.sabha-content-protected video,
482
+ html.sabha-content-protected canvas {
483
+ -webkit-user-select: none !important;
484
+ user-select: none !important;
485
+ pointer-events: auto;
486
+ }
487
+ html.sabha-content-protected.sabha-capture-obscured body::after {
488
+ content: "";
489
+ position: fixed;
490
+ inset: 0;
491
+ background: #000;
492
+ z-index: 2147483647;
493
+ pointer-events: none;
494
+ }
495
+ `;
496
+ document.head.appendChild(style);
497
+ document.documentElement.classList.add("sabha-content-protected");
498
+ this.styleEl = style;
499
+ }
500
+ bindGuards() {
501
+ const onContextMenu = (event) => {
502
+ if (!this.policy.blockScreenshots) return;
503
+ event.preventDefault();
504
+ };
505
+ const onKeyDown = (event) => {
506
+ if (!this.policy.blockScreenshots) return;
507
+ const key = event.key?.toLowerCase?.() ?? "";
508
+ const isPrintScreen = key === "printscreen";
509
+ const isMacShot = (event.metaKey || event.shiftKey) && (key === "3" || key === "4" || key === "5" || key === "6");
510
+ if (isPrintScreen || isMacShot) {
511
+ event.preventDefault();
512
+ this.obscureBriefly();
513
+ this.emit("screenshot");
514
+ }
515
+ };
516
+ const onVisibility = () => {
517
+ if (document.visibilityState === "hidden") {
518
+ if (this.policy.blockScreenRecording || this.policy.blockScreenshots) {
519
+ document.documentElement.classList.add("sabha-capture-obscured");
520
+ this.emit("visibility");
521
+ }
522
+ } else {
523
+ document.documentElement.classList.remove("sabha-capture-obscured");
524
+ }
525
+ };
526
+ const onBlur = () => {
527
+ if (this.policy.blockScreenshots) {
528
+ document.documentElement.classList.add("sabha-capture-obscured");
529
+ this.emit("screenshot");
530
+ }
531
+ };
532
+ const onFocus = () => {
533
+ document.documentElement.classList.remove("sabha-capture-obscured");
534
+ };
535
+ const originalGetDisplayMedia = navigator.mediaDevices?.getDisplayMedia?.bind(
536
+ navigator.mediaDevices
537
+ );
538
+ if (originalGetDisplayMedia && this.policy.blockScreenRecording) {
539
+ navigator.mediaDevices.getDisplayMedia = async (_constraints) => {
540
+ this.emit("recording");
541
+ this.obscureBriefly();
542
+ throw new DOMException(
543
+ "Screen recording is blocked by Sabha meeting policy",
544
+ "NotAllowedError"
545
+ );
546
+ };
547
+ this.cleanups.push(() => {
548
+ if (navigator.mediaDevices && originalGetDisplayMedia) {
549
+ navigator.mediaDevices.getDisplayMedia = originalGetDisplayMedia;
550
+ }
551
+ });
552
+ }
553
+ document.addEventListener("contextmenu", onContextMenu, true);
554
+ document.addEventListener("keydown", onKeyDown, true);
555
+ document.addEventListener("visibilitychange", onVisibility);
556
+ window.addEventListener("blur", onBlur);
557
+ window.addEventListener("focus", onFocus);
558
+ this.cleanups.push(
559
+ () => document.removeEventListener("contextmenu", onContextMenu, true),
560
+ () => document.removeEventListener("keydown", onKeyDown, true),
561
+ () => document.removeEventListener("visibilitychange", onVisibility),
562
+ () => window.removeEventListener("blur", onBlur),
563
+ () => window.removeEventListener("focus", onFocus),
564
+ () => document.documentElement.classList.remove("sabha-capture-obscured")
565
+ );
566
+ }
567
+ obscureBriefly() {
568
+ document.documentElement.classList.add("sabha-capture-obscured");
569
+ window.setTimeout(() => {
570
+ document.documentElement.classList.remove("sabha-capture-obscured");
571
+ }, 1500);
572
+ }
573
+ emit(type) {
574
+ const payload = { type, at: (/* @__PURE__ */ new Date()).toISOString() };
575
+ for (const listener of this.listeners) listener(payload);
576
+ }
577
+ };
578
+
579
+ // src/types.ts
580
+ var DEFAULT_BASE_URL = "https://sabha-api.sthanave.com";
581
+ var DEFAULT_MEDIA_POLICY = {
582
+ audio: true,
583
+ video: true,
584
+ allowScreenShare: true,
585
+ screenOff: false,
586
+ blockScreenshots: true,
587
+ blockScreenRecording: true
588
+ };
589
+
590
+ // src/realtime/MeetingConnection.ts
591
+ var DEFAULT_EVENTS = {
592
+ join: "meeting:join",
593
+ leave: "meeting:leave",
594
+ participantJoined: "meeting:participant-joined",
595
+ participantLeft: "meeting:participant-left",
596
+ meetingEnded: "meeting:ended",
597
+ error: "meeting:error",
598
+ mediaState: "meeting:media-state"
599
+ };
600
+ var MeetingConnection = class {
601
+ constructor(options) {
602
+ this.options = options;
603
+ this.socket = null;
604
+ this.state = "disconnected";
605
+ this.listeners = /* @__PURE__ */ new Map();
606
+ this.mediaUnsub = null;
607
+ this.captureUnsub = null;
608
+ this.events = { ...DEFAULT_EVENTS, ...options.events };
609
+ this.mediaPolicy = { ...DEFAULT_MEDIA_POLICY, ...options.mediaPolicy };
610
+ this.media = new MediaController(this.mediaPolicy);
611
+ this.protection = new ContentProtection(this.mediaPolicy);
612
+ }
613
+ get meetingId() {
614
+ return this.options.meetingId;
615
+ }
616
+ get connectionState() {
617
+ return this.state;
618
+ }
619
+ getMediaState() {
620
+ return this.media.getMediaState();
621
+ }
622
+ on(event, listener) {
623
+ const set = this.listeners.get(event) ?? /* @__PURE__ */ new Set();
624
+ set.add(listener);
625
+ this.listeners.set(event, set);
626
+ return () => {
627
+ set.delete(listener);
628
+ };
629
+ }
630
+ async connect() {
631
+ if (this.socket?.connected) {
632
+ await this.join();
633
+ return;
634
+ }
635
+ this.setState("connecting");
636
+ await new Promise((resolve, reject) => {
637
+ const auth = {};
638
+ if (this.options.accessToken) auth.token = this.options.accessToken;
639
+ if (this.options.sessionCookie) auth.session = this.options.sessionCookie;
640
+ const extraHeaders = {};
641
+ if (this.options.accessToken) {
642
+ extraHeaders.Authorization = `Bearer ${this.options.accessToken}`;
643
+ }
644
+ if (this.options.sessionCookie) {
645
+ extraHeaders.Cookie = `vm_session=${this.options.sessionCookie}`;
646
+ }
647
+ const socket = socket_ioClient.io(this.options.baseUrl, {
648
+ path: this.options.socketPath,
649
+ transports: ["websocket", "polling"],
650
+ withCredentials: this.options.withCredentials !== false,
651
+ auth,
652
+ extraHeaders,
653
+ autoConnect: true
654
+ });
655
+ this.socket = socket;
656
+ const onConnect = () => {
657
+ cleanup();
658
+ this.setState("connected");
659
+ this.bindServerEvents(socket);
660
+ this.join().then(async () => {
661
+ this.protection.enable();
662
+ this.mediaUnsub = this.media.onChange((mediaState) => {
663
+ this.emit("mediaState", mediaState);
664
+ socket.emit(this.events.mediaState, {
665
+ meetingId: this.options.meetingId,
666
+ ...mediaState
667
+ });
668
+ });
669
+ this.captureUnsub = this.protection.onCaptureAttempt((event) => {
670
+ this.emit("captureAttempt", event);
671
+ });
672
+ await this.media.start(this.mediaPolicy);
673
+ resolve();
674
+ }).catch(reject);
675
+ };
676
+ const onError = (error) => {
677
+ cleanup();
678
+ this.setState("error");
679
+ this.emit("error", error);
680
+ reject(
681
+ new SabhaError(error.message || "Failed to connect to Sabha meetings", {
682
+ code: "SOCKET_CONNECT_FAILED",
683
+ cause: error
684
+ })
685
+ );
686
+ };
687
+ const cleanup = () => {
688
+ socket.off("connect", onConnect);
689
+ socket.off("connect_error", onError);
690
+ };
691
+ socket.on("connect", onConnect);
692
+ socket.on("connect_error", onError);
693
+ });
694
+ }
695
+ async setAudioEnabled(enabled) {
696
+ return this.media.setAudioEnabled(enabled);
697
+ }
698
+ async setVideoEnabled(enabled) {
699
+ return this.media.setVideoEnabled(enabled);
700
+ }
701
+ async setScreenOff(off) {
702
+ return this.media.setScreenOff(off);
703
+ }
704
+ async startScreenShare() {
705
+ return this.media.startScreenShare();
706
+ }
707
+ async stopScreenShare() {
708
+ return this.media.stopScreenShare();
709
+ }
710
+ async leave() {
711
+ const socket = this.socket;
712
+ this.teardownMedia();
713
+ if (!socket) return;
714
+ await new Promise((resolve) => {
715
+ socket.timeout(5e3).emit(
716
+ this.events.leave,
717
+ { meetingId: this.options.meetingId },
718
+ () => resolve()
719
+ );
720
+ setTimeout(() => resolve(), 500);
721
+ });
722
+ socket.disconnect();
723
+ this.socket = null;
724
+ this.setState("disconnected");
725
+ }
726
+ disconnect() {
727
+ this.teardownMedia();
728
+ this.socket?.disconnect();
729
+ this.socket = null;
730
+ this.setState("disconnected");
731
+ }
732
+ emitRaw(event, payload) {
733
+ if (!this.socket?.connected) {
734
+ throw new SabhaError("Meeting socket is not connected", {
735
+ code: "SOCKET_NOT_CONNECTED"
736
+ });
737
+ }
738
+ this.socket.emit(event, payload);
739
+ }
740
+ teardownMedia() {
741
+ this.mediaUnsub?.();
742
+ this.captureUnsub?.();
743
+ this.mediaUnsub = null;
744
+ this.captureUnsub = null;
745
+ this.media.stopAll();
746
+ this.protection.disable();
747
+ }
748
+ async join() {
749
+ const socket = this.socket;
750
+ if (!socket?.connected) {
751
+ throw new SabhaError("Meeting socket is not connected", {
752
+ code: "SOCKET_NOT_CONNECTED"
753
+ });
754
+ }
755
+ await new Promise((resolve) => {
756
+ let settled = false;
757
+ const done = () => {
758
+ if (settled) return;
759
+ settled = true;
760
+ this.setState("joined");
761
+ resolve();
762
+ };
763
+ socket.timeout(5e3).emit(
764
+ this.events.join,
765
+ { meetingId: this.options.meetingId },
766
+ () => done()
767
+ );
768
+ setTimeout(done, 600);
769
+ });
770
+ }
771
+ bindServerEvents(socket) {
772
+ socket.on(this.events.participantJoined, (payload) => {
773
+ this.emit("participantJoined", payload);
774
+ this.emit("raw", { event: this.events.participantJoined, payload });
775
+ });
776
+ socket.on(this.events.participantLeft, (payload) => {
777
+ this.emit("participantLeft", payload);
778
+ this.emit("raw", { event: this.events.participantLeft, payload });
779
+ });
780
+ socket.on(
781
+ this.events.meetingEnded,
782
+ (payload) => {
783
+ this.emit("meetingEnded", {
784
+ meetingId: payload.meetingId ?? this.options.meetingId,
785
+ reason: payload.reason
786
+ });
787
+ this.emit("raw", { event: this.events.meetingEnded, payload });
788
+ this.setState("disconnected");
789
+ }
790
+ );
791
+ socket.on(this.events.error, (payload) => {
792
+ const message = payload && typeof payload === "object" && "message" in payload ? String(payload.message) : "Meeting socket error";
793
+ const error = new SabhaError(message, {
794
+ details: payload,
795
+ code: "MEETING_SOCKET_ERROR"
796
+ });
797
+ this.emit("error", error);
798
+ this.emit("raw", { event: this.events.error, payload });
799
+ });
800
+ socket.on("disconnect", () => {
801
+ this.setState("disconnected");
802
+ });
803
+ }
804
+ setState(state) {
805
+ this.state = state;
806
+ this.emit("state", state);
807
+ }
808
+ emit(event, payload) {
809
+ const set = this.listeners.get(event);
810
+ if (!set) return;
811
+ for (const listener of set) listener(payload);
812
+ }
813
+ };
814
+
815
+ // src/SabhaClient.ts
816
+ var SabhaClient = class {
817
+ constructor() {
818
+ this.config = { baseUrl: DEFAULT_BASE_URL };
819
+ this.tokens = null;
820
+ this.user = null;
821
+ this.http = null;
822
+ this.authApi = null;
823
+ this.meetingsApi = null;
824
+ this.activeConnection = null;
825
+ this.refreshPromise = null;
826
+ this.mediaPolicy = { ...DEFAULT_MEDIA_POLICY };
827
+ }
828
+ configure(config = {}) {
829
+ const baseUrl = (config.baseUrl ?? this.config.baseUrl ?? DEFAULT_BASE_URL).replace(
830
+ /\/+$/,
831
+ ""
832
+ );
833
+ this.config = {
834
+ withCredentials: true,
835
+ ...this.config,
836
+ ...config,
837
+ baseUrl
838
+ };
839
+ this.mediaPolicy = {
840
+ ...DEFAULT_MEDIA_POLICY,
841
+ ...this.config.mediaPolicy
842
+ };
843
+ this.tokens = {
844
+ accessToken: config.accessToken ?? this.tokens?.accessToken,
845
+ refreshToken: config.refreshToken ?? this.tokens?.refreshToken,
846
+ sessionCookie: config.sessionCookie ?? this.tokens?.sessionCookie
847
+ };
848
+ if (!this.tokens.accessToken && !this.tokens.sessionCookie && !this.tokens.refreshToken) {
849
+ if (!config.accessToken && !config.sessionCookie && !config.refreshToken) ;
850
+ }
851
+ const hasAnyToken = Boolean(
852
+ this.tokens.accessToken || this.tokens.sessionCookie || this.tokens.refreshToken
853
+ );
854
+ if (!hasAnyToken) this.tokens = null;
855
+ this.http = new HttpClient({
856
+ baseUrl,
857
+ withCredentials: this.config.withCredentials !== false,
858
+ getOrigin: () => this.getOrigin(),
859
+ getTokens: () => this.tokens,
860
+ setTokens: (tokens) => {
861
+ this.tokens = tokens;
862
+ },
863
+ onSessionCookie: (cookie) => {
864
+ this.tokens = {
865
+ ...this.tokens ?? {},
866
+ sessionCookie: cookie ?? void 0
867
+ };
868
+ },
869
+ onUnauthorized: () => this.tryRefresh()
870
+ });
871
+ this.authApi = new AuthApi(this.http, () => this.tokens?.sessionCookie);
872
+ this.meetingsApi = new MeetingsApi(this.http);
873
+ return this;
874
+ }
875
+ getStatus() {
876
+ const configured = Boolean(this.http && this.config.baseUrl);
877
+ const authenticated = Boolean(
878
+ this.tokens?.accessToken || this.tokens?.sessionCookie || this.config.withCredentials !== false && this.user
879
+ );
880
+ return {
881
+ configured,
882
+ authenticated: authenticated || Boolean(this.user),
883
+ baseUrl: this.config.baseUrl ?? DEFAULT_BASE_URL
884
+ };
885
+ }
886
+ getAccessToken() {
887
+ return this.tokens?.accessToken ?? null;
888
+ }
889
+ getSessionCookie() {
890
+ return this.tokens?.sessionCookie ?? null;
891
+ }
892
+ getUser() {
893
+ return this.user;
894
+ }
895
+ getMediaPolicy() {
896
+ return { ...this.mediaPolicy };
897
+ }
898
+ setMediaPolicy(policy) {
899
+ this.mediaPolicy = { ...this.mediaPolicy, ...policy };
900
+ this.config.mediaPolicy = this.mediaPolicy;
901
+ }
902
+ setTokens(tokens) {
903
+ this.ensureConfigured();
904
+ this.tokens = tokens;
905
+ }
906
+ clearSession() {
907
+ this.tokens = null;
908
+ this.user = null;
909
+ this.activeConnection?.disconnect();
910
+ this.activeConnection = null;
911
+ }
912
+ async login(input) {
913
+ const session = await this.auth().login(input);
914
+ this.applySession(session);
915
+ try {
916
+ const me = await this.auth().me();
917
+ this.user = me;
918
+ session.user = me;
919
+ } catch (error) {
920
+ this.clearSession();
921
+ throw new SabhaError(
922
+ "Login returned a user profile but no Sabha session was established. The API did not set `vm_session` (and no JWT token was returned), so POST /api/meetings will return 401 Sign in first. Fix session cookies on sabha-api (Set-Cookie: vm_session; SameSite=None; Secure for cross-origin) or return a bearer token from /api/auth/login.",
923
+ {
924
+ code: "SESSION_NOT_ESTABLISHED",
925
+ status: 401,
926
+ cause: error
927
+ }
928
+ );
929
+ }
930
+ return session;
931
+ }
932
+ /** Throws if /api/auth/me fails — call before create/start meeting. */
933
+ async ensureAuthenticated() {
934
+ try {
935
+ return await this.me();
936
+ } catch (error) {
937
+ throw new SabhaError(
938
+ "Not signed in to Sabha. Call login() first, and ensure the `vm_session` cookie is sent (credentials: 'include', CORS allow-credentials, allow-listed Origin).",
939
+ {
940
+ code: "UNAUTHENTICATED",
941
+ status: 401,
942
+ cause: error
943
+ }
944
+ );
945
+ }
946
+ }
947
+ async register(input) {
948
+ return this.auth().register(input);
949
+ }
950
+ async verifyEmail(input) {
951
+ const session = await this.auth().verifyEmail(input);
952
+ this.applySession(session);
953
+ return session;
954
+ }
955
+ async refreshSession() {
956
+ const refreshToken = this.tokens?.refreshToken;
957
+ if (!refreshToken) {
958
+ throw new SabhaError("No refresh token available", { code: "NO_REFRESH_TOKEN" });
959
+ }
960
+ const tokens = await this.auth().refresh(refreshToken);
961
+ this.tokens = { ...this.tokens ?? {}, ...tokens };
962
+ return tokens;
963
+ }
964
+ async me() {
965
+ const user = await this.auth().me();
966
+ this.user = user;
967
+ return user;
968
+ }
969
+ async logout() {
970
+ try {
971
+ if (this.user || this.tokens) {
972
+ await this.auth().logout(this.tokens?.refreshToken);
973
+ }
974
+ } finally {
975
+ this.clearSession();
976
+ }
977
+ }
978
+ async listMeetings() {
979
+ return this.meetings().list();
980
+ }
981
+ async createMeeting(input) {
982
+ await this.ensureAuthenticated();
983
+ return this.meetings().create(input);
984
+ }
985
+ async updateMeeting(meetingId, input) {
986
+ return this.meetings().update(meetingId, input);
987
+ }
988
+ async cancelMeeting(meetingId) {
989
+ await this.meetings().cancel(meetingId);
990
+ if (this.activeConnection?.meetingId === meetingId) {
991
+ this.activeConnection.disconnect();
992
+ this.activeConnection = null;
993
+ }
994
+ }
995
+ async startMeeting(input) {
996
+ await this.ensureAuthenticated();
997
+ let meeting;
998
+ if (input?.meetingId) {
999
+ const existing = (await this.listMeetings()).find((m) => m.id === input.meetingId);
1000
+ meeting = existing ?? {
1001
+ id: input.meetingId,
1002
+ title: input.title ?? "Meeting",
1003
+ startAt: (/* @__PURE__ */ new Date()).toISOString(),
1004
+ durationMin: input.durationMin ?? 30
1005
+ };
1006
+ } else {
1007
+ meeting = await this.meetings().create({
1008
+ title: input?.title ?? "Sabha Meeting",
1009
+ startAt: input?.startAt,
1010
+ durationMin: input?.durationMin,
1011
+ timeZone: input?.timeZone,
1012
+ inviteeIds: input?.inviteeIds,
1013
+ notes: input?.notes,
1014
+ groupId: input?.groupId
1015
+ });
1016
+ }
1017
+ const connection = await this.connect(meeting.id);
1018
+ return { meeting, connection };
1019
+ }
1020
+ async connect(meetingId) {
1021
+ this.ensureConfigured();
1022
+ if (this.activeConnection) {
1023
+ this.activeConnection.disconnect();
1024
+ this.activeConnection = null;
1025
+ }
1026
+ const connection = new MeetingConnection({
1027
+ baseUrl: this.config.baseUrl ?? DEFAULT_BASE_URL,
1028
+ meetingId,
1029
+ accessToken: this.tokens?.accessToken,
1030
+ sessionCookie: this.tokens?.sessionCookie,
1031
+ withCredentials: this.config.withCredentials !== false,
1032
+ socketPath: this.config.socketPath,
1033
+ events: this.config.socketEvents,
1034
+ mediaPolicy: this.mediaPolicy
1035
+ });
1036
+ try {
1037
+ await connection.connect();
1038
+ this.activeConnection = connection;
1039
+ return connection;
1040
+ } catch (error) {
1041
+ connection.disconnect();
1042
+ throw toSabhaError(error, "Failed to connect to Sabha meeting");
1043
+ }
1044
+ }
1045
+ getConnection() {
1046
+ return this.activeConnection;
1047
+ }
1048
+ async leaveMeeting() {
1049
+ if (!this.activeConnection) return;
1050
+ await this.activeConnection.leave();
1051
+ this.activeConnection = null;
1052
+ }
1053
+ applySession(session) {
1054
+ this.user = session.user;
1055
+ this.tokens = {
1056
+ accessToken: session.accessToken ?? this.tokens?.accessToken,
1057
+ refreshToken: session.refreshToken ?? this.tokens?.refreshToken,
1058
+ sessionCookie: session.sessionCookie ?? this.tokens?.sessionCookie
1059
+ };
1060
+ }
1061
+ async tryRefresh() {
1062
+ if (!this.tokens?.refreshToken) return false;
1063
+ if (this.refreshPromise) return this.refreshPromise;
1064
+ this.refreshPromise = (async () => {
1065
+ try {
1066
+ await this.refreshSession();
1067
+ return true;
1068
+ } catch {
1069
+ return false;
1070
+ } finally {
1071
+ this.refreshPromise = null;
1072
+ }
1073
+ })();
1074
+ return this.refreshPromise;
1075
+ }
1076
+ getOrigin() {
1077
+ if (this.config.origin) return this.config.origin;
1078
+ const win = globalThis.window;
1079
+ if (win?.location?.origin) return win.location.origin;
1080
+ return void 0;
1081
+ }
1082
+ ensureConfigured() {
1083
+ if (!this.http || !this.authApi || !this.meetingsApi) {
1084
+ this.configure(this.config);
1085
+ }
1086
+ if (!this.http || !this.authApi || !this.meetingsApi) {
1087
+ throw new SabhaError("Sabha client failed to configure", {
1088
+ code: "NOT_CONFIGURED"
1089
+ });
1090
+ }
1091
+ }
1092
+ auth() {
1093
+ this.ensureConfigured();
1094
+ return this.authApi;
1095
+ }
1096
+ meetings() {
1097
+ this.ensureConfigured();
1098
+ return this.meetingsApi;
1099
+ }
1100
+ };
1101
+ function createSabhaClient(config) {
1102
+ const client = new SabhaClient();
1103
+ client.configure(config ?? { baseUrl: DEFAULT_BASE_URL });
1104
+ return client;
1105
+ }
1106
+ var SabhaContext = react.createContext(null);
1107
+ var idleMedia = {
1108
+ audioEnabled: false,
1109
+ videoEnabled: false,
1110
+ screenSharing: false,
1111
+ screenOff: false
1112
+ };
1113
+ function SabhaProvider({
1114
+ children,
1115
+ client: clientProp,
1116
+ autoLoadMeetings = true,
1117
+ ...config
1118
+ }) {
1119
+ const resolvedConfig = {
1120
+ baseUrl: DEFAULT_BASE_URL,
1121
+ withCredentials: true,
1122
+ ...config
1123
+ };
1124
+ const client = react.useMemo(
1125
+ () => clientProp ?? createSabhaClient(resolvedConfig),
1126
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1127
+ [
1128
+ clientProp,
1129
+ resolvedConfig.baseUrl,
1130
+ resolvedConfig.accessToken,
1131
+ resolvedConfig.sessionCookie,
1132
+ resolvedConfig.origin
1133
+ ]
1134
+ );
1135
+ const [ready, setReady] = react.useState(false);
1136
+ const [authenticated, setAuthenticated] = react.useState(false);
1137
+ const [user, setUser] = react.useState(null);
1138
+ const [meetings, setMeetings] = react.useState([]);
1139
+ const [meetingsLoading, setMeetingsLoading] = react.useState(false);
1140
+ const [connection, setConnection] = react.useState(null);
1141
+ const [connectionState, setConnectionState] = react.useState("disconnected");
1142
+ const [mediaState, setMediaState] = react.useState(idleMedia);
1143
+ const [error, setError] = react.useState(null);
1144
+ react.useEffect(() => {
1145
+ client.configure(resolvedConfig);
1146
+ setAuthenticated(client.getStatus().authenticated);
1147
+ setUser(client.getUser());
1148
+ setReady(true);
1149
+ }, [
1150
+ client,
1151
+ resolvedConfig.baseUrl,
1152
+ resolvedConfig.accessToken,
1153
+ resolvedConfig.sessionCookie,
1154
+ resolvedConfig.origin,
1155
+ resolvedConfig.withCredentials
1156
+ ]);
1157
+ react.useEffect(() => {
1158
+ if (!connection) {
1159
+ setConnectionState("disconnected");
1160
+ setMediaState(idleMedia);
1161
+ return;
1162
+ }
1163
+ setConnectionState(connection.connectionState);
1164
+ setMediaState(connection.getMediaState());
1165
+ const offState = connection.on("state", setConnectionState);
1166
+ const offMedia = connection.on("mediaState", setMediaState);
1167
+ return () => {
1168
+ offState();
1169
+ offMedia();
1170
+ };
1171
+ }, [connection]);
1172
+ const refreshMeetings = react.useCallback(async () => {
1173
+ setMeetingsLoading(true);
1174
+ setError(null);
1175
+ try {
1176
+ const list = await client.listMeetings();
1177
+ setMeetings(list);
1178
+ setAuthenticated(true);
1179
+ return list;
1180
+ } catch (err) {
1181
+ const next = err instanceof Error ? err : new Error("Failed to load meetings");
1182
+ setError(next);
1183
+ throw next;
1184
+ } finally {
1185
+ setMeetingsLoading(false);
1186
+ }
1187
+ }, [client]);
1188
+ react.useEffect(() => {
1189
+ if (!ready || !autoLoadMeetings) return;
1190
+ void client.me().then((me) => {
1191
+ setUser(me);
1192
+ setAuthenticated(true);
1193
+ return refreshMeetings();
1194
+ }).catch(() => {
1195
+ });
1196
+ }, [ready, autoLoadMeetings, client, refreshMeetings]);
1197
+ const createMeeting = react.useCallback(
1198
+ async (input) => {
1199
+ setError(null);
1200
+ try {
1201
+ const meeting = await client.createMeeting(input);
1202
+ setMeetings((prev) => [meeting, ...prev.filter((m) => m.id !== meeting.id)]);
1203
+ return meeting;
1204
+ } catch (err) {
1205
+ const next = err instanceof Error ? err : new Error("Failed to create meeting");
1206
+ setError(next);
1207
+ throw next;
1208
+ }
1209
+ },
1210
+ [client]
1211
+ );
1212
+ const updateMeeting = react.useCallback(
1213
+ async (meetingId, input) => {
1214
+ setError(null);
1215
+ try {
1216
+ const meeting = await client.updateMeeting(meetingId, input);
1217
+ setMeetings((prev) => prev.map((m) => m.id === meeting.id ? meeting : m));
1218
+ return meeting;
1219
+ } catch (err) {
1220
+ const next = err instanceof Error ? err : new Error("Failed to update meeting");
1221
+ setError(next);
1222
+ throw next;
1223
+ }
1224
+ },
1225
+ [client]
1226
+ );
1227
+ const cancelMeeting = react.useCallback(
1228
+ async (meetingId) => {
1229
+ setError(null);
1230
+ try {
1231
+ await client.cancelMeeting(meetingId);
1232
+ setMeetings((prev) => prev.filter((m) => m.id !== meetingId));
1233
+ if (connection?.meetingId === meetingId) {
1234
+ setConnection(null);
1235
+ }
1236
+ } catch (err) {
1237
+ const next = err instanceof Error ? err : new Error("Failed to cancel meeting");
1238
+ setError(next);
1239
+ throw next;
1240
+ }
1241
+ },
1242
+ [client, connection]
1243
+ );
1244
+ const connect = react.useCallback(
1245
+ async (meetingId) => {
1246
+ setError(null);
1247
+ try {
1248
+ const next = await client.connect(meetingId);
1249
+ setConnection(next);
1250
+ return next;
1251
+ } catch (err) {
1252
+ const nextErr = err instanceof Error ? err : new Error("Failed to connect");
1253
+ setError(nextErr);
1254
+ throw nextErr;
1255
+ }
1256
+ },
1257
+ [client]
1258
+ );
1259
+ const startMeeting = react.useCallback(
1260
+ async (input) => {
1261
+ setError(null);
1262
+ try {
1263
+ const result = await client.startMeeting(input);
1264
+ setMeetings((prev) => [
1265
+ result.meeting,
1266
+ ...prev.filter((m) => m.id !== result.meeting.id)
1267
+ ]);
1268
+ setConnection(result.connection);
1269
+ return result;
1270
+ } catch (err) {
1271
+ const next = err instanceof Error ? err : new Error("Failed to start meeting");
1272
+ setError(next);
1273
+ throw next;
1274
+ }
1275
+ },
1276
+ [client]
1277
+ );
1278
+ const leaveMeeting = react.useCallback(async () => {
1279
+ setError(null);
1280
+ try {
1281
+ await client.leaveMeeting();
1282
+ } finally {
1283
+ setConnection(null);
1284
+ setConnectionState("disconnected");
1285
+ setMediaState(idleMedia);
1286
+ }
1287
+ }, [client]);
1288
+ const login = react.useCallback(
1289
+ async (input) => {
1290
+ setError(null);
1291
+ try {
1292
+ const session = await client.login(input);
1293
+ setAuthenticated(true);
1294
+ setUser(session.user);
1295
+ if (autoLoadMeetings) await refreshMeetings().catch(() => void 0);
1296
+ return session;
1297
+ } catch (err) {
1298
+ const next = err instanceof Error ? err : new Error("Login failed");
1299
+ setError(next);
1300
+ throw next;
1301
+ }
1302
+ },
1303
+ [autoLoadMeetings, client, refreshMeetings]
1304
+ );
1305
+ const logout = react.useCallback(async () => {
1306
+ setError(null);
1307
+ try {
1308
+ await client.logout();
1309
+ } finally {
1310
+ setAuthenticated(false);
1311
+ setUser(null);
1312
+ setMeetings([]);
1313
+ setConnection(null);
1314
+ setConnectionState("disconnected");
1315
+ setMediaState(idleMedia);
1316
+ }
1317
+ }, [client]);
1318
+ const setAudioEnabled = react.useCallback(
1319
+ async (enabled) => {
1320
+ if (!connection) throw new Error("Not connected to a meeting");
1321
+ return connection.setAudioEnabled(enabled);
1322
+ },
1323
+ [connection]
1324
+ );
1325
+ const setVideoEnabled = react.useCallback(
1326
+ async (enabled) => {
1327
+ if (!connection) throw new Error("Not connected to a meeting");
1328
+ return connection.setVideoEnabled(enabled);
1329
+ },
1330
+ [connection]
1331
+ );
1332
+ const setScreenOff = react.useCallback(
1333
+ async (off) => {
1334
+ if (!connection) throw new Error("Not connected to a meeting");
1335
+ return connection.setScreenOff(off);
1336
+ },
1337
+ [connection]
1338
+ );
1339
+ const startScreenShare = react.useCallback(async () => {
1340
+ if (!connection) throw new Error("Not connected to a meeting");
1341
+ return connection.startScreenShare();
1342
+ }, [connection]);
1343
+ const stopScreenShare = react.useCallback(async () => {
1344
+ if (!connection) throw new Error("Not connected to a meeting");
1345
+ return connection.stopScreenShare();
1346
+ }, [connection]);
1347
+ const value = react.useMemo(
1348
+ () => ({
1349
+ client,
1350
+ ready,
1351
+ authenticated,
1352
+ user,
1353
+ meetings,
1354
+ meetingsLoading,
1355
+ connection,
1356
+ connectionState,
1357
+ mediaState,
1358
+ error,
1359
+ refreshMeetings,
1360
+ createMeeting,
1361
+ updateMeeting,
1362
+ cancelMeeting,
1363
+ startMeeting,
1364
+ connect,
1365
+ leaveMeeting,
1366
+ login,
1367
+ logout,
1368
+ setAudioEnabled,
1369
+ setVideoEnabled,
1370
+ setScreenOff,
1371
+ startScreenShare,
1372
+ stopScreenShare,
1373
+ clearError: () => setError(null)
1374
+ }),
1375
+ [
1376
+ client,
1377
+ ready,
1378
+ authenticated,
1379
+ user,
1380
+ meetings,
1381
+ meetingsLoading,
1382
+ connection,
1383
+ connectionState,
1384
+ mediaState,
1385
+ error,
1386
+ refreshMeetings,
1387
+ createMeeting,
1388
+ updateMeeting,
1389
+ cancelMeeting,
1390
+ startMeeting,
1391
+ connect,
1392
+ leaveMeeting,
1393
+ login,
1394
+ logout,
1395
+ setAudioEnabled,
1396
+ setVideoEnabled,
1397
+ setScreenOff,
1398
+ startScreenShare,
1399
+ stopScreenShare
1400
+ ]
1401
+ );
1402
+ return /* @__PURE__ */ jsxRuntime.jsx(SabhaContext.Provider, { value, children });
1403
+ }
1404
+ function useSabha() {
1405
+ const ctx = react.useContext(SabhaContext);
1406
+ if (!ctx) {
1407
+ throw new Error("useSabha must be used within <SabhaProvider>");
1408
+ }
1409
+ return ctx;
1410
+ }
1411
+ function useSabhaMeetings() {
1412
+ const {
1413
+ meetings,
1414
+ meetingsLoading,
1415
+ refreshMeetings,
1416
+ createMeeting,
1417
+ updateMeeting,
1418
+ cancelMeeting,
1419
+ startMeeting,
1420
+ connect,
1421
+ leaveMeeting,
1422
+ connection,
1423
+ connectionState,
1424
+ mediaState,
1425
+ setAudioEnabled,
1426
+ setVideoEnabled,
1427
+ setScreenOff,
1428
+ startScreenShare,
1429
+ stopScreenShare,
1430
+ error
1431
+ } = useSabha();
1432
+ return {
1433
+ meetings,
1434
+ meetingsLoading,
1435
+ refreshMeetings,
1436
+ createMeeting,
1437
+ updateMeeting,
1438
+ cancelMeeting,
1439
+ startMeeting,
1440
+ connect,
1441
+ leaveMeeting,
1442
+ connection,
1443
+ connectionState,
1444
+ mediaState,
1445
+ setAudioEnabled,
1446
+ setVideoEnabled,
1447
+ setScreenOff,
1448
+ startScreenShare,
1449
+ stopScreenShare,
1450
+ error
1451
+ };
1452
+ }
1453
+
1454
+ exports.ContentProtection = ContentProtection;
1455
+ exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
1456
+ exports.DEFAULT_MEDIA_POLICY = DEFAULT_MEDIA_POLICY;
1457
+ exports.MediaController = MediaController;
1458
+ exports.MeetingConnection = MeetingConnection;
1459
+ exports.SabhaClient = SabhaClient;
1460
+ exports.SabhaError = SabhaError;
1461
+ exports.SabhaProvider = SabhaProvider;
1462
+ exports.createSabhaClient = createSabhaClient;
1463
+ exports.normalizeMeeting = normalizeMeeting;
1464
+ exports.toSabhaError = toSabhaError;
1465
+ exports.useSabha = useSabha;
1466
+ exports.useSabhaMeetings = useSabhaMeetings;
3
1467
  //# sourceMappingURL=index.js.map
4
1468
  //# sourceMappingURL=index.js.map