farvex 0.2.0 → 1.0.0

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