farvex 0.2.0 → 1.0.1

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,114 +1,138 @@
1
- import { io } from 'socket.io-client';
2
- import { Room, RoomEvent, ConnectionState, Track, ConnectionQuality } from 'livekit-client';
1
+ import { Centrifuge, UnauthorizedError } from 'centrifuge';
3
2
 
4
- // src/shared/types.ts
5
- function nullify(value) {
6
- if (value === void 0 || value === null) {
7
- return null;
8
- }
9
- if (typeof value === "string" && value.length === 0) {
10
- return null;
11
- }
12
- return value;
13
- }
3
+ // src/api/generated/core/bodySerializer.gen.ts
4
+ var jsonBodySerializer = {
5
+ bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value)
6
+ };
14
7
 
15
- // src/shared/error.ts
16
- function make(kind, retryable, code, title, overrides = {}) {
17
- return {
18
- kind,
19
- code,
20
- title,
21
- retryable,
22
- ...overrides
23
- };
24
- }
25
- var callpadError = {
26
- validation: (code, title, ov) => make("validation", false, code, title, ov),
27
- unauth: (code, title, ov) => make("unauth", false, code, title, ov),
28
- forbidden: (code, title, ov) => make("forbidden", false, code, title, ov),
29
- notfound: (code, title, ov) => make("notfound", false, code, title, ov),
30
- precondition: (code, title, ov) => make("precondition", false, code, title, ov),
31
- conflict: (code, title, ov) => make("conflict", false, code, title, ov),
32
- internal: (code, title, ov) => make("internal", true, code, title, ov),
33
- unavailable: (code, title, ov) => make("unavailable", true, code, title, ov),
34
- timeout: (code, title, ov) => make("timeout", true, code, title, ov),
35
- network: (code, title, ov) => make("network", true, code, title, ov),
36
- rateLimited: (code, title, ov) => make("rate_limited", true, code, title, ov),
37
- mediaDevice: (code, title, ov) => make("media_device", false, code, title, ov),
38
- mediaUnavailable: (code, title, ov) => make("media_unavailable", true, code, title, ov),
39
- disposed: () => make("disposed", false, "callpad.disposed", "Client has been disposed"),
40
- fromUnknown: (cause, context) => {
41
- if (cause instanceof Error) {
42
- return make(
43
- "internal",
44
- false,
45
- "callpad.unknown",
46
- context ?? "Unknown error",
47
- {
48
- detail: cause.message,
49
- cause
8
+ // src/api/generated/core/serverSentEvents.gen.ts
9
+ function createSseClient({
10
+ onRequest,
11
+ onSseError,
12
+ onSseEvent,
13
+ responseTransformer,
14
+ responseValidator,
15
+ sseDefaultRetryDelay,
16
+ sseMaxRetryAttempts,
17
+ sseMaxRetryDelay,
18
+ sseSleepFn,
19
+ url,
20
+ ...options
21
+ }) {
22
+ let lastEventId;
23
+ const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
24
+ const createStream = async function* () {
25
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
26
+ let attempt = 0;
27
+ const signal = options.signal ?? new AbortController().signal;
28
+ while (true) {
29
+ if (signal.aborted) break;
30
+ attempt++;
31
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
32
+ if (lastEventId !== void 0) {
33
+ headers.set("Last-Event-ID", lastEventId);
34
+ }
35
+ try {
36
+ const requestInit = {
37
+ redirect: "follow",
38
+ ...options,
39
+ body: options.serializedBody,
40
+ headers,
41
+ signal
42
+ };
43
+ let request = new Request(url, requestInit);
44
+ if (onRequest) {
45
+ request = await onRequest(url, requestInit);
50
46
  }
51
- );
52
- }
53
- return make(
54
- "internal",
55
- false,
56
- "callpad.unknown",
57
- context ?? "Unknown error",
58
- {
59
- detail: typeof cause === "string" ? cause : void 0
47
+ const _fetch = options.fetch ?? globalThis.fetch;
48
+ const response = await _fetch(request);
49
+ if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
50
+ if (!response.body) throw new Error("No body in SSE response");
51
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
52
+ let buffer = "";
53
+ const abortHandler = () => {
54
+ try {
55
+ reader.cancel();
56
+ } catch {
57
+ }
58
+ };
59
+ signal.addEventListener("abort", abortHandler);
60
+ try {
61
+ while (true) {
62
+ const { done, value } = await reader.read();
63
+ if (done) break;
64
+ buffer += value;
65
+ buffer = buffer.replace(/\r\n?/g, "\n");
66
+ const chunks = buffer.split("\n\n");
67
+ buffer = chunks.pop() ?? "";
68
+ for (const chunk of chunks) {
69
+ const lines = chunk.split("\n");
70
+ const dataLines = [];
71
+ let eventName;
72
+ for (const line of lines) {
73
+ if (line.startsWith("data:")) {
74
+ dataLines.push(line.replace(/^data:\s*/, ""));
75
+ } else if (line.startsWith("event:")) {
76
+ eventName = line.replace(/^event:\s*/, "");
77
+ } else if (line.startsWith("id:")) {
78
+ lastEventId = line.replace(/^id:\s*/, "");
79
+ } else if (line.startsWith("retry:")) {
80
+ const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
81
+ if (!Number.isNaN(parsed)) {
82
+ retryDelay = parsed;
83
+ }
84
+ }
85
+ }
86
+ let data;
87
+ let parsedJson = false;
88
+ if (dataLines.length) {
89
+ const rawData = dataLines.join("\n");
90
+ try {
91
+ data = JSON.parse(rawData);
92
+ parsedJson = true;
93
+ } catch {
94
+ data = rawData;
95
+ }
96
+ }
97
+ if (parsedJson) {
98
+ if (responseValidator) {
99
+ await responseValidator(data);
100
+ }
101
+ if (responseTransformer) {
102
+ data = await responseTransformer(data);
103
+ }
104
+ }
105
+ onSseEvent?.({
106
+ data,
107
+ event: eventName,
108
+ id: lastEventId,
109
+ retry: retryDelay
110
+ });
111
+ if (dataLines.length) {
112
+ yield data;
113
+ }
114
+ }
115
+ }
116
+ } finally {
117
+ signal.removeEventListener("abort", abortHandler);
118
+ reader.releaseLock();
119
+ }
120
+ break;
121
+ } catch (error) {
122
+ onSseError?.(error);
123
+ if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) {
124
+ break;
125
+ }
126
+ const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4);
127
+ await sleep(backoff);
60
128
  }
61
- );
62
- }
63
- };
64
-
65
- // src/shared/result.ts
66
- function ok(value) {
67
- return { ok: true, value };
68
- }
69
- function err(error) {
70
- return { ok: false, error };
71
- }
72
- function isOk(r) {
73
- return r.ok;
74
- }
75
- function isErr(r) {
76
- return !r.ok;
129
+ }
130
+ };
131
+ const stream = createStream();
132
+ return { stream };
77
133
  }
78
134
 
79
- // src/core/logger.ts
80
- var noopLogger = {
81
- debug: () => void 0,
82
- info: () => void 0,
83
- warn: () => void 0,
84
- error: () => void 0,
85
- child: () => noopLogger
86
- };
87
-
88
- // src/api/generated/core/bodySerializer.ts
89
- var jsonBodySerializer = {
90
- bodySerializer: (body) => JSON.stringify(
91
- body,
92
- (key, value) => typeof value === "bigint" ? value.toString() : value
93
- )
94
- };
95
-
96
- // src/api/generated/core/auth.ts
97
- var getAuthToken = async (auth, callback) => {
98
- const token = typeof callback === "function" ? await callback(auth) : callback;
99
- if (!token) {
100
- return;
101
- }
102
- if (auth.scheme === "bearer") {
103
- return `Bearer ${token}`;
104
- }
105
- if (auth.scheme === "basic") {
106
- return `Basic ${btoa(token)}`;
107
- }
108
- return token;
109
- };
110
-
111
- // src/api/generated/core/pathSerializer.ts
135
+ // src/api/generated/core/pathSerializer.gen.ts
112
136
  var separatorArrayExplode = (style) => {
113
137
  switch (style) {
114
138
  case "label":
@@ -207,11 +231,7 @@ var serializeObjectParam = ({
207
231
  if (style !== "deepObject" && !explode) {
208
232
  let values = [];
209
233
  Object.entries(value).forEach(([key, v]) => {
210
- values = [
211
- ...values,
212
- key,
213
- allowReserved ? v : encodeURIComponent(v)
214
- ];
234
+ values = [...values, key, allowReserved ? v : encodeURIComponent(v)];
215
235
  });
216
236
  const joinedValues2 = values.join(",");
217
237
  switch (style) {
@@ -236,7 +256,7 @@ var serializeObjectParam = ({
236
256
  return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
237
257
  };
238
258
 
239
- // src/api/generated/client/utils.ts
259
+ // src/api/generated/core/utils.gen.ts
240
260
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
241
261
  var defaultPathSerializer = ({ path, url: _url }) => {
242
262
  let url = _url;
@@ -262,10 +282,7 @@ var defaultPathSerializer = ({ path, url: _url }) => {
262
282
  continue;
263
283
  }
264
284
  if (Array.isArray(value)) {
265
- url = url.replace(
266
- match,
267
- serializeArrayParam({ explode, name, style, value })
268
- );
285
+ url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
269
286
  continue;
270
287
  }
271
288
  if (typeof value === "object") {
@@ -299,10 +316,62 @@ var defaultPathSerializer = ({ path, url: _url }) => {
299
316
  }
300
317
  return url;
301
318
  };
319
+ var getUrl = ({
320
+ baseUrl,
321
+ path,
322
+ query,
323
+ querySerializer,
324
+ url: _url
325
+ }) => {
326
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
327
+ let url = (baseUrl ?? "") + pathUrl;
328
+ if (path) {
329
+ url = defaultPathSerializer({ path, url });
330
+ }
331
+ let search = query ? querySerializer(query) : "";
332
+ if (search.startsWith("?")) {
333
+ search = search.substring(1);
334
+ }
335
+ if (search) {
336
+ url += `?${search}`;
337
+ }
338
+ return url;
339
+ };
340
+ function getValidRequestBody(options) {
341
+ const hasBody = options.body !== void 0;
342
+ const isSerializedBody = hasBody && options.bodySerializer;
343
+ if (isSerializedBody) {
344
+ if ("serializedBody" in options) {
345
+ const hasSerializedBody = options.serializedBody !== void 0 && options.serializedBody !== "";
346
+ return hasSerializedBody ? options.serializedBody : null;
347
+ }
348
+ return options.body !== "" ? options.body : null;
349
+ }
350
+ if (hasBody) {
351
+ return options.body;
352
+ }
353
+ return void 0;
354
+ }
355
+
356
+ // src/api/generated/core/auth.gen.ts
357
+ var getAuthToken = async (auth, callback) => {
358
+ const token = typeof callback === "function" ? await callback(auth) : callback;
359
+ if (!token) {
360
+ return;
361
+ }
362
+ if (auth.scheme === "bearer") {
363
+ return `Bearer ${token}`;
364
+ }
365
+ if (auth.scheme === "basic") {
366
+ return `Basic ${btoa(token)}`;
367
+ }
368
+ return token;
369
+ };
370
+
371
+ // src/api/generated/client/utils.gen.ts
302
372
  var createQuerySerializer = ({
303
- allowReserved,
304
- array,
305
- object
373
+ parameters = {},
374
+ ...args
306
375
  } = {}) => {
307
376
  const querySerializer = (queryParams) => {
308
377
  const search = [];
@@ -312,29 +381,30 @@ var createQuerySerializer = ({
312
381
  if (value === void 0 || value === null) {
313
382
  continue;
314
383
  }
384
+ const options = parameters[name] || args;
315
385
  if (Array.isArray(value)) {
316
386
  const serializedArray = serializeArrayParam({
317
- allowReserved,
387
+ allowReserved: options.allowReserved,
318
388
  explode: true,
319
389
  name,
320
390
  style: "form",
321
391
  value,
322
- ...array
392
+ ...options.array
323
393
  });
324
394
  if (serializedArray) search.push(serializedArray);
325
395
  } else if (typeof value === "object") {
326
396
  const serializedObject = serializeObjectParam({
327
- allowReserved,
397
+ allowReserved: options.allowReserved,
328
398
  explode: true,
329
399
  name,
330
400
  style: "deepObject",
331
401
  value,
332
- ...object
402
+ ...options.object
333
403
  });
334
404
  if (serializedObject) search.push(serializedObject);
335
405
  } else {
336
406
  const serializedPrimitive = serializePrimitiveParam({
337
- allowReserved,
407
+ allowReserved: options.allowReserved,
338
408
  name,
339
409
  value
340
410
  });
@@ -360,20 +430,28 @@ var getParseAs = (contentType) => {
360
430
  if (cleanContent === "multipart/form-data") {
361
431
  return "formData";
362
432
  }
363
- if (["application/", "audio/", "image/", "video/"].some(
364
- (type) => cleanContent.startsWith(type)
365
- )) {
433
+ if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
366
434
  return "blob";
367
435
  }
368
436
  if (cleanContent.startsWith("text/")) {
369
437
  return "text";
370
438
  }
439
+ return;
371
440
  };
372
- var setAuthParams = async ({
373
- security,
374
- ...options
375
- }) => {
376
- for (const auth of security) {
441
+ var checkForExistence = (options, name) => {
442
+ if (!name) {
443
+ return false;
444
+ }
445
+ if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
446
+ return true;
447
+ }
448
+ return false;
449
+ };
450
+ async function setAuthParams(options) {
451
+ for (const auth of options.security ?? []) {
452
+ if (checkForExistence(options, auth.name)) {
453
+ continue;
454
+ }
377
455
  const token = await getAuthToken(auth, options.auth);
378
456
  if (!token) {
379
457
  continue;
@@ -394,40 +472,15 @@ var setAuthParams = async ({
394
472
  options.headers.set(name, token);
395
473
  break;
396
474
  }
397
- return;
398
- }
399
- };
400
- var buildUrl = (options) => {
401
- const url = getUrl({
402
- baseUrl: options.baseUrl,
403
- path: options.path,
404
- query: options.query,
405
- querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
406
- url: options.url
407
- });
408
- return url;
409
- };
410
- var getUrl = ({
411
- baseUrl,
412
- path,
413
- query,
414
- querySerializer,
415
- url: _url
416
- }) => {
417
- const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
418
- let url = (baseUrl ?? "") + pathUrl;
419
- if (path) {
420
- url = defaultPathSerializer({ path, url });
421
- }
422
- let search = query ? querySerializer(query) : "";
423
- if (search.startsWith("?")) {
424
- search = search.substring(1);
425
- }
426
- if (search) {
427
- url += `?${search}`;
428
475
  }
429
- return url;
430
- };
476
+ }
477
+ var buildUrl = (options) => getUrl({
478
+ baseUrl: options.baseUrl,
479
+ path: options.path,
480
+ query: options.query,
481
+ querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
482
+ url: options.url
483
+ });
431
484
  var mergeConfigs = (a, b) => {
432
485
  const config = { ...a, ...b };
433
486
  if (config.baseUrl?.endsWith("/")) {
@@ -436,13 +489,20 @@ var mergeConfigs = (a, b) => {
436
489
  config.headers = mergeHeaders(a.headers, b.headers);
437
490
  return config;
438
491
  };
492
+ var headersEntries = (headers) => {
493
+ const entries = [];
494
+ headers.forEach((value, key) => {
495
+ entries.push([key, value]);
496
+ });
497
+ return entries;
498
+ };
439
499
  var mergeHeaders = (...headers) => {
440
500
  const mergedHeaders = new Headers();
441
501
  for (const header of headers) {
442
- if (!header || typeof header !== "object") {
502
+ if (!header) {
443
503
  continue;
444
504
  }
445
- const iterator = header instanceof Headers ? header.entries() : Object.entries(header);
505
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
446
506
  for (const [key, value] of iterator) {
447
507
  if (value === null) {
448
508
  mergedHeaders.delete(key);
@@ -461,42 +521,37 @@ var mergeHeaders = (...headers) => {
461
521
  return mergedHeaders;
462
522
  };
463
523
  var Interceptors = class {
464
- _fns;
465
- constructor() {
466
- this._fns = [];
467
- }
524
+ fns = [];
468
525
  clear() {
469
- this._fns = [];
526
+ this.fns = [];
470
527
  }
471
- getInterceptorIndex(id) {
472
- if (typeof id === "number") {
473
- return this._fns[id] ? id : -1;
474
- } else {
475
- return this._fns.indexOf(id);
528
+ eject(id) {
529
+ const index = this.getInterceptorIndex(id);
530
+ if (this.fns[index]) {
531
+ this.fns[index] = null;
476
532
  }
477
533
  }
478
534
  exists(id) {
479
535
  const index = this.getInterceptorIndex(id);
480
- return !!this._fns[index];
536
+ return Boolean(this.fns[index]);
481
537
  }
482
- eject(id) {
483
- const index = this.getInterceptorIndex(id);
484
- if (this._fns[index]) {
485
- this._fns[index] = null;
538
+ getInterceptorIndex(id) {
539
+ if (typeof id === "number") {
540
+ return this.fns[id] ? id : -1;
486
541
  }
542
+ return this.fns.indexOf(id);
487
543
  }
488
544
  update(id, fn) {
489
545
  const index = this.getInterceptorIndex(id);
490
- if (this._fns[index]) {
491
- this._fns[index] = fn;
546
+ if (this.fns[index]) {
547
+ this.fns[index] = fn;
492
548
  return id;
493
- } else {
494
- return false;
495
549
  }
550
+ return false;
496
551
  }
497
552
  use(fn) {
498
- this._fns = [...this._fns, fn];
499
- return this._fns.length - 1;
553
+ this.fns.push(fn);
554
+ return this.fns.length - 1;
500
555
  }
501
556
  };
502
557
  var createInterceptors = () => ({
@@ -526,7 +581,7 @@ var createConfig = (override = {}) => ({
526
581
  ...override
527
582
  });
528
583
 
529
- // src/api/generated/client/client.ts
584
+ // src/api/generated/client/client.gen.ts
530
585
  var createClient = (config = {}) => {
531
586
  let _config = mergeConfigs(createConfig(), config);
532
587
  const getConfig = () => ({ ..._config });
@@ -535,2362 +590,886 @@ var createClient = (config = {}) => {
535
590
  return getConfig();
536
591
  };
537
592
  const interceptors = createInterceptors();
538
- const request = async (options) => {
593
+ const beforeRequest = async (options) => {
539
594
  const opts = {
540
595
  ..._config,
541
596
  ...options,
542
597
  fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
543
- headers: mergeHeaders(_config.headers, options.headers)
598
+ headers: mergeHeaders(_config.headers, options.headers),
599
+ serializedBody: void 0
544
600
  };
545
601
  if (opts.security) {
546
- await setAuthParams({
547
- ...opts,
548
- security: opts.security
549
- });
602
+ await setAuthParams(opts);
603
+ }
604
+ if (opts.requestValidator) {
605
+ await opts.requestValidator(opts);
550
606
  }
551
- if (opts.body && opts.bodySerializer) {
552
- opts.body = opts.bodySerializer(opts.body);
607
+ if (opts.body !== void 0 && opts.bodySerializer) {
608
+ opts.serializedBody = opts.bodySerializer(opts.body);
553
609
  }
554
- if (opts.body === void 0 || opts.body === "") {
610
+ if (opts.body === void 0 || opts.serializedBody === "") {
555
611
  opts.headers.delete("Content-Type");
556
612
  }
557
- const url = buildUrl(opts);
558
- const requestInit = {
559
- redirect: "follow",
560
- ...opts
561
- };
562
- let request2 = new Request(url, requestInit);
563
- for (const fn of interceptors.request._fns) {
564
- if (fn) {
565
- request2 = await fn(request2, opts);
613
+ const resolvedOpts = opts;
614
+ const url = buildUrl(resolvedOpts);
615
+ return { opts: resolvedOpts, url };
616
+ };
617
+ const request = async (options) => {
618
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
619
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
620
+ let request2;
621
+ let response;
622
+ try {
623
+ const { opts, url } = await beforeRequest(options);
624
+ const requestInit = {
625
+ redirect: "follow",
626
+ ...opts,
627
+ body: getValidRequestBody(opts)
628
+ };
629
+ request2 = new Request(url, requestInit);
630
+ for (const fn of interceptors.request.fns) {
631
+ if (fn) {
632
+ request2 = await fn(request2, opts);
633
+ }
566
634
  }
567
- }
568
- const _fetch = opts.fetch;
569
- let response = await _fetch(request2);
570
- for (const fn of interceptors.response._fns) {
571
- if (fn) {
572
- response = await fn(response, request2, opts);
635
+ const _fetch = opts.fetch;
636
+ response = await _fetch(request2);
637
+ for (const fn of interceptors.response.fns) {
638
+ if (fn) {
639
+ response = await fn(response, request2, opts);
640
+ }
573
641
  }
574
- }
575
- const result = {
576
- request: request2,
577
- response
578
- };
579
- if (response.ok) {
580
- if (response.status === 204 || response.headers.get("Content-Length") === "0") {
581
- return opts.responseStyle === "data" ? {} : {
582
- data: {},
642
+ const result = {
643
+ request: request2,
644
+ response
645
+ };
646
+ if (response.ok) {
647
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
648
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
649
+ let emptyData;
650
+ switch (parseAs) {
651
+ case "arrayBuffer":
652
+ case "blob":
653
+ case "text":
654
+ emptyData = await response[parseAs]();
655
+ break;
656
+ case "formData":
657
+ emptyData = new FormData();
658
+ break;
659
+ case "stream":
660
+ emptyData = response.body;
661
+ break;
662
+ case "json":
663
+ default:
664
+ emptyData = {};
665
+ break;
666
+ }
667
+ return opts.responseStyle === "data" ? emptyData : {
668
+ data: emptyData,
669
+ ...result
670
+ };
671
+ }
672
+ let data;
673
+ switch (parseAs) {
674
+ case "arrayBuffer":
675
+ case "blob":
676
+ case "formData":
677
+ case "text":
678
+ data = await response[parseAs]();
679
+ break;
680
+ case "json": {
681
+ const text = await response.text();
682
+ data = text ? JSON.parse(text) : {};
683
+ break;
684
+ }
685
+ case "stream":
686
+ return opts.responseStyle === "data" ? response.body : {
687
+ data: response.body,
688
+ ...result
689
+ };
690
+ }
691
+ if (parseAs === "json") {
692
+ if (opts.responseValidator) {
693
+ await opts.responseValidator(data);
694
+ }
695
+ if (opts.responseTransformer) {
696
+ data = await opts.responseTransformer(data);
697
+ }
698
+ }
699
+ return opts.responseStyle === "data" ? data : {
700
+ data,
583
701
  ...result
584
702
  };
585
703
  }
586
- const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
587
- if (parseAs === "stream") {
588
- return opts.responseStyle === "data" ? response.body : {
589
- data: response.body,
590
- ...result
591
- };
704
+ const textError = await response.text();
705
+ let jsonError;
706
+ try {
707
+ jsonError = JSON.parse(textError);
708
+ } catch {
592
709
  }
593
- let data = await response[parseAs]();
594
- if (parseAs === "json") {
595
- if (opts.responseValidator) {
596
- await opts.responseValidator(data);
597
- }
598
- if (opts.responseTransformer) {
599
- data = await opts.responseTransformer(data);
710
+ throw jsonError ?? textError;
711
+ } catch (error) {
712
+ let finalError = error;
713
+ for (const fn of interceptors.error.fns) {
714
+ if (fn) {
715
+ finalError = await fn(finalError, response, request2, options);
600
716
  }
601
717
  }
602
- return opts.responseStyle === "data" ? data : {
603
- data,
604
- ...result
605
- };
606
- }
607
- let error = await response.text();
608
- try {
609
- error = JSON.parse(error);
610
- } catch {
611
- }
612
- let finalError = error;
613
- for (const fn of interceptors.error._fns) {
614
- if (fn) {
615
- finalError = await fn(error, response, request2, opts);
718
+ finalError = finalError || {};
719
+ if (throwOnError) {
720
+ throw finalError;
616
721
  }
722
+ return responseStyle === "data" ? void 0 : {
723
+ error: finalError,
724
+ request: request2,
725
+ response
726
+ };
617
727
  }
618
- finalError = finalError || {};
619
- if (opts.throwOnError) {
620
- throw finalError;
621
- }
622
- return opts.responseStyle === "data" ? void 0 : {
623
- error: finalError,
624
- ...result
625
- };
626
728
  };
729
+ const makeMethodFn = (method) => (options) => request({ ...options, method });
730
+ const makeSseFn = (method) => async (options) => {
731
+ const { opts, url } = await beforeRequest(options);
732
+ return createSseClient({
733
+ ...opts,
734
+ body: opts.body,
735
+ method,
736
+ onRequest: async (url2, init) => {
737
+ let request2 = new Request(url2, init);
738
+ for (const fn of interceptors.request.fns) {
739
+ if (fn) {
740
+ request2 = await fn(request2, opts);
741
+ }
742
+ }
743
+ return request2;
744
+ },
745
+ serializedBody: getValidRequestBody(opts),
746
+ url
747
+ });
748
+ };
749
+ const _buildUrl = (options) => buildUrl({ ..._config, ...options });
627
750
  return {
628
- buildUrl,
629
- connect: (options) => request({ ...options, method: "CONNECT" }),
630
- delete: (options) => request({ ...options, method: "DELETE" }),
631
- get: (options) => request({ ...options, method: "GET" }),
751
+ buildUrl: _buildUrl,
752
+ connect: makeMethodFn("CONNECT"),
753
+ delete: makeMethodFn("DELETE"),
754
+ get: makeMethodFn("GET"),
632
755
  getConfig,
633
- head: (options) => request({ ...options, method: "HEAD" }),
756
+ head: makeMethodFn("HEAD"),
634
757
  interceptors,
635
- options: (options) => request({ ...options, method: "OPTIONS" }),
636
- patch: (options) => request({ ...options, method: "PATCH" }),
637
- post: (options) => request({ ...options, method: "POST" }),
638
- put: (options) => request({ ...options, method: "PUT" }),
758
+ options: makeMethodFn("OPTIONS"),
759
+ patch: makeMethodFn("PATCH"),
760
+ post: makeMethodFn("POST"),
761
+ put: makeMethodFn("PUT"),
639
762
  request,
640
763
  setConfig,
641
- trace: (options) => request({ ...options, method: "TRACE" })
764
+ sse: {
765
+ connect: makeSseFn("CONNECT"),
766
+ delete: makeSseFn("DELETE"),
767
+ get: makeSseFn("GET"),
768
+ head: makeSseFn("HEAD"),
769
+ options: makeSseFn("OPTIONS"),
770
+ patch: makeSseFn("PATCH"),
771
+ post: makeSseFn("POST"),
772
+ put: makeSseFn("PUT"),
773
+ trace: makeSseFn("TRACE")
774
+ },
775
+ trace: makeMethodFn("TRACE")
642
776
  };
643
777
  };
644
778
 
645
- // src/api/client-config.ts
646
- var createClientConfig = (base) => ({
647
- ...base,
648
- throwOnError: false
649
- });
779
+ // src/api/client.ts
780
+ var CallpadError = class extends Error {
781
+ constructor(code, message, status) {
782
+ super(message);
783
+ this.code = code;
784
+ this.status = status;
785
+ this.name = "CallpadError";
786
+ }
787
+ code;
788
+ status;
789
+ };
790
+ var apiClient = (config) => {
791
+ const client2 = createClient({
792
+ baseUrl: config.apiUrl,
793
+ fetch: config.fetch
794
+ });
795
+ client2.interceptors.request.use(async (request) => {
796
+ const token = await config.auth.getAccessToken();
797
+ request.headers.set("Authorization", `Bearer ${token}`);
798
+ return request;
799
+ });
800
+ return client2;
801
+ };
802
+ var unwrap = async (result, auth) => {
803
+ const response = await result;
804
+ if (response.error !== void 0) {
805
+ if (response.response?.status === 401) {
806
+ await auth?.onUnauthorized?.();
807
+ }
808
+ throw errorFrom(response.error, response.response?.status);
809
+ }
810
+ if (response.data === void 0) {
811
+ throw new CallpadError("empty_response", "API response did not include data");
812
+ }
813
+ return response.data;
814
+ };
815
+ var errorFrom = (error, status) => {
816
+ if (isProblem(error)) {
817
+ return new CallpadError(error.code, error.message, status);
818
+ }
819
+ if (error instanceof Error) {
820
+ return new CallpadError("request_failed", error.message, status);
821
+ }
822
+ if (typeof error === "string" && error.length > 0) {
823
+ return new CallpadError("request_failed", error, status);
824
+ }
825
+ return new CallpadError("request_failed", "Request failed", status);
826
+ };
827
+ var isProblem = (value) => {
828
+ if (!value || typeof value !== "object") {
829
+ return false;
830
+ }
831
+ const problem = value;
832
+ return typeof problem.code === "string" && typeof problem.message === "string";
833
+ };
650
834
 
651
835
  // src/api/generated/client.gen.ts
652
- var client = createClient(createClientConfig(createConfig({
653
- baseUrl: "http://localhost:3002"
654
- })));
836
+ var client = createClient(createConfig());
655
837
 
656
- // src/api/map-error.ts
657
- function isBackendErrorBody(body) {
658
- if (body === null || typeof body !== "object") {
659
- return false;
838
+ // src/api/generated/sdk.gen.ts
839
+ var getRealtimeToken = (options) => (options?.client ?? client).get({
840
+ security: [{ scheme: "bearer", type: "http" }],
841
+ url: "/api/v1/realtime/token",
842
+ ...options
843
+ });
844
+ var listSessions = (options) => (options?.client ?? client).get({
845
+ security: [{ scheme: "bearer", type: "http" }],
846
+ url: "/api/v1/sessions",
847
+ ...options
848
+ });
849
+ var createSessionToken = (options) => (options.client ?? client).post({
850
+ security: [{ scheme: "bearer", type: "http" }],
851
+ url: "/api/v1/sessions/{sessionId}/tokens",
852
+ ...options
853
+ });
854
+ var leaveSession = (options) => (options.client ?? client).post({
855
+ security: [{ scheme: "bearer", type: "http" }],
856
+ url: "/api/v1/sessions/{sessionId}/leave",
857
+ ...options
858
+ });
859
+ var endSession = (options) => (options.client ?? client).post({
860
+ security: [{ scheme: "bearer", type: "http" }],
861
+ url: "/api/v1/sessions/{sessionId}/end",
862
+ ...options
863
+ });
864
+ var startRecording = (options) => (options.client ?? client).post({
865
+ security: [{ scheme: "bearer", type: "http" }],
866
+ url: "/api/v1/sessions/{sessionId}/recording/start",
867
+ ...options
868
+ });
869
+ var stopRecording = (options) => (options.client ?? client).post({
870
+ security: [{ scheme: "bearer", type: "http" }],
871
+ url: "/api/v1/sessions/{sessionId}/recording/stop",
872
+ ...options
873
+ });
874
+ var addSessionParticipant = (options) => (options.client ?? client).post({
875
+ security: [{ scheme: "bearer", type: "http" }],
876
+ url: "/api/v1/sessions/{sessionId}/participants",
877
+ ...options,
878
+ headers: {
879
+ "Content-Type": "application/json",
880
+ ...options.headers
660
881
  }
661
- const maybe = body;
662
- if (maybe.error === null || typeof maybe.error !== "object") {
663
- return false;
882
+ });
883
+ var removeSessionParticipant = (options) => (options.client ?? client).delete({
884
+ security: [{ scheme: "bearer", type: "http" }],
885
+ url: "/api/v1/sessions/{sessionId}/participants/{participantId}",
886
+ ...options
887
+ });
888
+ var acceptSessionParticipant = (options) => (options.client ?? client).post({
889
+ security: [{ scheme: "bearer", type: "http" }],
890
+ url: "/api/v1/sessions/{sessionId}/participants/{participantId}/accept",
891
+ ...options
892
+ });
893
+ var rejectSessionParticipant = (options) => (options.client ?? client).post({
894
+ security: [{ scheme: "bearer", type: "http" }],
895
+ url: "/api/v1/sessions/{sessionId}/participants/{participantId}/reject",
896
+ ...options
897
+ });
898
+ var createSession = (options) => (options.client ?? client).post({
899
+ security: [{ scheme: "bearer", type: "http" }],
900
+ url: "/api/v1/vendors/{vendor}/sessions",
901
+ ...options,
902
+ headers: {
903
+ "Content-Type": "application/json",
904
+ ...options.headers
664
905
  }
665
- const e = maybe.error;
666
- return typeof e.code === "string" && typeof e.title === "string" && typeof e.kind === "string";
667
- }
668
- var kindMap = {
669
- validation: "validation",
670
- unauth: "unauth",
671
- forbidden: "forbidden",
672
- notfound: "notfound",
673
- precondition: "precondition",
674
- conflict: "conflict",
675
- internal: "internal",
676
- unavailable: "unavailable",
677
- timeout: "timeout"
906
+ });
907
+
908
+ // src/core/events.ts
909
+ var createEmitter = () => {
910
+ const listeners = /* @__PURE__ */ new Map();
911
+ const emit = (event, payload) => {
912
+ const fns = listeners.get(event);
913
+ if (!fns) {
914
+ return;
915
+ }
916
+ for (const fn of fns) {
917
+ try {
918
+ fn(payload);
919
+ } catch (err) {
920
+ if (event !== "error") {
921
+ emit("error", err instanceof Error ? err : new Error("Event listener failed"));
922
+ }
923
+ }
924
+ }
925
+ };
926
+ return {
927
+ on: (event, fn) => {
928
+ let fns = listeners.get(event);
929
+ if (!fns) {
930
+ fns = /* @__PURE__ */ new Set();
931
+ listeners.set(event, fns);
932
+ }
933
+ fns.add(fn);
934
+ return () => {
935
+ fns?.delete(fn);
936
+ if (fns?.size === 0) {
937
+ listeners.delete(event);
938
+ }
939
+ };
940
+ },
941
+ emit
942
+ };
678
943
  };
679
- function mapError(body, status) {
680
- if (status === 0 || status === void 0 || status === null) {
681
- return callpadError.network("callpad.network", "Network error", {
682
- status: status ?? void 0
683
- });
684
- }
685
- if (!isBackendErrorBody(body)) {
686
- if (status === 401) {
687
- return callpadError.unauth("callpad.unauth", "Unauthorized", { status });
944
+ var createRealtime = (client2, auth, store, emitter) => {
945
+ let rt = null;
946
+ let disposed = false;
947
+ const fetchToken = async () => unwrap(getRealtimeToken({ client: client2 }), auth);
948
+ const refreshToken = async () => {
949
+ try {
950
+ const token = await fetchToken();
951
+ return token.token;
952
+ } catch (err) {
953
+ if (err instanceof CallpadError && (err.status === 401 || err.status === 403)) {
954
+ throw new UnauthorizedError(err.message);
955
+ }
956
+ throw err;
688
957
  }
689
- if (status === 403) {
690
- return callpadError.forbidden("callpad.forbidden", "Forbidden", {
691
- status
692
- });
958
+ };
959
+ const applyMessage = (message) => {
960
+ if (message.event === "session.upsert") {
961
+ const change = store.upsert(message.data);
962
+ if (change === "added") {
963
+ emitter.emit("session.added", message.data);
964
+ }
965
+ if (change === "updated") {
966
+ emitter.emit("session.updated", message.data);
967
+ }
968
+ return;
693
969
  }
694
- if (status === 404) {
695
- return callpadError.notfound("callpad.not_found", "Not found", {
696
- status
970
+ if (store.remove(message.data)) {
971
+ emitter.emit("session.removed", {
972
+ sessionId: message.data.id,
973
+ vendor: message.data.vendor,
974
+ version: message.data.version
697
975
  });
698
976
  }
699
- if (status >= 500) {
700
- return callpadError.internal(
701
- "callpad.internal",
702
- "Internal server error",
703
- { status }
704
- );
977
+ };
978
+ const setStatus = (status) => {
979
+ store.setStatus(status);
980
+ emitter.emit("status", status);
981
+ };
982
+ const disconnect = async () => {
983
+ rt?.disconnect();
984
+ rt = null;
985
+ if (!disposed) {
986
+ setStatus("offline");
705
987
  }
706
- return callpadError.internal(
707
- "callpad.unknown_response",
708
- "Unexpected response shape",
709
- {
710
- status
711
- }
712
- );
713
- }
714
- const e = body.error;
715
- const kind = kindMap[e.kind] ?? "internal";
716
- return {
717
- kind,
718
- code: e.code,
719
- title: e.title,
720
- detail: e.detail,
721
- retryable: e.retryable,
722
- issues: e.issues,
723
- status
724
- };
725
- }
726
-
727
- // src/api/call.ts
728
- async function apiCall(call) {
729
- try {
730
- const res = await call();
731
- if (res.error !== void 0 || !res.response.ok) {
732
- return err(
733
- mapError(
734
- res.error !== void 0 ? { error: res.error } : void 0,
735
- res.response.status
736
- )
737
- );
738
- }
739
- if (res.data === void 0) {
740
- return err(
741
- callpadError.internal(
742
- "callpad.empty_response",
743
- "Expected response body, got empty"
744
- )
745
- );
746
- }
747
- return ok(res.data);
748
- } catch (cause) {
749
- return err(callpadError.fromUnknown(cause, "HTTP request failed"));
750
- }
751
- }
752
-
753
- // src/api/generated/sdk.gen.ts
754
- var getApiV1PulseGrants = (options) => {
755
- return (options?.client ?? client).get({
756
- security: [
757
- {
758
- scheme: "bearer",
759
- type: "http"
760
- }
761
- ],
762
- url: "/api/v1/pulse/grants",
763
- ...options
764
- });
765
- };
766
- var getApiV1VendorsBySlugPresence = (options) => {
767
- return (options.client ?? client).get({
768
- security: [
769
- {
770
- scheme: "bearer",
771
- type: "http"
772
- }
773
- ],
774
- url: "/api/v1/vendors/{slug}/presence",
775
- ...options
776
- });
777
- };
778
- var deleteApiV1VendorsBySlugPresenceStatus = (options) => {
779
- return (options.client ?? client).delete({
780
- security: [
781
- {
782
- scheme: "bearer",
783
- type: "http"
784
- }
785
- ],
786
- url: "/api/v1/vendors/{slug}/presence/status",
787
- ...options
788
- });
789
- };
790
- var putApiV1VendorsBySlugPresenceStatus = (options) => {
791
- return (options.client ?? client).put({
792
- security: [
793
- {
794
- scheme: "bearer",
795
- type: "http"
796
- }
797
- ],
798
- url: "/api/v1/vendors/{slug}/presence/status",
799
- ...options,
800
- headers: {
801
- "Content-Type": "application/json",
802
- ...options.headers
803
- }
804
- });
805
- };
806
- var getApiV1Sessions = (options) => {
807
- return (options?.client ?? client).get({
808
- security: [
809
- {
810
- scheme: "bearer",
811
- type: "http"
812
- }
813
- ],
814
- url: "/api/v1/sessions",
815
- ...options
816
- });
817
- };
818
- var postApiV1Sessions = (options) => {
819
- return (options.client ?? client).post({
820
- security: [
821
- {
822
- scheme: "bearer",
823
- type: "http"
824
- }
825
- ],
826
- url: "/api/v1/sessions",
827
- ...options,
828
- headers: {
829
- "Content-Type": "application/json",
830
- ...options.headers
831
- }
832
- });
833
- };
834
- var getApiV1SessionsInvites = (options) => {
835
- return (options?.client ?? client).get({
836
- security: [
837
- {
838
- scheme: "bearer",
839
- type: "http"
840
- }
841
- ],
842
- url: "/api/v1/sessions/invites",
843
- ...options
844
- });
845
- };
846
- var postApiV1SessionsBySessionIdInvitesByInviteIdAccept = (options) => {
847
- return (options.client ?? client).post({
848
- security: [
849
- {
850
- scheme: "bearer",
851
- type: "http"
852
- }
853
- ],
854
- url: "/api/v1/sessions/{sessionId}/invites/{inviteId}/accept",
855
- ...options
856
- });
857
- };
858
- var postApiV1SessionsBySessionIdInvitesByInviteIdReject = (options) => {
859
- return (options.client ?? client).post({
860
- security: [
861
- {
862
- scheme: "bearer",
863
- type: "http"
864
- }
865
- ],
866
- url: "/api/v1/sessions/{sessionId}/invites/{inviteId}/reject",
867
- ...options
868
- });
869
- };
870
- var getApiV1SessionsBySessionId = (options) => {
871
- return (options.client ?? client).get({
872
- security: [
873
- {
874
- scheme: "bearer",
875
- type: "http"
876
- }
877
- ],
878
- url: "/api/v1/sessions/{sessionId}",
879
- ...options
880
- });
881
- };
882
- var patchApiV1SessionsBySessionId = (options) => {
883
- return (options.client ?? client).patch({
884
- security: [
885
- {
886
- scheme: "bearer",
887
- type: "http"
888
- }
889
- ],
890
- url: "/api/v1/sessions/{sessionId}",
891
- ...options,
892
- headers: {
893
- "Content-Type": "application/json",
894
- ...options.headers
895
- }
896
- });
897
- };
898
- var postApiV1SessionsBySessionIdJoin = (options) => {
899
- return (options.client ?? client).post({
900
- security: [
901
- {
902
- scheme: "bearer",
903
- type: "http"
904
- }
905
- ],
906
- url: "/api/v1/sessions/{sessionId}/join",
907
- ...options
908
- });
909
- };
910
- var postApiV1SessionsBySessionIdCancel = (options) => {
911
- return (options.client ?? client).post({
912
- security: [
913
- {
914
- scheme: "bearer",
915
- type: "http"
916
- }
917
- ],
918
- url: "/api/v1/sessions/{sessionId}/cancel",
919
- ...options
920
- });
921
- };
922
- var postApiV1SessionsBySessionIdLeave = (options) => {
923
- return (options.client ?? client).post({
924
- security: [
925
- {
926
- scheme: "bearer",
927
- type: "http"
928
- }
929
- ],
930
- url: "/api/v1/sessions/{sessionId}/leave",
931
- ...options
932
- });
933
- };
934
- var postApiV1SessionsBySessionIdHold = (options) => {
935
- return (options.client ?? client).post({
936
- security: [
937
- {
938
- scheme: "bearer",
939
- type: "http"
940
- }
941
- ],
942
- url: "/api/v1/sessions/{sessionId}/hold",
943
- ...options
944
- });
945
- };
946
- var postApiV1SessionsBySessionIdUnhold = (options) => {
947
- return (options.client ?? client).post({
948
- security: [
949
- {
950
- scheme: "bearer",
951
- type: "http"
952
- }
953
- ],
954
- url: "/api/v1/sessions/{sessionId}/unhold",
955
- ...options
956
- });
957
- };
958
- var postApiV1SessionsBySessionIdEnd = (options) => {
959
- return (options.client ?? client).post({
960
- security: [
961
- {
962
- scheme: "bearer",
963
- type: "http"
964
- }
965
- ],
966
- url: "/api/v1/sessions/{sessionId}/end",
967
- ...options
968
- });
969
- };
970
- var postApiV1SessionsBySessionIdParticipants = (options) => {
971
- return (options.client ?? client).post({
972
- security: [
973
- {
974
- scheme: "bearer",
975
- type: "http"
976
- }
977
- ],
978
- url: "/api/v1/sessions/{sessionId}/participants",
979
- ...options,
980
- headers: {
981
- "Content-Type": "application/json",
982
- ...options.headers
983
- }
984
- });
985
- };
986
- var deleteApiV1SessionsBySessionIdParticipantsByParticipantId = (options) => {
987
- return (options.client ?? client).delete({
988
- security: [
989
- {
990
- scheme: "bearer",
991
- type: "http"
992
- }
993
- ],
994
- url: "/api/v1/sessions/{sessionId}/participants/{participantId}",
995
- ...options
996
- });
997
- };
998
- var postApiV1SessionsBySessionIdRecordings = (options) => {
999
- return (options.client ?? client).post({
1000
- security: [
1001
- {
1002
- scheme: "bearer",
1003
- type: "http"
1004
- }
1005
- ],
1006
- url: "/api/v1/sessions/{sessionId}/recordings",
1007
- ...options
1008
- });
1009
- };
1010
- var postApiV1SessionsBySessionIdRecordingsByRecordingIdStop = (options) => {
1011
- return (options.client ?? client).post({
1012
- security: [
1013
- {
1014
- scheme: "bearer",
1015
- type: "http"
1016
- }
1017
- ],
1018
- url: "/api/v1/sessions/{sessionId}/recordings/{recordingId}/stop",
1019
- ...options
1020
- });
1021
- };
1022
-
1023
- // src/api/retry.ts
1024
- var RETRY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
1025
- var DEFAULT_MAX_ATTEMPTS = 3;
1026
- var DEFAULT_INITIAL_DELAY_MS = 300;
1027
- function sleep(ms) {
1028
- return new Promise((resolve) => {
1029
- setTimeout(resolve, ms);
1030
- });
1031
- }
1032
- function nextDelay(attempt, initial) {
1033
- return initial * 2 ** (attempt - 1);
1034
- }
1035
- function makeRetryingFetch(base, opts = {}) {
1036
- const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
1037
- const initialDelay = opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
1038
- return async (request) => {
1039
- let attempt = 0;
1040
- while (true) {
1041
- attempt += 1;
1042
- const res = await base(request.clone());
1043
- if (!RETRY_STATUSES.has(res.status) || attempt >= maxAttempts) {
1044
- return res;
1045
- }
1046
- await sleep(nextDelay(attempt, initialDelay));
1047
- }
1048
- };
1049
- }
1050
-
1051
- // src/core/dispatch.ts
1052
- function dispatchFromView(view) {
1053
- return {
1054
- id: view.id,
1055
- source: view.source,
1056
- strategy: view.strategy,
1057
- state: view.state,
1058
- candidateParticipantIds: view.candidateParticipantIds,
1059
- winnerParticipantId: nullify(view.winnerParticipantId),
1060
- startedAt: view.startedAt,
1061
- resolvedAt: nullify(view.resolvedAt),
1062
- timeoutAt: nullify(view.timeoutAt)
1063
- };
1064
- }
1065
-
1066
- // src/core/participant.ts
1067
- var DEFAULT_MEDIA = {
1068
- micPublished: false,
1069
- micMuted: true,
1070
- cameraPublished: false,
1071
- cameraMuted: true,
1072
- screenShareActive: false
1073
- };
1074
- function participantFromView(view, selfParticipantId, mediaLookup) {
1075
- const media = mediaLookup.get(view.participantIdentity);
1076
- return {
1077
- id: view.id,
1078
- ref: view.ref,
1079
- info: view.info,
1080
- role: view.role,
1081
- capabilities: view.capabilities,
1082
- transport: view.transport,
1083
- state: view.state,
1084
- participantIdentity: view.participantIdentity,
1085
- participantSid: nullify(view.participantSid),
1086
- invitedAt: view.invitedAt,
1087
- acceptedAt: nullify(view.acceptedAt),
1088
- joinedAt: nullify(view.joinedAt),
1089
- endedAt: nullify(view.endedAt),
1090
- leftReason: nullify(view.leftReason),
1091
- failureReason: nullify(view.failureReason),
1092
- isSelf: selfParticipantId !== null && view.id === selfParticipantId,
1093
- isSpeaking: media?.isSpeaking ?? false,
1094
- audioLevel: media?.audioLevel ?? 0,
1095
- connectionQuality: media?.connectionQuality ?? "unknown",
1096
- media: media ? {
1097
- micPublished: media.micPublished,
1098
- micMuted: media.micMuted,
1099
- cameraPublished: media.cameraPublished,
1100
- cameraMuted: media.cameraMuted,
1101
- screenShareActive: media.screenShareActive
1102
- } : DEFAULT_MEDIA
1103
- };
1104
- }
1105
-
1106
- // src/core/recording.ts
1107
- function recordingFromView(view) {
1108
- return {
1109
- id: view.id,
1110
- target: view.target,
1111
- policy: view.policy,
1112
- state: view.state,
1113
- livekitEgressId: nullify(view.livekitEgressId),
1114
- requestedAt: view.requestedAt,
1115
- startedAt: nullify(view.startedAt),
1116
- stoppedAt: nullify(view.stoppedAt),
1117
- failureReason: nullify(view.failureReason)
1118
- };
1119
- }
1120
-
1121
- // src/core/state/event-bus.ts
1122
- var EventBus = class {
1123
- handlers = /* @__PURE__ */ new Map();
1124
- on(type, handler) {
1125
- let set = this.handlers.get(type);
1126
- if (!set) {
1127
- set = /* @__PURE__ */ new Set();
1128
- this.handlers.set(type, set);
1129
- }
1130
- set.add(handler);
1131
- return () => {
1132
- set?.delete(handler);
1133
- };
1134
- }
1135
- emit(type, payload) {
1136
- const set = this.handlers.get(type);
1137
- if (!set) {
1138
- return;
1139
- }
1140
- for (const handler of set) {
1141
- try {
1142
- handler(payload);
1143
- } catch {
1144
- }
1145
- }
1146
- }
1147
- clear() {
1148
- this.handlers.clear();
1149
- }
1150
- hasHandlers(type) {
1151
- const set = this.handlers.get(type);
1152
- return set !== void 0 && set.size > 0;
1153
- }
1154
- };
1155
-
1156
- // src/core/state/reconciler.ts
1157
- function shouldApplyUpsert(prev, incoming) {
1158
- if (prev === void 0) {
1159
- return true;
1160
- }
1161
- return incoming.version > prev.version;
1162
- }
1163
-
1164
- // src/core/telephony-leg.ts
1165
- function telephonyLegFromOperation(participant, op) {
1166
- return {
1167
- id: op.id,
1168
- participantId: participant.id,
1169
- participantIdentity: participant.participantIdentity,
1170
- direction: op.direction,
1171
- state: op.state,
1172
- remotePhoneNumber: op.remotePhoneNumber,
1173
- localPhoneNumber: nullify(op.localPhoneNumber),
1174
- livekitSipCallId: nullify(op.provider.livekitSipCallId),
1175
- providerSipCallId: nullify(op.provider.providerSipCallId),
1176
- livekitSipTrunkId: nullify(op.provider.livekitSipTrunkId),
1177
- vendorPhoneNumberId: nullify(op.provider.vendorPhoneNumberId),
1178
- sipExtension: nullify(op.provider.sipExtension),
1179
- createdAt: op.createdAt,
1180
- ringingAt: nullify(op.ringingAt),
1181
- activeAt: nullify(op.activeAt),
1182
- endedAt: nullify(op.endedAt),
1183
- failureReason: nullify(op.failureReason),
1184
- sipStatusCode: nullify(op.sipStatusCode),
1185
- providerError: nullify(op.providerError)
1186
- };
1187
- }
1188
- function collectSipLegs(participants) {
1189
- const legs = [];
1190
- for (const participant of participants) {
1191
- for (const op of participant.operations) {
1192
- if (op.kind === "sip_leg") {
1193
- legs.push(telephonyLegFromOperation(participant, op));
1194
- }
1195
- }
1196
- }
1197
- return legs;
1198
- }
1199
- var DEFAULT_ROTATE_LEAD_MS = 10 * 60 * 1e3;
1200
- var FORWARDED_EVENTS = [
1201
- "session.upsert",
1202
- "session.remove",
1203
- "session.invite.upsert",
1204
- "session.invite.remove"
1205
- ];
1206
- var PulseTransport = class {
1207
- socket = null;
1208
- grant = null;
1209
- rotateTimer = null;
1210
- status = "idle";
1211
- disposed = false;
1212
- bus = new EventBus();
1213
- rotateLeadMs;
1214
- logger;
1215
- grantFetcher;
1216
- constructor(opts) {
1217
- this.rotateLeadMs = opts.rotateLeadMs ?? DEFAULT_ROTATE_LEAD_MS;
1218
- this.logger = opts.logger.child({ component: "pulse" });
1219
- this.grantFetcher = opts.grantFetcher;
1220
- }
1221
- getStatus() {
1222
- return this.status;
1223
- }
1224
- on(type, handler) {
1225
- return this.bus.on(type, handler);
1226
- }
1227
- async connect() {
1228
- if (this.disposed) {
1229
- return err(callpadError.disposed());
1230
- }
1231
- if (this.status === "connected" || this.status === "connecting") {
1232
- return ok(void 0);
1233
- }
1234
- this.setStatus("connecting");
1235
- const grant = await this.grantFetcher();
1236
- if (!grant.ok) {
1237
- this.setStatus("disconnected");
1238
- return err(grant.error);
1239
- }
1240
- this.grant = grant.value;
1241
- try {
1242
- await this.openSocket(grant.value);
1243
- } catch (cause) {
1244
- this.setStatus("disconnected");
1245
- return err(
1246
- callpadError.unavailable(
1247
- "pulse.connect_failed",
1248
- "Pulse connect failed",
1249
- {
1250
- cause: cause instanceof Error ? cause : void 0
1251
- }
1252
- )
1253
- );
1254
- }
1255
- this.scheduleRotate(grant.value);
1256
- return ok(void 0);
1257
- }
1258
- async disconnect() {
1259
- this.clearRotate();
1260
- this.closeSocket();
1261
- this.setStatus("disconnected");
1262
- }
1263
- async dispose() {
1264
- if (this.disposed) {
1265
- return;
1266
- }
1267
- this.disposed = true;
1268
- this.clearRotate();
1269
- this.closeSocket();
1270
- this.bus.clear();
1271
- this.status = "disconnected";
1272
- }
1273
- setStatus(next) {
1274
- if (next === this.status) {
1275
- return;
1276
- }
1277
- const previous = this.status;
1278
- this.status = next;
1279
- this.bus.emit("status.changed", { status: next, previous });
1280
- }
1281
- openSocket(grant) {
1282
- return new Promise((resolve, reject) => {
1283
- const socket = io(grant.url, {
1284
- path: grant.path,
1285
- transports: ["websocket"],
1286
- auth: { token: grant.token },
1287
- reconnection: true,
1288
- reconnectionAttempts: Infinity
1289
- });
1290
- socket.on("connect", () => {
1291
- this.setStatus("connected");
1292
- });
1293
- socket.on("reconnect_attempt", () => {
1294
- this.setStatus("recovering");
1295
- });
1296
- socket.on("disconnect", () => {
1297
- if (!this.disposed) {
1298
- this.setStatus("recovering");
1299
- }
1300
- });
1301
- socket.on("connect_error", (cause) => {
1302
- const error = callpadError.unavailable(
1303
- "pulse.connect_error",
1304
- "Pulse socket error",
1305
- { cause: cause instanceof Error ? cause : void 0 }
1306
- );
1307
- this.bus.emit("error", { error });
1308
- });
1309
- for (const event of FORWARDED_EVENTS) {
1310
- socket.on(event, (payload) => {
1311
- this.bus.emit(event, payload);
1312
- });
1313
- }
1314
- socket.once("connect", () => resolve());
1315
- socket.once("connect_error", (cause) => {
1316
- if (this.status !== "connected") {
1317
- reject(cause);
1318
- }
1319
- });
1320
- this.socket = socket;
1321
- });
1322
- }
1323
- closeSocket() {
1324
- if (!this.socket) {
1325
- return;
1326
- }
1327
- this.socket.removeAllListeners();
1328
- this.socket.disconnect();
1329
- this.socket = null;
1330
- }
1331
- scheduleRotate(grant) {
1332
- this.clearRotate();
1333
- const expiresAt = Date.parse(grant.expiresAt);
1334
- if (Number.isNaN(expiresAt)) {
1335
- return;
1336
- }
1337
- const refreshAt = Math.max(0, expiresAt - Date.now() - this.rotateLeadMs);
1338
- this.rotateTimer = setTimeout(() => {
1339
- void this.rotateGrant();
1340
- }, refreshAt);
1341
- this.rotateTimer.unref?.();
1342
- }
1343
- scheduleRotateRetry() {
1344
- this.clearRotate();
1345
- this.rotateTimer = setTimeout(() => {
1346
- void this.rotateGrant();
1347
- }, this.rotateLeadMs);
1348
- this.rotateTimer.unref?.();
1349
- }
1350
- clearRotate() {
1351
- if (this.rotateTimer) {
1352
- clearTimeout(this.rotateTimer);
1353
- this.rotateTimer = null;
1354
- }
1355
- }
1356
- async rotateGrant() {
1357
- if (this.disposed) {
1358
- return;
1359
- }
1360
- const grant = await this.grantFetcher();
1361
- if (!grant.ok) {
1362
- this.logger.warn(
1363
- { code: grant.error.code },
1364
- "pulse grant refresh failed"
1365
- );
1366
- this.scheduleRotateRetry();
1367
- return;
1368
- }
1369
- this.grant = grant.value;
1370
- this.closeSocket();
1371
- try {
1372
- await this.openSocket(grant.value);
1373
- } catch (cause) {
1374
- this.logger.warn(
1375
- { err: cause instanceof Error ? cause : void 0 },
1376
- "pulse reopen failed"
1377
- );
1378
- }
1379
- this.scheduleRotate(grant.value);
1380
- }
1381
- };
1382
- function qualityOf(q) {
1383
- switch (q) {
1384
- case ConnectionQuality.Excellent:
1385
- return "excellent";
1386
- case ConnectionQuality.Good:
1387
- return "good";
1388
- case ConnectionQuality.Poor:
1389
- return "poor";
1390
- case ConnectionQuality.Lost:
1391
- return "lost";
1392
- default:
1393
- return "unknown";
1394
- }
1395
- }
1396
- function snapshotOf(p) {
1397
- const micPub = p.getTrackPublication(Track.Source.Microphone);
1398
- const camPub = p.getTrackPublication(Track.Source.Camera);
1399
- const screenPub = p.getTrackPublication(Track.Source.ScreenShare);
1400
- return {
1401
- identity: p.identity,
1402
- isSpeaking: p.isSpeaking,
1403
- audioLevel: p.audioLevel,
1404
- micPublished: micPub !== void 0,
1405
- micMuted: micPub?.isMuted ?? true,
1406
- cameraPublished: camPub !== void 0,
1407
- cameraMuted: camPub?.isMuted ?? true,
1408
- screenShareActive: screenPub !== void 0 && !screenPub.isMuted,
1409
- connectionQuality: qualityOf(p.connectionQuality)
1410
- };
1411
- }
1412
- function snapshotEquals(a, b) {
1413
- return a.isSpeaking === b.isSpeaking && a.audioLevel === b.audioLevel && a.micPublished === b.micPublished && a.micMuted === b.micMuted && a.cameraPublished === b.cameraPublished && a.cameraMuted === b.cameraMuted && a.screenShareActive === b.screenShareActive && a.connectionQuality === b.connectionQuality;
1414
- }
1415
- var MediaRoomAdapter = class {
1416
- room;
1417
- bus = new EventBus();
1418
- logger;
1419
- snapshots = /* @__PURE__ */ new Map();
1420
- state = "unconnected";
1421
- disposed = false;
1422
- constructor(logger) {
1423
- this.logger = logger.child({ component: "media" });
1424
- this.room = new Room({
1425
- adaptiveStream: true,
1426
- dynacast: true,
1427
- disconnectOnPageLeave: false,
1428
- singlePeerConnection: false
1429
- });
1430
- this.wireRoomEvents();
1431
- }
1432
- getRoom() {
1433
- return this.room;
1434
- }
1435
- getState() {
1436
- return this.state;
1437
- }
1438
- getSnapshot(identity) {
1439
- return this.snapshots.get(identity);
1440
- }
1441
- listSnapshots() {
1442
- return this.snapshots;
1443
- }
1444
- on(type, handler) {
1445
- return this.bus.on(type, handler);
1446
- }
1447
- async connect(opts) {
1448
- if (this.disposed) {
1449
- return err(callpadError.disposed());
1450
- }
1451
- this.setState("connecting");
1452
- try {
1453
- await this.room.connect(opts.url, opts.token);
1454
- await this.room.localParticipant.setMicrophoneEnabled(true);
1455
- if (opts.video === true) {
1456
- await this.room.localParticipant.setCameraEnabled(true);
1457
- }
1458
- return ok(void 0);
1459
- } catch (cause) {
1460
- this.setState("disconnected");
1461
- return err(
1462
- callpadError.mediaUnavailable(
1463
- "media.connect_failed",
1464
- "Media connect failed",
1465
- {
1466
- cause: cause instanceof Error ? cause : void 0
1467
- }
1468
- )
1469
- );
1470
- }
1471
- }
1472
- async disconnect() {
1473
- await this.room.disconnect(true);
1474
- this.snapshots.clear();
1475
- this.setState("disconnected");
1476
- }
1477
- async dispose() {
1478
- if (this.disposed) {
1479
- return;
1480
- }
1481
- this.disposed = true;
1482
- await this.room.disconnect(true);
1483
- this.bus.clear();
1484
- this.snapshots.clear();
1485
- this.state = "disconnected";
1486
- }
1487
- setState(next) {
1488
- if (next === this.state) {
1489
- return;
1490
- }
1491
- const previous = this.state;
1492
- this.state = next;
1493
- this.bus.emit("state.changed", { state: next, previous });
1494
- }
1495
- wireRoomEvents() {
1496
- this.room.on(RoomEvent.ConnectionStateChanged, (state) => {
1497
- switch (state) {
1498
- case ConnectionState.Connected:
1499
- this.setState("connected");
1500
- break;
1501
- case ConnectionState.Connecting:
1502
- this.setState("connecting");
1503
- break;
1504
- case ConnectionState.Reconnecting:
1505
- this.setState("reconnecting");
1506
- break;
1507
- case ConnectionState.Disconnected:
1508
- this.setState("disconnected");
1509
- break;
1510
- }
1511
- });
1512
- const refresh = (p) => {
1513
- const snapshot = snapshotOf(p);
1514
- const previous = this.snapshots.get(p.identity);
1515
- if (previous && snapshotEquals(previous, snapshot)) {
1516
- return;
1517
- }
1518
- this.snapshots.set(p.identity, snapshot);
1519
- this.bus.emit("participant.changed", { identity: p.identity, snapshot });
1520
- };
1521
- this.room.on(
1522
- RoomEvent.ParticipantConnected,
1523
- (p) => refresh(p)
1524
- );
1525
- this.room.on(RoomEvent.ParticipantDisconnected, (p) => {
1526
- this.snapshots.delete(p.identity);
1527
- this.bus.emit("participant.changed", {
1528
- identity: p.identity,
1529
- snapshot: {
1530
- identity: p.identity,
1531
- isSpeaking: false,
1532
- audioLevel: 0,
1533
- micPublished: false,
1534
- micMuted: true,
1535
- cameraPublished: false,
1536
- cameraMuted: true,
1537
- screenShareActive: false,
1538
- connectionQuality: "lost"
1539
- }
1540
- });
1541
- });
1542
- this.room.on(RoomEvent.TrackSubscribed, (_track, _pub, p) => refresh(p));
1543
- this.room.on(RoomEvent.TrackUnsubscribed, (_track, _pub, p) => refresh(p));
1544
- this.room.on(RoomEvent.TrackMuted, (_pub, p) => refresh(p));
1545
- this.room.on(RoomEvent.TrackUnmuted, (_pub, p) => refresh(p));
1546
- this.room.on(
1547
- RoomEvent.LocalTrackPublished,
1548
- (_pub, p) => refresh(p)
1549
- );
1550
- this.room.on(
1551
- RoomEvent.LocalTrackUnpublished,
1552
- (_pub, p) => refresh(p)
1553
- );
1554
- this.room.on(
1555
- RoomEvent.ConnectionQualityChanged,
1556
- (_q, p) => refresh(p)
1557
- );
1558
- this.room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
1559
- const identities = speakers.map((p) => p.identity);
1560
- this.bus.emit("active_speakers.changed", { identities });
1561
- });
1562
- this.room.on(RoomEvent.MediaDevicesError, (cause) => {
1563
- this.bus.emit("error", {
1564
- error: callpadError.mediaDevice(
1565
- "media.device_error",
1566
- "Media device error",
1567
- {
1568
- cause: cause instanceof Error ? cause : void 0
1569
- }
1570
- )
1571
- });
1572
- this.logger.warn(
1573
- { err: cause instanceof Error ? cause : void 0 },
1574
- "media device error"
1575
- );
1576
- });
1577
- }
1578
- };
1579
-
1580
- // src/core/session/session.ts
1581
- var Session = class {
1582
- view;
1583
- vendor;
1584
- logger;
1585
- bus = new EventBus();
1586
- media;
1587
- commands;
1588
- ctx;
1589
- mediaStatus = "unconnected";
1590
- selfParticipantId;
1591
- disposed = false;
1592
- cachedGrant = null;
1593
- cachedParticipants = null;
1594
- cachedTelephonyLegs = null;
1595
- cachedRecordings = null;
1596
- cachedDispatches = null;
1597
- constructor(opts) {
1598
- this.vendor = opts.vendor;
1599
- this.logger = opts.logger.child({ session: opts.view.id });
1600
- this.view = opts.view;
1601
- this.selfParticipantId = opts.selfParticipantId;
1602
- this.commands = opts.commands;
1603
- this.media = new MediaRoomAdapter(this.logger);
1604
- this.ctx = {
1605
- session: this,
1606
- media: this.media,
1607
- applyView: (view) => this.applyView(view)
1608
- };
1609
- this.wireMedia();
1610
- }
1611
- get id() {
1612
- return this.view.id;
1613
- }
1614
- get callId() {
1615
- return this.view.callId;
1616
- }
1617
- get vendorId() {
1618
- return this.view.vendorId;
1619
- }
1620
- get vendorSlug() {
1621
- return this.vendor;
1622
- }
1623
- get mediaType() {
1624
- return this.view.mediaType;
1625
- }
1626
- get state() {
1627
- return this.view.state;
1628
- }
1629
- get version() {
1630
- return this.view.version;
1631
- }
1632
- get route() {
1633
- return this.view.route;
1634
- }
1635
- get viewSnapshot() {
1636
- return this.view;
1637
- }
1638
- get mediaStatusSnapshot() {
1639
- return this.mediaStatus;
1640
- }
1641
- get mediaRoom() {
1642
- return this.media.getRoom();
1643
- }
1644
- get participants() {
1645
- if (this.cachedParticipants === null) {
1646
- const lookup = this.media.listSnapshots();
1647
- this.cachedParticipants = this.view.participants.map(
1648
- (p) => participantFromView(p, this.selfParticipantId, lookup)
1649
- );
1650
- }
1651
- return this.cachedParticipants;
1652
- }
1653
- get telephonyLegs() {
1654
- if (this.cachedTelephonyLegs === null) {
1655
- this.cachedTelephonyLegs = collectSipLegs(this.view.participants);
1656
- }
1657
- return this.cachedTelephonyLegs;
1658
- }
1659
- get dispatches() {
1660
- if (this.cachedDispatches === null) {
1661
- this.cachedDispatches = this.view.dispatches.map(dispatchFromView);
1662
- }
1663
- return this.cachedDispatches;
1664
- }
1665
- get recordings() {
1666
- if (this.cachedRecordings === null) {
1667
- this.cachedRecordings = this.view.recordings.map(recordingFromView);
1668
- }
1669
- return this.cachedRecordings;
1670
- }
1671
- get activeRecording() {
1672
- const active = this.view.recordings.find(
1673
- (r) => r.state === "requested" || r.state === "active"
1674
- );
1675
- return active ? recordingFromView(active) : null;
1676
- }
1677
- get self() {
1678
- if (this.selfParticipantId === null) {
1679
- return null;
1680
- }
1681
- return this.participants.find((p) => p.id === this.selfParticipantId) ?? null;
1682
- }
1683
- capabilitiesOf(participantId) {
1684
- const id = participantId ?? this.selfParticipantId;
1685
- if (id === null || id === void 0) {
1686
- return [];
1687
- }
1688
- const participant = this.view.participants.find((p) => p.id === id);
1689
- return participant?.capabilities ?? [];
1690
- }
1691
- on(type, handler) {
1692
- return this.bus.on(type, handler);
1693
- }
1694
- applyView(next) {
1695
- const previous = this.view;
1696
- if (!shouldApplyUpsert(previous, next)) {
1697
- return;
1698
- }
1699
- this.view = next;
1700
- this.invalidateCaches();
1701
- this.bus.emit("view.changed", { view: next, previous });
1702
- if (next.state !== previous.state) {
1703
- this.bus.emit("state.changed", {
1704
- state: next.state,
1705
- previous: previous.state
1706
- });
1707
- }
1708
- this.diffParticipants(previous, next);
1709
- this.diffTelephonyLegs(previous, next);
1710
- this.diffDispatches(previous, next);
1711
- this.diffRecordings(previous, next);
1712
- if (previous.state !== "on_hold" && next.state === "on_hold") {
1713
- this.bus.emit("session.held", { heldAt: (/* @__PURE__ */ new Date()).toISOString() });
1714
- }
1715
- if (previous.state === "on_hold" && next.state !== "on_hold" && next.state !== "ended") {
1716
- this.bus.emit("session.unheld", { unheldAt: (/* @__PURE__ */ new Date()).toISOString() });
1717
- }
1718
- if (previous.state !== "ended" && next.state === "ended") {
1719
- this.bus.emit("session.ended", {
1720
- reason: next.endReason ?? "completed",
1721
- endedAt: next.endedAt ?? (/* @__PURE__ */ new Date()).toISOString()
1722
- });
1723
- }
1724
- }
1725
- async dispatch(name, ...rest) {
1726
- if (this.disposed) {
1727
- return err(callpadError.disposed());
1728
- }
1729
- const cmd = this.commands.get(name);
1730
- if (!cmd) {
1731
- return err(
1732
- callpadError.notfound(
1733
- "session.unknown_command",
1734
- `Unknown session command: ${String(name)}`
1735
- )
1736
- );
1737
- }
1738
- return cmd.execute(this.ctx, rest[0]);
1739
- }
1740
- async join() {
1741
- if (this.disposed) {
1742
- return err(callpadError.disposed());
1743
- }
1744
- if (this.mediaStatus === "connected") {
1745
- return ok(void 0);
1746
- }
1747
- if (this.isEnded()) {
1748
- return ok(void 0);
1749
- }
1750
- const grant = await this.requestGrant();
1751
- if (!grant.ok) {
1752
- return grant;
1753
- }
1754
- return this.joinWithGrant(grant.value);
1755
- }
1756
- async joinWithGrant(grant) {
1757
- if (this.disposed) {
1758
- return err(callpadError.disposed());
1759
- }
1760
- if (this.mediaStatus === "connected") {
1761
- return ok(void 0);
1762
- }
1763
- if (this.isEnded()) {
1764
- return ok(void 0);
1765
- }
1766
- const previousSelfId = this.selfParticipantId;
1767
- this.cachedGrant = grant;
1768
- this.selfParticipantId = grant.participantId;
1769
- this.cachedParticipants = null;
1770
- const connected = await this.media.connect({
1771
- url: grant.url,
1772
- token: grant.token,
1773
- video: this.view.mediaType === "video"
1774
- });
1775
- if (!connected.ok) {
1776
- this.cachedGrant = null;
1777
- this.selfParticipantId = previousSelfId;
1778
- this.cachedParticipants = null;
1779
- if (this.isEnded()) {
1780
- return ok(void 0);
1781
- }
1782
- }
1783
- return connected;
1784
- }
1785
- isEnded() {
1786
- return this.view.state === "ended";
1787
- }
1788
- leave() {
1789
- return this.dispatch("leave");
1790
- }
1791
- cancel() {
1792
- return this.dispatch("cancel");
1793
- }
1794
- end() {
1795
- return this.dispatch("end");
1796
- }
1797
- hold() {
1798
- return this.dispatch("hold");
1799
- }
1800
- unhold() {
1801
- return this.dispatch("unhold");
1802
- }
1803
- startRecording() {
1804
- return this.dispatch("startRecording");
1805
- }
1806
- stopRecording(recordingId) {
1807
- return this.dispatch("stopRecording", recordingId);
1808
- }
1809
- invite(target2) {
1810
- return this.dispatch("invite", target2);
1811
- }
1812
- removeParticipant(participantId) {
1813
- return this.dispatch("removeParticipant", participantId);
1814
- }
1815
- updateName(name) {
1816
- return this.dispatch("updateName", name);
1817
- }
1818
- mediaGrant() {
1819
- return this.cachedGrant;
1820
- }
1821
- async dispose() {
1822
- if (this.disposed) {
1823
- return;
1824
- }
1825
- this.disposed = true;
1826
- await this.media.dispose();
1827
- this.bus.clear();
1828
- }
1829
- async requestGrant() {
1830
- return apiCall(
1831
- () => postApiV1SessionsBySessionIdJoin({
1832
- path: { sessionId: this.id },
1833
- query: { vendor: this.vendor }
1834
- })
1835
- );
1836
- }
1837
- invalidateCaches() {
1838
- this.cachedParticipants = null;
1839
- this.cachedTelephonyLegs = null;
1840
- this.cachedRecordings = null;
1841
- this.cachedDispatches = null;
1842
- }
1843
- wireMedia() {
1844
- this.media.on("state.changed", ({ state }) => {
1845
- this.mediaStatus = state;
1846
- this.bus.emit("media.status.changed", { status: state });
1847
- });
1848
- this.media.on("participant.changed", ({ identity }) => {
1849
- this.cachedParticipants = null;
1850
- const view = this.view.participants.find(
1851
- (p2) => p2.participantIdentity === identity
1852
- );
1853
- if (!view) {
1854
- return;
1855
- }
1856
- const p = participantFromView(
1857
- view,
1858
- this.selfParticipantId,
1859
- this.media.listSnapshots()
1860
- );
1861
- this.bus.emit("participant.updated", { participant: p });
1862
- });
1863
- this.media.on("error", ({ error }) => {
1864
- this.bus.emit("error", { error });
1865
- });
1866
- }
1867
- diffParticipants(prev, next) {
1868
- const byId = new Map(prev.participants.map((p) => [p.id, p]));
1869
- const lookup = this.media.listSnapshots();
1870
- const prevJoinedCount = prev.participants.filter(
1871
- (p) => p.state === "joined"
1872
- ).length;
1873
- for (const current of next.participants) {
1874
- const before = byId.get(current.id);
1875
- const participant = participantFromView(
1876
- current,
1877
- this.selfParticipantId,
1878
- lookup
1879
- );
1880
- if (!before) {
1881
- this.bus.emit("participant.invited", { participant });
1882
- continue;
1883
- }
1884
- if (before.state === current.state) {
1885
- continue;
1886
- }
1887
- switch (current.state) {
1888
- case "accepted":
1889
- this.bus.emit("participant.accepted", { participant });
1890
- break;
1891
- case "joined": {
1892
- const isFirstJoin = prevJoinedCount === 0;
1893
- this.bus.emit("participant.joined", { participant, isFirstJoin });
1894
- break;
1895
- }
1896
- case "left": {
1897
- const reason = nullify(current.leftReason) ?? "left";
1898
- this.bus.emit("participant.left", { participant, reason });
1899
- break;
1900
- }
1901
- case "declined":
1902
- this.bus.emit("participant.declined", { participant });
1903
- break;
1904
- case "withdrawn":
1905
- this.bus.emit("participant.withdrawn", { participant });
1906
- break;
1907
- case "removed":
1908
- this.bus.emit("participant.removed", { participant });
1909
- break;
1910
- case "failed": {
1911
- const reason = nullify(current.failureReason) ?? "unknown";
1912
- const failedSipLeg = current.operations.find(
1913
- (op) => op.kind === "sip_leg" && op.state === "failed"
1914
- );
1915
- this.bus.emit("participant.failed", {
1916
- participant,
1917
- reason,
1918
- legFailureReason: nullify(failedSipLeg?.failureReason ?? null),
1919
- sipStatusCode: nullify(failedSipLeg?.sipStatusCode ?? null)
1920
- });
1921
- break;
1922
- }
1923
- default:
1924
- this.bus.emit("participant.updated", { participant });
1925
- }
1926
- }
1927
- }
1928
- diffTelephonyLegs(prev, next) {
1929
- const beforeState = /* @__PURE__ */ new Map();
1930
- for (const p of prev.participants) {
1931
- for (const op of p.operations) {
1932
- if (op.kind === "sip_leg") {
1933
- beforeState.set(op.id, op.state);
1934
- }
1935
- }
1936
- }
1937
- for (const participant of next.participants) {
1938
- for (const op of participant.operations) {
1939
- if (op.kind !== "sip_leg") {
1940
- continue;
1941
- }
1942
- const beforeOp = beforeState.get(op.id);
1943
- if (beforeOp === op.state) {
1944
- continue;
1945
- }
1946
- this.bus.emit("telephony_leg.updated", {
1947
- leg: telephonyLegFromOperation(participant, op)
1948
- });
1949
- }
1950
- }
1951
- }
1952
- diffDispatches(prev, next) {
1953
- const byId = new Map(prev.dispatches.map((d) => [d.id, d]));
1954
- for (const current of next.dispatches) {
1955
- const before = byId.get(current.id);
1956
- const dispatch = dispatchFromView(current);
1957
- if (!before) {
1958
- if (current.state === "active") {
1959
- this.bus.emit("dispatch.started", { dispatch });
1960
- }
1961
- continue;
1962
- }
1963
- if (before.state === current.state) {
1964
- continue;
1965
- }
1966
- switch (current.state) {
1967
- case "resolved":
1968
- this.bus.emit("dispatch.resolved", { dispatch });
1969
- break;
1970
- case "cancelled":
1971
- this.bus.emit("dispatch.cancelled", { dispatch });
1972
- break;
1973
- case "expired":
1974
- this.bus.emit("dispatch.expired", { dispatch });
1975
- break;
1976
- }
1977
- }
1978
- }
1979
- diffRecordings(prev, next) {
1980
- const byId = new Map(prev.recordings.map((r) => [r.id, r]));
1981
- for (const current of next.recordings) {
1982
- const before = byId.get(current.id);
1983
- const recording = recordingFromView(current);
1984
- if (!before) {
1985
- this.bus.emit("recording.requested", { recording });
1986
- continue;
1987
- }
1988
- if (before.state === current.state) {
1989
- continue;
1990
- }
1991
- if (current.state === "active") {
1992
- this.bus.emit("recording.started", { recording });
1993
- continue;
1994
- }
1995
- if (current.state === "stopped") {
1996
- this.bus.emit("recording.stopped", { recording });
1997
- continue;
1998
- }
1999
- if (current.state === "failed") {
2000
- this.bus.emit("recording.failed", {
2001
- recording,
2002
- reason: nullify(current.failureReason)
2003
- });
2004
- }
2005
- }
2006
- }
2007
- };
2008
-
2009
- // src/core/session/commands.ts
2010
- function target(ctx) {
2011
- return {
2012
- path: { sessionId: ctx.session.id },
2013
- query: { vendor: ctx.session.vendorSlug }
2014
- };
2015
- }
2016
- var holdCommand = {
2017
- name: "hold",
2018
- async execute(ctx) {
2019
- const res = await apiCall(
2020
- () => postApiV1SessionsBySessionIdHold(target(ctx))
2021
- );
2022
- if (!res.ok) {
2023
- return res;
2024
- }
2025
- ctx.applyView(res.value);
2026
- return ok(void 0);
2027
- }
2028
- };
2029
- var unholdCommand = {
2030
- name: "unhold",
2031
- async execute(ctx) {
2032
- const res = await apiCall(
2033
- () => postApiV1SessionsBySessionIdUnhold(target(ctx))
2034
- );
2035
- if (!res.ok) {
2036
- return res;
2037
- }
2038
- ctx.applyView(res.value);
2039
- return ok(void 0);
2040
- }
2041
- };
2042
- var cancelCommand = {
2043
- name: "cancel",
2044
- async execute(ctx) {
2045
- const res = await apiCall(
2046
- () => postApiV1SessionsBySessionIdCancel(target(ctx))
2047
- );
2048
- if (!res.ok) {
2049
- return res;
2050
- }
2051
- ctx.applyView(res.value);
2052
- return ok(void 0);
2053
- }
2054
- };
2055
- var endCommand = {
2056
- name: "end",
2057
- async execute(ctx) {
2058
- const res = await apiCall(
2059
- () => postApiV1SessionsBySessionIdEnd(target(ctx))
2060
- );
2061
- if (!res.ok) {
2062
- return res;
2063
- }
2064
- ctx.applyView(res.value);
2065
- return ok(void 0);
2066
- }
2067
- };
2068
- var leaveCommand = {
2069
- name: "leave",
2070
- async execute(ctx) {
2071
- if (ctx.session.state === "ended") {
2072
- await ctx.media.disconnect();
2073
- return ok(void 0);
2074
- }
2075
- const res = await apiCall(
2076
- () => postApiV1SessionsBySessionIdLeave(target(ctx))
2077
- );
2078
- await ctx.media.disconnect();
2079
- if (!res.ok) {
2080
- return res;
2081
- }
2082
- ctx.applyView(res.value);
2083
- return ok(void 0);
2084
- }
2085
- };
2086
- var inviteCommand = {
2087
- name: "invite",
2088
- async execute(ctx, invited) {
2089
- const body = { target: invited };
2090
- const res = await apiCall(
2091
- () => postApiV1SessionsBySessionIdParticipants({
2092
- ...target(ctx),
2093
- body
2094
- })
2095
- );
2096
- if (!res.ok) {
2097
- return res;
2098
- }
2099
- ctx.applyView(res.value);
2100
- return ok(void 0);
2101
- }
2102
- };
2103
- var removeParticipantCommand = {
2104
- name: "removeParticipant",
2105
- async execute(ctx, participantId) {
2106
- const res = await apiCall(
2107
- () => deleteApiV1SessionsBySessionIdParticipantsByParticipantId({
2108
- path: { sessionId: ctx.session.id, participantId },
2109
- query: { vendor: ctx.session.vendorSlug }
2110
- })
2111
- );
2112
- if (!res.ok) {
2113
- return res;
2114
- }
2115
- ctx.applyView(res.value);
2116
- return ok(void 0);
2117
- }
2118
- };
2119
- var updateNameCommand = {
2120
- name: "updateName",
2121
- async execute(ctx, name) {
2122
- const res = await apiCall(
2123
- () => patchApiV1SessionsBySessionId({
2124
- ...target(ctx),
2125
- body: { name }
2126
- })
2127
- );
2128
- if (!res.ok) {
2129
- return res;
2130
- }
2131
- ctx.applyView(res.value);
2132
- return ok(void 0);
2133
- }
2134
- };
2135
- var startRecordingCommand = {
2136
- name: "startRecording",
2137
- async execute(ctx) {
2138
- const res = await apiCall(
2139
- () => postApiV1SessionsBySessionIdRecordings(target(ctx))
2140
- );
2141
- if (!res.ok) {
2142
- return res;
2143
- }
2144
- return ok(void 0);
2145
- }
2146
- };
2147
- var stopRecordingCommand = {
2148
- name: "stopRecording",
2149
- async execute(ctx, recordingId) {
2150
- const res = await apiCall(
2151
- () => postApiV1SessionsBySessionIdRecordingsByRecordingIdStop({
2152
- path: { sessionId: ctx.session.id, recordingId },
2153
- query: { vendor: ctx.session.vendorSlug }
2154
- })
2155
- );
2156
- if (!res.ok) {
2157
- return res;
2158
- }
2159
- return ok(void 0);
2160
- }
2161
- };
2162
-
2163
- // src/core/managers/session-manager.ts
2164
- var ACTIVE_STATES = /* @__PURE__ */ new Set(["ringing", "active", "on_hold"]);
2165
- var SessionManager = class {
2166
- sessions = /* @__PURE__ */ new Map();
2167
- vendor;
2168
- logger;
2169
- pulse;
2170
- commands;
2171
- selfParticipantId;
2172
- bus = new EventBus();
2173
- unsubs = [];
2174
- cachedList = null;
2175
- disposed = false;
2176
- constructor(opts) {
2177
- this.vendor = opts.vendor;
2178
- this.logger = opts.logger.child({ component: "session-manager" });
2179
- this.pulse = opts.pulse;
2180
- this.selfParticipantId = opts.selfParticipantId;
2181
- this.commands = opts.commands;
2182
- this.unsubs.push(
2183
- this.pulse.on("session.upsert", (view) => this.onUpsert(view)),
2184
- this.pulse.on(
2185
- "session.remove",
2186
- ({ sessionId, version }) => this.onRemove(sessionId, version)
2187
- )
2188
- );
2189
- }
2190
- list() {
2191
- if (this.cachedList === null) {
2192
- this.cachedList = Array.from(this.sessions.values());
2193
- }
2194
- return this.cachedList;
2195
- }
2196
- get(id) {
2197
- return this.sessions.get(id) ?? null;
2198
- }
2199
- active() {
2200
- for (const session of this.sessions.values()) {
2201
- if (ACTIVE_STATES.has(session.state)) {
2202
- return session;
988
+ };
989
+ return {
990
+ connect: async () => {
991
+ if (rt) {
992
+ return;
2203
993
  }
994
+ disposed = false;
995
+ setStatus("connecting");
996
+ try {
997
+ const first = await fetchToken();
998
+ rt = new Centrifuge(first.url, {
999
+ token: first.token,
1000
+ getToken: refreshToken
1001
+ });
1002
+ rt.on("connecting", () => {
1003
+ const next = store.getStatus() === "ready" ? "reconnecting" : "connecting";
1004
+ setStatus(next);
1005
+ });
1006
+ rt.on("connected", () => {
1007
+ setStatus("ready");
1008
+ });
1009
+ rt.on("disconnected", () => {
1010
+ if (!disposed) {
1011
+ setStatus("offline");
1012
+ }
1013
+ });
1014
+ rt.on("error", (ctx) => {
1015
+ emitter.emit("error", new Error(ctx.error.message));
1016
+ });
1017
+ rt.on("publication", (ctx) => {
1018
+ try {
1019
+ const message = parseMessage(ctx.data);
1020
+ if (message) {
1021
+ applyMessage(message);
1022
+ }
1023
+ } catch (err) {
1024
+ emitter.emit("error", err instanceof Error ? err : new Error("Realtime event failed"));
1025
+ }
1026
+ });
1027
+ rt.connect();
1028
+ await rt.ready();
1029
+ } catch (err) {
1030
+ await disconnect();
1031
+ throw err;
1032
+ }
1033
+ },
1034
+ disconnect,
1035
+ dispose: async () => {
1036
+ disposed = true;
1037
+ rt?.disconnect();
1038
+ rt = null;
1039
+ setStatus("disposed");
2204
1040
  }
1041
+ };
1042
+ };
1043
+ var parseMessage = (value) => {
1044
+ if (!value || typeof value !== "object") {
2205
1045
  return null;
2206
1046
  }
2207
- activeId() {
2208
- return this.active()?.id ?? null;
2209
- }
2210
- on(type, handler) {
2211
- return this.bus.on(type, handler);
2212
- }
2213
- async prime() {
2214
- if (this.disposed) {
2215
- return err(callpadError.disposed());
2216
- }
2217
- const res = await apiCall(
2218
- () => getApiV1Sessions({ query: { vendor: this.vendor, limit: 50 } })
2219
- );
2220
- if (!res.ok) {
2221
- return res;
2222
- }
2223
- for (const view of res.value.items) {
2224
- this.onUpsert(view);
2225
- }
2226
- return ok(void 0);
2227
- }
2228
- async create(body) {
2229
- if (this.disposed) {
2230
- return err(callpadError.disposed());
2231
- }
2232
- const res = await apiCall(
2233
- () => postApiV1Sessions({
2234
- query: { vendor: this.vendor },
2235
- body
2236
- })
2237
- );
2238
- if (!res.ok) {
2239
- return res;
2240
- }
2241
- const session = this.onUpsert(res.value);
2242
- return ok(session);
2243
- }
2244
- async fetchById(id) {
2245
- if (this.disposed) {
2246
- return err(callpadError.disposed());
2247
- }
2248
- const res = await apiCall(
2249
- () => getApiV1SessionsBySessionId({
2250
- path: { sessionId: id },
2251
- query: { vendor: this.vendor }
2252
- })
2253
- );
2254
- if (!res.ok) {
2255
- return res;
2256
- }
2257
- const session = this.onUpsert(res.value);
2258
- return ok(session);
2259
- }
2260
- onAcceptedInvite(view) {
2261
- return this.onUpsert(view);
2262
- }
2263
- onUpsert(view) {
2264
- const existing = this.sessions.get(view.id);
2265
- if (existing) {
2266
- if (shouldApplyUpsert(existing, view)) {
2267
- existing.applyView(view);
2268
- }
2269
- return existing;
2270
- }
2271
- const session = new Session({
2272
- vendor: this.vendor,
2273
- view,
2274
- logger: this.logger,
2275
- selfParticipantId: this.selfParticipantId,
2276
- commands: this.commands
2277
- });
2278
- this.sessions.set(view.id, session);
2279
- this.cachedList = null;
2280
- this.bus.emit("session.added", { session });
2281
- return session;
2282
- }
2283
- onRemove(sessionId, _version) {
2284
- const session = this.sessions.get(sessionId);
2285
- if (!session) {
2286
- return;
2287
- }
2288
- this.sessions.delete(sessionId);
2289
- this.cachedList = null;
2290
- this.bus.emit("session.removed", { sessionId });
2291
- void session.dispose();
1047
+ const message = value;
1048
+ if (message.event === "session.upsert" && message.data) {
1049
+ return message;
2292
1050
  }
2293
- async dispose() {
2294
- if (this.disposed) {
2295
- return;
2296
- }
2297
- this.disposed = true;
2298
- for (const unsub of this.unsubs) {
2299
- unsub();
2300
- }
2301
- await Promise.all(
2302
- Array.from(this.sessions.values(), (session) => session.dispose())
2303
- );
2304
- this.sessions.clear();
2305
- this.bus.clear();
1051
+ if (message.event === "session.remove" && message.data) {
1052
+ return message;
2306
1053
  }
1054
+ return null;
2307
1055
  };
2308
1056
 
2309
- // src/core/invite.ts
2310
- var Invite = class {
2311
- view;
2312
- vendor;
2313
- onAccept;
2314
- disposed = false;
2315
- constructor(view, vendor, onAccept) {
2316
- this.view = view;
2317
- this.vendor = vendor;
2318
- this.onAccept = onAccept;
2319
- }
2320
- get id() {
2321
- return this.view.id;
2322
- }
2323
- get sessionId() {
2324
- return this.view.sessionId;
2325
- }
2326
- get callId() {
2327
- return this.view.callId;
2328
- }
2329
- get state() {
2330
- return this.view.state;
2331
- }
2332
- get createdAt() {
2333
- return this.view.createdAt;
2334
- }
2335
- get expiresAt() {
2336
- return nullify(this.view.expiresAt);
2337
- }
2338
- get completedAt() {
2339
- return nullify(this.view.completedAt);
2340
- }
2341
- get dispatchId() {
2342
- return nullify(this.view.dispatchId);
2343
- }
2344
- get recipient() {
2345
- return this.view.recipient;
2346
- }
2347
- get participantId() {
2348
- return this.view.participantId;
2349
- }
2350
- get inviterParticipantId() {
2351
- return nullify(this.view.inviterParticipantId);
2352
- }
2353
- get session() {
2354
- return this.view.session;
2355
- }
2356
- applyView(next) {
2357
- this.view = next;
2358
- }
2359
- async accept() {
2360
- if (this.disposed) {
2361
- return err(callpadError.disposed());
2362
- }
2363
- const res = await apiCall(
2364
- () => postApiV1SessionsBySessionIdInvitesByInviteIdAccept({
2365
- path: { sessionId: this.sessionId, inviteId: this.id },
2366
- query: { vendor: this.vendor }
2367
- })
2368
- );
2369
- if (!res.ok) {
2370
- return res;
2371
- }
2372
- return this.onAccept(res.value);
2373
- }
2374
- async reject() {
2375
- if (this.disposed) {
2376
- return err(callpadError.disposed());
2377
- }
2378
- const res = await apiCall(
2379
- () => postApiV1SessionsBySessionIdInvitesByInviteIdReject({
2380
- path: { sessionId: this.sessionId, inviteId: this.id },
2381
- query: { vendor: this.vendor }
2382
- })
2383
- );
2384
- if (!res.ok) {
2385
- return res;
1057
+ // src/core/store.ts
1058
+ var createSessionStore = (userId) => {
1059
+ const sessions = /* @__PURE__ */ new Map();
1060
+ const subscribers = /* @__PURE__ */ new Map();
1061
+ let status = "idle";
1062
+ let activeId = null;
1063
+ let listCache = [];
1064
+ let invitesCache = [];
1065
+ let inviteKey = "";
1066
+ let inviteExpiryTimer = null;
1067
+ const emit = (topic) => {
1068
+ const fns = subscribers.get(topic);
1069
+ if (!fns) {
1070
+ return;
2386
1071
  }
2387
- return ok(void 0);
2388
- }
2389
- dispose() {
2390
- this.disposed = true;
2391
- }
2392
- };
2393
-
2394
- // src/core/managers/invite-manager.ts
2395
- var InviteManager = class {
2396
- invites = /* @__PURE__ */ new Map();
2397
- sessionIndex = /* @__PURE__ */ new Map();
2398
- vendor;
2399
- pulse;
2400
- onAccept;
2401
- bus = new EventBus();
2402
- unsubs = [];
2403
- cachedList = null;
2404
- disposed = false;
2405
- constructor(opts) {
2406
- this.vendor = opts.vendor;
2407
- this.pulse = opts.pulse;
2408
- this.onAccept = opts.onAccept;
2409
- this.unsubs.push(
2410
- this.pulse.on("session.invite.upsert", (view) => this.onUpsert(view)),
2411
- this.pulse.on(
2412
- "session.invite.remove",
2413
- ({ inviteId }) => this.onRemove(inviteId)
2414
- ),
2415
- this.pulse.on(
2416
- "session.remove",
2417
- ({ sessionId }) => this.dropBySession(sessionId)
2418
- )
2419
- );
2420
- }
2421
- list() {
2422
- if (this.cachedList === null) {
2423
- this.cachedList = Array.from(this.invites.values());
1072
+ for (const fn of fns) {
1073
+ fn();
2424
1074
  }
2425
- return this.cachedList;
2426
- }
2427
- get(id) {
2428
- return this.invites.get(id) ?? null;
2429
- }
2430
- first() {
2431
- const iterator = this.invites.values().next();
2432
- return iterator.done ? null : iterator.value;
2433
- }
2434
- on(type, handler) {
2435
- return this.bus.on(type, handler);
2436
- }
2437
- async prime() {
2438
- if (this.disposed) {
2439
- return err(callpadError.disposed());
1075
+ };
1076
+ const subscribe = (topic, fn) => {
1077
+ let fns = subscribers.get(topic);
1078
+ if (!fns) {
1079
+ fns = /* @__PURE__ */ new Set();
1080
+ subscribers.set(topic, fns);
2440
1081
  }
2441
- const res = await apiCall(
2442
- () => getApiV1SessionsInvites({ query: { vendor: this.vendor } })
1082
+ fns.add(fn);
1083
+ return () => {
1084
+ fns?.delete(fn);
1085
+ if (fns?.size === 0) {
1086
+ subscribers.delete(topic);
1087
+ }
1088
+ };
1089
+ };
1090
+ const refresh = () => {
1091
+ listCache = Array.from(sessions.values()).sort(
1092
+ (a, b) => b.createdAt.localeCompare(a.createdAt)
2443
1093
  );
2444
- if (!res.ok) {
2445
- return res;
2446
- }
2447
- for (const view of res.value) {
2448
- this.onUpsert(view);
2449
- }
2450
- return ok(void 0);
2451
- }
2452
- onUpsert(view) {
2453
- if (view.state !== "pending") {
2454
- this.onRemove(view.id);
2455
- return;
2456
- }
2457
- const existing = this.invites.get(view.id);
2458
- if (existing) {
2459
- if (!shouldApplyUpsert(existing.session, view.session)) {
2460
- return;
1094
+ if (activeId) {
1095
+ const active = sessions.get(activeId);
1096
+ if (!active || active.state === "ended") {
1097
+ activeId = null;
2461
1098
  }
2462
- existing.applyView(view);
2463
- this.bus.emit("invite.updated", { invite: existing });
2464
- return;
2465
- }
2466
- const invite = new Invite(view, this.vendor, this.onAccept);
2467
- this.invites.set(view.id, invite);
2468
- this.trackSession(view.sessionId, view.id);
2469
- this.cachedList = null;
2470
- this.bus.emit("invite.added", { invite });
2471
- }
2472
- onRemove(inviteId) {
2473
- const existing = this.invites.get(inviteId);
2474
- if (!existing) {
2475
- return;
2476
- }
2477
- this.invites.delete(inviteId);
2478
- this.untrackSession(existing.sessionId, inviteId);
2479
- this.cachedList = null;
2480
- this.bus.emit("invite.removed", { inviteId });
2481
- existing.dispose();
2482
- }
2483
- dropBySession(sessionId) {
2484
- const ids = this.sessionIndex.get(sessionId);
2485
- if (!ids) {
2486
- return;
2487
1099
  }
2488
- for (const inviteId of ids) {
2489
- this.onRemove(inviteId);
1100
+ if (!activeId) {
1101
+ activeId = listCache.find((session) => session.state !== "ended")?.id ?? null;
2490
1102
  }
2491
- }
2492
- trackSession(sessionId, inviteId) {
2493
- let set = this.sessionIndex.get(sessionId);
2494
- if (!set) {
2495
- set = /* @__PURE__ */ new Set();
2496
- this.sessionIndex.set(sessionId, set);
2497
- }
2498
- set.add(inviteId);
2499
- }
2500
- untrackSession(sessionId, inviteId) {
2501
- const set = this.sessionIndex.get(sessionId);
2502
- if (!set) {
2503
- return;
1103
+ };
1104
+ const refreshInvites = () => {
1105
+ const now = Date.now();
1106
+ const invites = listCache.map((session) => inviteFrom(session, userId, now)).filter((invite) => invite !== null);
1107
+ scheduleInviteExpiry(invites, now);
1108
+ const nextKey = invites.map(
1109
+ (invite) => `${invite.session.id}:${invite.participant.id}:${invite.participant.state}:${invite.participant.inviteExpiresAt ?? ""}`
1110
+ ).join("|");
1111
+ if (nextKey === inviteKey) {
1112
+ invitesCache = invites;
1113
+ return false;
2504
1114
  }
2505
- set.delete(inviteId);
2506
- if (set.size === 0) {
2507
- this.sessionIndex.delete(sessionId);
1115
+ inviteKey = nextKey;
1116
+ invitesCache = invites;
1117
+ return true;
1118
+ };
1119
+ const clearInviteExpiryTimer = () => {
1120
+ if (inviteExpiryTimer) {
1121
+ globalThis.clearTimeout(inviteExpiryTimer);
1122
+ inviteExpiryTimer = null;
2508
1123
  }
2509
- }
2510
- async dispose() {
2511
- if (this.disposed) {
1124
+ };
1125
+ const scheduleInviteExpiry = (invites, now) => {
1126
+ clearInviteExpiryTimer();
1127
+ const nextExpiry = invites.reduce((min, invite) => {
1128
+ const expiresAt = inviteExpiryMs(invite.participant);
1129
+ if (expiresAt === null) {
1130
+ return min;
1131
+ }
1132
+ return min === null ? expiresAt : Math.min(min, expiresAt);
1133
+ }, null);
1134
+ if (nextExpiry === null) {
2512
1135
  return;
2513
1136
  }
2514
- this.disposed = true;
2515
- for (const unsub of this.unsubs) {
2516
- unsub();
2517
- }
2518
- for (const invite of this.invites.values()) {
2519
- invite.dispose();
1137
+ inviteExpiryTimer = globalThis.setTimeout(
1138
+ () => {
1139
+ inviteExpiryTimer = null;
1140
+ if (refreshInvites()) {
1141
+ emit("invites");
1142
+ }
1143
+ },
1144
+ Math.max(0, nextExpiry - now)
1145
+ );
1146
+ };
1147
+ const notifySessionChange = (sessionId) => {
1148
+ refresh();
1149
+ const invitesChanged = refreshInvites();
1150
+ emit(`session:${sessionId}`);
1151
+ emit("sessions");
1152
+ if (invitesChanged) {
1153
+ emit("invites");
2520
1154
  }
2521
- this.invites.clear();
2522
- this.sessionIndex.clear();
2523
- this.bus.clear();
2524
- }
2525
- };
2526
-
2527
- // src/core/presence.ts
2528
- function presenceFromItem(item) {
2529
- return {
2530
- id: item.id,
2531
- availability: nullify(item.availability),
2532
- connectivity: nullify(item.connectivity),
2533
- lastSeenAt: nullify(item.lastSeenAt)
2534
1155
  };
2535
- }
2536
-
2537
- // src/core/managers/presence-manager.ts
2538
- var PresenceManager = class {
2539
- vendor;
2540
- bus = new EventBus();
2541
- disposed = false;
2542
- constructor(opts) {
2543
- this.vendor = opts.vendor;
2544
- }
2545
- on(type, handler) {
2546
- return this.bus.on(type, handler);
2547
- }
2548
- async get(refs) {
2549
- if (this.disposed) {
2550
- return err(callpadError.disposed());
1156
+ const get = (sessionId) => {
1157
+ if (sessionId) {
1158
+ return sessions.get(sessionId) ?? null;
2551
1159
  }
2552
- if (refs.length === 0) {
2553
- return ok(/* @__PURE__ */ new Map());
1160
+ if (!activeId) {
1161
+ return null;
2554
1162
  }
2555
- const byKind = /* @__PURE__ */ new Map();
2556
- for (const ref of refs) {
2557
- const list = byKind.get(ref.kind) ?? [];
2558
- list.push(ref.id);
2559
- byKind.set(ref.kind, list);
1163
+ return sessions.get(activeId) ?? null;
1164
+ };
1165
+ const upsert = (session, mode = "event") => {
1166
+ const prev = sessions.get(session.id);
1167
+ const stale = prev ? mode === "command" ? session.version < prev.version : session.version <= prev.version : false;
1168
+ if (stale) {
1169
+ return null;
2560
1170
  }
2561
- const responses = await Promise.all(
2562
- Array.from(byKind.entries()).map(
2563
- ([kind, ids]) => apiCall(
2564
- () => getApiV1VendorsBySlugPresence({
2565
- path: { slug: this.vendor },
2566
- query: { type: kind, ids: ids.join(",") }
2567
- })
2568
- )
2569
- )
2570
- );
2571
- const result = /* @__PURE__ */ new Map();
2572
- for (const res of responses) {
2573
- if (!res.ok) {
2574
- return res;
1171
+ sessions.set(session.id, session);
1172
+ notifySessionChange(session.id);
1173
+ return prev ? "updated" : "added";
1174
+ };
1175
+ return {
1176
+ getStatus: () => status,
1177
+ setStatus: (next) => {
1178
+ if (status === next) {
1179
+ return;
2575
1180
  }
2576
- for (const item of res.value.items) {
2577
- const snapshot = presenceFromItem(item);
2578
- result.set(snapshot.id, snapshot);
2579
- this.bus.emit("presence.updated", { snapshot });
1181
+ status = next;
1182
+ emit("status");
1183
+ },
1184
+ list: () => listCache,
1185
+ get,
1186
+ invites: () => invitesCache,
1187
+ invite: (sessionId) => invitesCache.find((invite) => invite.session.id === sessionId) ?? null,
1188
+ sync: (items) => {
1189
+ for (const session of items) {
1190
+ const prev = sessions.get(session.id);
1191
+ if (!prev || session.version >= prev.version) {
1192
+ sessions.set(session.id, session);
1193
+ }
2580
1194
  }
2581
- }
2582
- return ok(result);
2583
- }
2584
- async setMyStatus(mode, note) {
2585
- if (this.disposed) {
2586
- return err(callpadError.disposed());
2587
- }
2588
- const res = await apiCall(
2589
- () => putApiV1VendorsBySlugPresenceStatus({
2590
- path: { slug: this.vendor },
2591
- body: { status: mode, note: note ?? void 0 }
2592
- })
2593
- );
2594
- if (!res.ok) {
2595
- return res;
2596
- }
2597
- return ok(void 0);
2598
- }
2599
- async clearMyStatus() {
2600
- if (this.disposed) {
2601
- return err(callpadError.disposed());
2602
- }
2603
- const res = await apiCall(
2604
- () => deleteApiV1VendorsBySlugPresenceStatus({
2605
- path: { slug: this.vendor }
2606
- })
2607
- );
2608
- if (!res.ok) {
2609
- return res;
2610
- }
2611
- return ok(void 0);
2612
- }
2613
- async dispose() {
2614
- this.disposed = true;
2615
- this.bus.clear();
1195
+ refresh();
1196
+ const invitesChanged = refreshInvites();
1197
+ emit("sessions");
1198
+ for (const session of items) {
1199
+ emit(`session:${session.id}`);
1200
+ }
1201
+ if (invitesChanged) {
1202
+ emit("invites");
1203
+ }
1204
+ },
1205
+ upsert,
1206
+ remove: (payload) => {
1207
+ const prev = sessions.get(payload.id);
1208
+ if (!prev) {
1209
+ return false;
1210
+ }
1211
+ if (payload.version !== void 0 && payload.version < prev.version) {
1212
+ return false;
1213
+ }
1214
+ sessions.delete(payload.id);
1215
+ notifySessionChange(payload.id);
1216
+ return true;
1217
+ },
1218
+ patchRecording: (control) => {
1219
+ const prev = sessions.get(control.sessionId);
1220
+ if (!prev || control.sessionVersion < prev.version) {
1221
+ return null;
1222
+ }
1223
+ const next = {
1224
+ ...prev,
1225
+ version: Math.max(prev.version, control.sessionVersion),
1226
+ recording: control.recording
1227
+ };
1228
+ sessions.set(next.id, next);
1229
+ notifySessionChange(next.id);
1230
+ return next;
1231
+ },
1232
+ can: (session, action) => can(session, userId, action),
1233
+ dispose: () => {
1234
+ clearInviteExpiryTimer();
1235
+ subscribers.clear();
1236
+ },
1237
+ subscribe
1238
+ };
1239
+ };
1240
+ var selfParticipant = (session, userId) => session.participants.find((participant) => participant.userId === userId) ?? null;
1241
+ var inviteFrom = (session, userId, now) => {
1242
+ const participant = selfParticipant(session, userId);
1243
+ if (!participant || session.state === "ended" || !isActiveInvite(participant, now)) {
1244
+ return null;
2616
1245
  }
1246
+ return { session, participant };
2617
1247
  };
2618
-
2619
- // src/core/plugin.ts
2620
- var PluginHost = class {
2621
- handles = [];
2622
- async register(plugin, ctx) {
2623
- const handle = await plugin.install(ctx);
2624
- this.handles.push(handle);
1248
+ var can = (session, userId, action) => {
1249
+ const self = selfParticipant(session, userId);
1250
+ if (!self || session.state === "ended") {
1251
+ return false;
2625
1252
  }
2626
- async disposeAll() {
2627
- while (this.handles.length > 0) {
2628
- const h = this.handles.pop();
2629
- if (h) {
2630
- try {
2631
- await h.dispose();
2632
- } catch {
2633
- }
2634
- }
2635
- }
1253
+ switch (action) {
1254
+ case "accept":
1255
+ case "reject":
1256
+ return isActiveInvite(self);
1257
+ case "join":
1258
+ return self.transport === "webrtc" && (self.state === "accepted" || self.state === "joined");
1259
+ case "leave":
1260
+ return self.state === "accepted" || self.state === "joined";
1261
+ case "end":
1262
+ case "addParticipant":
1263
+ case "removeParticipant":
1264
+ return isHost(self) && (self.state === "accepted" || self.state === "joined");
1265
+ case "startRecording":
1266
+ return isHost(self) && session.state === "active" && session.recording.status === "off";
1267
+ case "stopRecording":
1268
+ return isHost(self) && session.recording.status === "recording";
1269
+ }
1270
+ };
1271
+ var isActiveInvite = (participant, now = Date.now()) => {
1272
+ const expiresAt = inviteExpiryMs(participant);
1273
+ return participant.state === "invited" && expiresAt !== null && expiresAt > now;
1274
+ };
1275
+ var inviteExpiryMs = (participant) => {
1276
+ if (!participant.inviteExpiresAt) {
1277
+ return null;
2636
1278
  }
1279
+ const expiresAt = Date.parse(participant.inviteExpiresAt);
1280
+ return Number.isFinite(expiresAt) ? expiresAt : null;
2637
1281
  };
1282
+ var isHost = (participant) => participant.role === "host" && (participant.kind === "agent" || participant.kind === "vendor_user");
2638
1283
 
2639
1284
  // src/core/client.ts
2640
- var CallpadClient = class {
2641
- config;
2642
- logger;
2643
- bus = new EventBus();
2644
- pluginHost = new PluginHost();
2645
- sessionCommands = /* @__PURE__ */ new Map();
2646
- pulseTransport = null;
2647
- sessions = null;
2648
- invites = null;
2649
- presence = null;
2650
- responseInterceptorId = null;
2651
- status = "idle";
2652
- disposed = false;
2653
- constructor(config) {
2654
- this.config = config;
2655
- this.logger = (config.logger ?? noopLogger).child({
2656
- component: "callpad-client"
2657
- });
2658
- this.registerSessionCommand(holdCommand);
2659
- this.registerSessionCommand(unholdCommand);
2660
- this.registerSessionCommand(cancelCommand);
2661
- this.registerSessionCommand(endCommand);
2662
- this.registerSessionCommand(leaveCommand);
2663
- this.registerSessionCommand(inviteCommand);
2664
- this.registerSessionCommand(removeParticipantCommand);
2665
- this.registerSessionCommand(updateNameCommand);
2666
- this.registerSessionCommand(startRecordingCommand);
2667
- this.registerSessionCommand(stopRecordingCommand);
2668
- }
2669
- registerSessionCommand(cmd) {
2670
- this.sessionCommands.set(
2671
- cmd.name,
2672
- cmd
2673
- );
2674
- }
2675
- getStatus() {
2676
- return this.status;
2677
- }
2678
- getSessions() {
2679
- if (!this.sessions) {
2680
- throw new Error(
2681
- "CallpadClient: sessions not initialized; call connect() first"
2682
- );
2683
- }
2684
- return this.sessions;
2685
- }
2686
- getInvites() {
2687
- if (!this.invites) {
2688
- throw new Error(
2689
- "CallpadClient: invites not initialized; call connect() first"
2690
- );
2691
- }
2692
- return this.invites;
2693
- }
2694
- getPresence() {
2695
- if (!this.presence) {
2696
- throw new Error(
2697
- "CallpadClient: presence not initialized; call connect() first"
2698
- );
2699
- }
2700
- return this.presence;
2701
- }
2702
- on(type, handler) {
2703
- return this.bus.on(type, handler);
2704
- }
2705
- async connect() {
2706
- if (this.disposed) {
2707
- return err(callpadError.disposed());
2708
- }
2709
- if (this.status === "ready" || this.status === "connecting") {
2710
- return ok(void 0);
2711
- }
2712
- this.setStatus("connecting");
2713
- this.configureApiClient();
2714
- const pulse = new PulseTransport({
2715
- grantFetcher: () => this.fetchPulseGrant(),
2716
- logger: this.logger
2717
- });
2718
- this.pulseTransport = pulse;
2719
- const sessions = new SessionManager({
2720
- vendor: this.config.vendor,
2721
- logger: this.logger,
2722
- pulse,
2723
- selfParticipantId: null,
2724
- commands: this.sessionCommands
2725
- });
2726
- const invites = new InviteManager({
2727
- vendor: this.config.vendor,
2728
- pulse,
2729
- onAccept: async ({ session: view, joinGrant }) => {
2730
- const session = sessions.onAcceptedInvite(view);
2731
- return session.joinWithGrant(joinGrant);
2732
- }
2733
- });
2734
- const presence = new PresenceManager({
2735
- vendor: this.config.vendor
2736
- });
2737
- sessions.on("session.added", ({ session }) => {
2738
- this.bus.emit("session.added", { session });
2739
- });
2740
- sessions.on("session.removed", ({ sessionId }) => {
2741
- this.bus.emit("session.removed", { sessionId });
2742
- });
2743
- invites.on("invite.added", ({ invite }) => {
2744
- this.bus.emit("invite.added", { inviteId: invite.id });
2745
- });
2746
- invites.on("invite.removed", ({ inviteId }) => {
2747
- this.bus.emit("invite.removed", { inviteId });
2748
- });
2749
- pulse.on("status.changed", ({ status }) => {
2750
- if (status === "recovering") {
2751
- this.setStatus("degraded");
2752
- } else if (status === "connected") {
2753
- this.setStatus("ready");
2754
- } else if (status === "disconnected") {
2755
- this.setStatus("offline");
2756
- }
2757
- });
2758
- this.sessions = sessions;
2759
- this.invites = invites;
2760
- this.presence = presence;
2761
- const [connectPulse, sessionsPrime, invitesPrime] = await Promise.all([
2762
- pulse.connect(),
2763
- sessions.prime(),
2764
- invites.prime()
2765
- ]);
2766
- if (!connectPulse.ok) {
2767
- this.setStatus("offline");
2768
- this.bus.emit("error", { error: connectPulse.error });
2769
- return connectPulse;
2770
- }
2771
- for (const r of [sessionsPrime, invitesPrime]) {
2772
- if (!r.ok) {
2773
- this.logger.warn({ code: r.error.code }, "prime failed");
2774
- }
2775
- }
2776
- this.setStatus("ready");
2777
- return ok(void 0);
2778
- }
2779
- async dispose() {
2780
- if (this.disposed) {
2781
- return;
2782
- }
2783
- this.disposed = true;
2784
- if (this.responseInterceptorId !== null) {
2785
- client.interceptors.response.eject(this.responseInterceptorId);
2786
- this.responseInterceptorId = null;
1285
+ var createSessionClient = (config) => {
1286
+ const core = createCore(config);
1287
+ return {
1288
+ kind: "session",
1289
+ get status() {
1290
+ return core.store.getStatus();
1291
+ },
1292
+ sessions: {
1293
+ ...createCommonSessions(core),
1294
+ start: (input) => startSession(core, input.vendor, input.target)
1295
+ },
1296
+ invites: {
1297
+ list: core.store.invites,
1298
+ get: core.store.invite
1299
+ },
1300
+ connect: core.connect,
1301
+ disconnect: core.realtime.disconnect,
1302
+ dispose: core.dispose,
1303
+ on: core.emitter.on,
1304
+ subscribe: core.store.subscribe
1305
+ };
1306
+ };
1307
+ var createHostClient = (config) => {
1308
+ const core = createCore(config);
1309
+ return {
1310
+ kind: "host",
1311
+ vendor: config.vendor,
1312
+ get status() {
1313
+ return core.store.getStatus();
1314
+ },
1315
+ sessions: {
1316
+ ...createCommonSessions(core),
1317
+ start: (input) => startSession(core, config.vendor, input.target)
1318
+ },
1319
+ invites: {
1320
+ list: core.store.invites,
1321
+ get: core.store.invite
1322
+ },
1323
+ host: createHostControls(core),
1324
+ connect: core.connect,
1325
+ disconnect: core.realtime.disconnect,
1326
+ dispose: core.dispose,
1327
+ on: core.emitter.on,
1328
+ subscribe: core.store.subscribe
1329
+ };
1330
+ };
1331
+ var createCore = (config) => {
1332
+ const api = apiClient(config);
1333
+ const store = createSessionStore(config.userId);
1334
+ const emitter = createEmitter();
1335
+ const realtime = createRealtime(api, config.auth, store, emitter);
1336
+ const applySession = (session, mode = "event") => {
1337
+ const change = store.upsert(session, mode);
1338
+ if (change === "added") {
1339
+ emitter.emit("session.added", session);
2787
1340
  }
2788
- await this.pluginHost.disposeAll();
2789
- await this.sessions?.dispose();
2790
- await this.invites?.dispose();
2791
- await this.presence?.dispose();
2792
- await this.pulseTransport?.dispose();
2793
- this.bus.clear();
2794
- this.setStatus("disposed");
2795
- }
2796
- async createSession(body) {
2797
- const manager = this.sessions;
2798
- if (!manager) {
2799
- return err(
2800
- callpadError.precondition(
2801
- "callpad.not_connected",
2802
- "Client not connected"
2803
- )
2804
- );
1341
+ if (change === "updated") {
1342
+ emitter.emit("session.updated", session);
2805
1343
  }
2806
- return manager.create(body);
2807
- }
2808
- async use(plugin) {
2809
- const pulse = this.pulseTransport;
2810
- const sessions = this.sessions;
2811
- const invites = this.invites;
2812
- const presence = this.presence;
2813
- if (!pulse || !sessions || !invites || !presence) {
2814
- return err(
2815
- callpadError.precondition(
2816
- "callpad.not_connected",
2817
- "Plugins require connect() first"
2818
- )
2819
- );
1344
+ return session;
1345
+ };
1346
+ const applyStarted = (started, mode = "event") => {
1347
+ applySession(started.session, mode);
1348
+ return started;
1349
+ };
1350
+ const patchRecording = (control) => {
1351
+ const session = store.patchRecording(control);
1352
+ if (session) {
1353
+ emitter.emit("session.updated", session);
2820
1354
  }
2821
- const ctx = {
2822
- logger: this.logger.child({ plugin: plugin.name }),
2823
- pulse,
2824
- sessions,
2825
- invites,
2826
- presence,
2827
- registerSessionCommand: (cmd) => this.registerSessionCommand(cmd),
2828
- onSessionCreated: (handler) => this.bus.on("session.added", ({ session }) => handler(session)),
2829
- onSessionDisposed: (handler) => this.bus.on("session.removed", ({ sessionId }) => handler(sessionId))
2830
- };
1355
+ return control;
1356
+ };
1357
+ const connect = async () => {
2831
1358
  try {
2832
- await this.pluginHost.register(plugin, ctx);
2833
- return ok(void 0);
2834
- } catch (cause) {
2835
- return err(
2836
- callpadError.internal(
2837
- "callpad.plugin_install_failed",
2838
- `Plugin "${plugin.name}" failed to install`,
2839
- { cause: cause instanceof Error ? cause : void 0 }
2840
- )
2841
- );
2842
- }
2843
- }
2844
- configureApiClient() {
2845
- const baseUrl = this.config.apiBaseUrl;
2846
- const auth = this.config.auth.getAccessToken;
2847
- const retryingFetch = makeRetryingFetch((request) => fetch(request), {
2848
- maxAttempts: 3,
2849
- initialDelayMs: 300
2850
- });
2851
- client.setConfig({
2852
- baseUrl,
2853
- auth: async () => auth(),
2854
- fetch: retryingFetch
2855
- });
2856
- if (this.responseInterceptorId !== null) {
2857
- client.interceptors.response.eject(this.responseInterceptorId);
1359
+ await realtime.connect();
1360
+ } catch (err) {
1361
+ emitter.emit("error", err instanceof Error ? err : new Error("Connection failed"));
1362
+ throw err;
2858
1363
  }
2859
- let unauthorizedFired = false;
2860
- this.responseInterceptorId = client.interceptors.response.use(
2861
- (response) => {
2862
- if (response.status === 401) {
2863
- if (!unauthorizedFired) {
2864
- unauthorizedFired = true;
2865
- this.config.auth.onUnauthorized?.();
2866
- }
2867
- } else if (unauthorizedFired) {
2868
- unauthorizedFired = false;
1364
+ };
1365
+ const dispose = async () => {
1366
+ await realtime.dispose();
1367
+ store.dispose();
1368
+ };
1369
+ return {
1370
+ api,
1371
+ auth: config.auth,
1372
+ store,
1373
+ emitter,
1374
+ realtime,
1375
+ connect,
1376
+ dispose,
1377
+ applySession,
1378
+ applyStarted,
1379
+ patchRecording
1380
+ };
1381
+ };
1382
+ var createCommonSessions = (core) => ({
1383
+ list: core.store.list,
1384
+ get: core.store.get,
1385
+ sync: async (query) => {
1386
+ const response = await unwrap(listSessions({ client: core.api, query }), core.auth);
1387
+ core.store.sync(response.items);
1388
+ return core.store.list();
1389
+ },
1390
+ join: (sessionId) => unwrap(createSessionToken({ client: core.api, path: { sessionId } }), core.auth),
1391
+ accept: async (input) => core.applyStarted(
1392
+ await unwrap(
1393
+ acceptSessionParticipant({
1394
+ client: core.api,
1395
+ path: {
1396
+ sessionId: input.sessionId,
1397
+ participantId: input.participantId
2869
1398
  }
2870
- return response;
2871
- }
2872
- );
2873
- }
2874
- async fetchPulseGrant() {
2875
- return apiCall(
2876
- () => getApiV1PulseGrants({
2877
- query: { vendor: this.config.vendor }
2878
- })
2879
- );
2880
- }
2881
- setStatus(next) {
2882
- if (next === this.status) {
2883
- return;
1399
+ }),
1400
+ core.auth
1401
+ ),
1402
+ "command"
1403
+ ),
1404
+ reject: async (input) => core.applySession(
1405
+ await unwrap(
1406
+ rejectSessionParticipant({
1407
+ client: core.api,
1408
+ path: {
1409
+ sessionId: input.sessionId,
1410
+ participantId: input.participantId
1411
+ }
1412
+ }),
1413
+ core.auth
1414
+ ),
1415
+ "command"
1416
+ ),
1417
+ leave: async (sessionId) => core.applySession(
1418
+ await unwrap(leaveSession({ client: core.api, path: { sessionId } }), core.auth),
1419
+ "command"
1420
+ ),
1421
+ can: core.store.can
1422
+ });
1423
+ var createHostControls = (core) => ({
1424
+ end: async (sessionId) => {
1425
+ await unwrap(endSession({ client: core.api, path: { sessionId } }), core.auth);
1426
+ if (core.store.remove({ id: sessionId })) {
1427
+ core.emitter.emit("session.removed", { sessionId });
2884
1428
  }
2885
- const previous = this.status;
2886
- this.status = next;
2887
- this.bus.emit("status.changed", { status: next, previous });
2888
- }
2889
- };
2890
- function createCallpadClient(config) {
2891
- return new CallpadClient(config);
2892
- }
1429
+ },
1430
+ addParticipant: async (input) => core.applyStarted(
1431
+ await unwrap(
1432
+ addSessionParticipant({
1433
+ client: core.api,
1434
+ path: { sessionId: input.sessionId },
1435
+ body: { target: input.target }
1436
+ }),
1437
+ core.auth
1438
+ ),
1439
+ "command"
1440
+ ),
1441
+ removeParticipant: async (input) => core.applySession(
1442
+ await unwrap(
1443
+ removeSessionParticipant({
1444
+ client: core.api,
1445
+ path: {
1446
+ sessionId: input.sessionId,
1447
+ participantId: input.participantId
1448
+ }
1449
+ }),
1450
+ core.auth
1451
+ ),
1452
+ "command"
1453
+ ),
1454
+ startRecording: async (sessionId) => core.patchRecording(
1455
+ await unwrap(startRecording({ client: core.api, path: { sessionId } }), core.auth)
1456
+ ),
1457
+ stopRecording: async (sessionId) => core.patchRecording(
1458
+ await unwrap(stopRecording({ client: core.api, path: { sessionId } }), core.auth)
1459
+ )
1460
+ });
1461
+ var startSession = async (core, vendor, target) => core.applyStarted(
1462
+ await unwrap(
1463
+ createSession({
1464
+ client: core.api,
1465
+ path: { vendor },
1466
+ body: { target }
1467
+ }),
1468
+ core.auth
1469
+ ),
1470
+ "command"
1471
+ );
2893
1472
 
2894
- export { CallpadClient, callpadError, createCallpadClient, err, isErr, isOk, noopLogger, ok };
1473
+ export { CallpadError, createHostClient, createSessionClient };
2895
1474
  //# sourceMappingURL=index.js.map
2896
1475
  //# sourceMappingURL=index.js.map