@babelforce/babelconnect-sdk 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@ import { createClient, } from "@connectrpc/connect";
2
2
  import { createGrpcWebTransport } from "@connectrpc/connect-web";
3
3
  import { Agent } from "./gen/babelconnect/v1/babelconnect_connect.js";
4
4
  import { AddConferenceMember, AnswerCall, CallDirection, CallLifecycle, CallSource, Command, EndConference, Error as BcError, FlagRecording, Hangup, HistoryRequest, SmsThreadRequest, PhonebookRequest, HoldConferenceMember, Hold, KickConferenceMember, LeaveConference, MarkConversationRead, Mute, MuteConferenceMember, PlaceCall, Register, ResetLineStatus, SendDigits, SendSmsRequest, SetAgentNumber, SetConversationOpen, SetDisplayAs, SetPresence, SetWebrtc, StartConference, StartRecording, StopRecording, SetRecordingTags, SubscribeRequest, Transfer, WrapUpCancel, WrapUpExtend, } from "./gen/babelconnect/v1/babelconnect_pb.js";
5
- import { browserMediaFactory, toRTCIceServers, } from "./media.js";
5
+ import { browserMediaFactory, MediaError, toRTCIceServers, } from "./media.js";
6
6
  import { StateCache } from "./state-cache.js";
7
7
  /**
8
8
  * The TypeScript "dumb renderer" client: opens the `Subscribe`/`Send` gRPC-web
@@ -420,9 +420,15 @@ export class BabelconnectClient {
420
420
  }));
421
421
  }
422
422
  catch (e) {
423
+ // CALL-M5: a Media impl classifies a mic-acquisition failure (no mic,
424
+ // permission denied, device busy) into a distinct code via MediaError;
425
+ // anything else stays the opaque catch-all. The distinct code lets the
426
+ // UI/host explain the cause instead of showing a raw WebRTC string.
427
+ const code = e instanceof MediaError ? e.code : "media_answer_failed";
428
+ const message = e instanceof MediaError ? e.message : String(e);
423
429
  this.opts.onError?.(new BcError({
424
- code: "media_answer_failed",
425
- message: String(e),
430
+ code,
431
+ message,
426
432
  callId: call.id,
427
433
  }));
428
434
  }
@@ -332,6 +332,18 @@ export declare class Identity extends Message<Identity> {
332
332
  * @generated from field: babelconnect.v1.AgentState state = 5;
333
333
  */
334
334
  state: AgentState;
335
+ /**
336
+ * account / company display name, when known (ACC-E1)
337
+ *
338
+ * @generated from field: string account_name = 6;
339
+ */
340
+ accountName: string;
341
+ /**
342
+ * accounts this agent may switch to (ACC-E2)
343
+ *
344
+ * @generated from field: repeated babelconnect.v1.Account accounts = 7;
345
+ */
346
+ accounts: Account[];
335
347
  constructor(data?: PartialMessage<Identity>);
336
348
  static readonly runtime: typeof proto3;
337
349
  static readonly typeName = "babelconnect.v1.Identity";
@@ -341,6 +353,41 @@ export declare class Identity extends Message<Identity> {
341
353
  static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Identity;
342
354
  static equals(a: Identity | PlainMessage<Identity> | undefined, b: Identity | PlainMessage<Identity> | undefined): boolean;
343
355
  }
356
+ /**
357
+ * Account is one tenant the signed-in agent can operate in — the switchable set
358
+ * (agent-role only) surfaced for the account picker (ACC-E2). Sourced from
359
+ * GET /api/v2/user/accounts.
360
+ *
361
+ * @generated from message babelconnect.v1.Account
362
+ */
363
+ export declare class Account extends Message<Account> {
364
+ /**
365
+ * customer / account id
366
+ *
367
+ * @generated from field: string id = 1;
368
+ */
369
+ id: string;
370
+ /**
371
+ * company / display name
372
+ *
373
+ * @generated from field: string name = 2;
374
+ */
375
+ name: string;
376
+ /**
377
+ * the account this session is currently scoped to
378
+ *
379
+ * @generated from field: bool current = 3;
380
+ */
381
+ current: boolean;
382
+ constructor(data?: PartialMessage<Account>);
383
+ static readonly runtime: typeof proto3;
384
+ static readonly typeName = "babelconnect.v1.Account";
385
+ static readonly fields: FieldList;
386
+ static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Account;
387
+ static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Account;
388
+ static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Account;
389
+ static equals(a: Account | PlainMessage<Account> | undefined, b: Account | PlainMessage<Account> | undefined): boolean;
390
+ }
344
391
  /**
345
392
  * AgentView is the complete per-agent state the server owns and renders to. A
346
393
  * snapshot carries the whole view; patches carry entity-level deltas.
@@ -532,6 +579,12 @@ export declare class FeatureAccount extends Message<FeatureAccount> {
532
579
  * @generated from field: bool show_status = 3;
533
580
  */
534
581
  showStatus: boolean;
582
+ /**
583
+ * the multi-account switcher (ACC-E2)
584
+ *
585
+ * @generated from field: bool allow_account_switch = 4;
586
+ */
587
+ allowAccountSwitch: boolean;
535
588
  constructor(data?: PartialMessage<FeatureAccount>);
536
589
  static readonly runtime: typeof proto3;
537
590
  static readonly typeName = "babelconnect.v1.FeatureAccount";
@@ -716,6 +769,14 @@ export declare class AgentInfo extends Message<AgentInfo> {
716
769
  * @generated from field: bool line_blocked = 18;
717
770
  */
718
771
  lineBlocked: boolean;
772
+ /**
773
+ * Accounts this agent may switch to without re-logging-in (agent-role only,
774
+ * resolved at auth from GET /api/v2/user/accounts). One is marked `current`.
775
+ * Drives the Account tab's switcher; empty/single ⇒ no picker (ACC-E2).
776
+ *
777
+ * @generated from field: repeated babelconnect.v1.Account accounts = 19;
778
+ */
779
+ accounts: Account[];
719
780
  constructor(data?: PartialMessage<AgentInfo>);
720
781
  static readonly runtime: typeof proto3;
721
782
  static readonly typeName = "babelconnect.v1.AgentInfo";
@@ -471,6 +471,18 @@ export class Identity extends Message {
471
471
  * @generated from field: babelconnect.v1.AgentState state = 5;
472
472
  */
473
473
  state = AgentState.UNSPECIFIED;
474
+ /**
475
+ * account / company display name, when known (ACC-E1)
476
+ *
477
+ * @generated from field: string account_name = 6;
478
+ */
479
+ accountName = "";
480
+ /**
481
+ * accounts this agent may switch to (ACC-E2)
482
+ *
483
+ * @generated from field: repeated babelconnect.v1.Account accounts = 7;
484
+ */
485
+ accounts = [];
474
486
  constructor(data) {
475
487
  super();
476
488
  proto3.util.initPartial(data, this);
@@ -483,6 +495,8 @@ export class Identity extends Message {
483
495
  { no: 3, name: "number", kind: "scalar", T: 9 /* ScalarType.STRING */ },
484
496
  { no: 4, name: "username", kind: "scalar", T: 9 /* ScalarType.STRING */ },
485
497
  { no: 5, name: "state", kind: "enum", T: proto3.getEnumType(AgentState) },
498
+ { no: 6, name: "account_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
499
+ { no: 7, name: "accounts", kind: "message", T: Account, repeated: true },
486
500
  ]);
487
501
  static fromBinary(bytes, options) {
488
502
  return new Identity().fromBinary(bytes, options);
@@ -497,6 +511,56 @@ export class Identity extends Message {
497
511
  return proto3.util.equals(Identity, a, b);
498
512
  }
499
513
  }
514
+ /**
515
+ * Account is one tenant the signed-in agent can operate in — the switchable set
516
+ * (agent-role only) surfaced for the account picker (ACC-E2). Sourced from
517
+ * GET /api/v2/user/accounts.
518
+ *
519
+ * @generated from message babelconnect.v1.Account
520
+ */
521
+ export class Account extends Message {
522
+ /**
523
+ * customer / account id
524
+ *
525
+ * @generated from field: string id = 1;
526
+ */
527
+ id = "";
528
+ /**
529
+ * company / display name
530
+ *
531
+ * @generated from field: string name = 2;
532
+ */
533
+ name = "";
534
+ /**
535
+ * the account this session is currently scoped to
536
+ *
537
+ * @generated from field: bool current = 3;
538
+ */
539
+ current = false;
540
+ constructor(data) {
541
+ super();
542
+ proto3.util.initPartial(data, this);
543
+ }
544
+ static runtime = proto3;
545
+ static typeName = "babelconnect.v1.Account";
546
+ static fields = proto3.util.newFieldList(() => [
547
+ { no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
548
+ { no: 2, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
549
+ { no: 3, name: "current", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
550
+ ]);
551
+ static fromBinary(bytes, options) {
552
+ return new Account().fromBinary(bytes, options);
553
+ }
554
+ static fromJson(jsonValue, options) {
555
+ return new Account().fromJson(jsonValue, options);
556
+ }
557
+ static fromJsonString(jsonString, options) {
558
+ return new Account().fromJsonString(jsonString, options);
559
+ }
560
+ static equals(a, b) {
561
+ return proto3.util.equals(Account, a, b);
562
+ }
563
+ }
500
564
  /**
501
565
  * AgentView is the complete per-agent state the server owns and renders to. A
502
566
  * snapshot carries the whole view; patches carry entity-level deltas.
@@ -767,6 +831,12 @@ export class FeatureAccount extends Message {
767
831
  * @generated from field: bool show_status = 3;
768
832
  */
769
833
  showStatus = false;
834
+ /**
835
+ * the multi-account switcher (ACC-E2)
836
+ *
837
+ * @generated from field: bool allow_account_switch = 4;
838
+ */
839
+ allowAccountSwitch = false;
770
840
  constructor(data) {
771
841
  super();
772
842
  proto3.util.initPartial(data, this);
@@ -777,6 +847,7 @@ export class FeatureAccount extends Message {
777
847
  { no: 1, name: "enabled", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
778
848
  { no: 2, name: "allow_device_switch", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
779
849
  { no: 3, name: "show_status", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
850
+ { no: 4, name: "allow_account_switch", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
780
851
  ]);
781
852
  static fromBinary(bytes, options) {
782
853
  return new FeatureAccount().fromBinary(bytes, options);
@@ -1007,6 +1078,14 @@ export class AgentInfo extends Message {
1007
1078
  * @generated from field: bool line_blocked = 18;
1008
1079
  */
1009
1080
  lineBlocked = false;
1081
+ /**
1082
+ * Accounts this agent may switch to without re-logging-in (agent-role only,
1083
+ * resolved at auth from GET /api/v2/user/accounts). One is marked `current`.
1084
+ * Drives the Account tab's switcher; empty/single ⇒ no picker (ACC-E2).
1085
+ *
1086
+ * @generated from field: repeated babelconnect.v1.Account accounts = 19;
1087
+ */
1088
+ accounts = [];
1010
1089
  constructor(data) {
1011
1090
  super();
1012
1091
  proto3.util.initPartial(data, this);
@@ -1032,6 +1111,7 @@ export class AgentInfo extends Message {
1032
1111
  { no: 16, name: "account_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
1033
1112
  { no: 17, name: "account_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
1034
1113
  { no: 18, name: "line_blocked", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
1114
+ { no: 19, name: "accounts", kind: "message", T: Account, repeated: true },
1035
1115
  ]);
1036
1116
  static fromBinary(bytes, options) {
1037
1117
  return new AgentInfo().fromBinary(bytes, options);
package/dist/index.d.ts CHANGED
@@ -12,6 +12,6 @@
12
12
  export { BabelconnectClient, type ConnectOptions } from "./client.js";
13
13
  export { StateCache } from "./state-cache.js";
14
14
  export { passwordGrant, revokeToken, pkceChallenge, buildAuthorizeUrl, authorizationCodeGrant, DEFAULT_CLIENT_ID, type TokenResponse, type PkceChallenge, type AuthorizeUrlOptions, } from "./auth.js";
15
- export { BrowserWebrtcMedia, browserMediaFactory, type Media, type MediaFactory } from "./media.js";
15
+ export { BrowserWebrtcMedia, browserMediaFactory, MediaError, micErrorCode, MIC_NOT_FOUND, MIC_PERMISSION_DENIED, MIC_IN_USE, MEDIA_ANSWER_FAILED, type Media, type MediaFactory, } from "./media.js";
16
16
  export * from "./gen/babelconnect/v1/babelconnect_pb.js";
17
17
  export { Agent } from "./gen/babelconnect/v1/babelconnect_connect.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  export { BabelconnectClient } from "./client.js";
13
13
  export { StateCache } from "./state-cache.js";
14
14
  export { passwordGrant, revokeToken, pkceChallenge, buildAuthorizeUrl, authorizationCodeGrant, DEFAULT_CLIENT_ID, } from "./auth.js";
15
- export { BrowserWebrtcMedia, browserMediaFactory } from "./media.js";
15
+ export { BrowserWebrtcMedia, browserMediaFactory, MediaError, micErrorCode, MIC_NOT_FOUND, MIC_PERMISSION_DENIED, MIC_IN_USE, MEDIA_ANSWER_FAILED, } from "./media.js";
16
16
  // The generated babelconnect.v1 messages + enums (AgentView, CallState, Command, …)
17
17
  // and the Agent service descriptor.
18
18
  export * from "./gen/babelconnect/v1/babelconnect_pb.js";
package/dist/media.d.ts CHANGED
@@ -19,6 +19,33 @@ export interface Media {
19
19
  }
20
20
  /** Builds the {@link Media} for a given call id. */
21
21
  export type MediaFactory = (callId: string) => Media;
22
+ /** No microphone hardware is available (`NotFoundError`). */
23
+ export declare const MIC_NOT_FOUND = "mic_not_found";
24
+ /** Microphone access was denied (`NotAllowedError` / `SecurityError`). */
25
+ export declare const MIC_PERMISSION_DENIED = "mic_permission_denied";
26
+ /** The microphone is held by another application (`NotReadableError` / `AbortError`). */
27
+ export declare const MIC_IN_USE = "mic_in_use";
28
+ /** The pre-existing catch-all, kept as the fallback when the cause is unclear. */
29
+ export declare const MEDIA_ANSWER_FAILED = "media_answer_failed";
30
+ /**
31
+ * Thrown by a {@link Media} implementation when it cannot start audio for a
32
+ * call — e.g. the microphone is missing, permission was denied, or the device
33
+ * is held by another app (CALL-M5). {@link code} is a stable classification the
34
+ * SDK surfaces on `onError` (and hosts receive as `cti.error`) so the UI can
35
+ * show cause-specific help instead of a raw WebRTC string.
36
+ */
37
+ export declare class MediaError extends Error {
38
+ readonly code: string;
39
+ constructor(code: string, message: string);
40
+ }
41
+ /**
42
+ * Classify a caught `getUserMedia` failure into one of the mic error codes,
43
+ * falling back to {@link MEDIA_ANSWER_FAILED} when the cause can't be told
44
+ * apart. On the web the real `DOMException` is available so we read its `.name`;
45
+ * the string form is also scanned as a fallback (and to stay in step with the
46
+ * Dart classifier, which only has the collapsed `'Unable to getUserMedia: …'`).
47
+ */
48
+ export declare function micErrorCode(error: unknown): string;
22
49
  /**
23
50
  * The default browser WebRTC backend: capture the mic, answer the server's offer
24
51
  * (non-trickle ICE), and play the remote audio through a hidden `<audio>` element.
package/dist/media.js CHANGED
@@ -1,3 +1,57 @@
1
+ /** No microphone hardware is available (`NotFoundError`). */
2
+ export const MIC_NOT_FOUND = "mic_not_found";
3
+ /** Microphone access was denied (`NotAllowedError` / `SecurityError`). */
4
+ export const MIC_PERMISSION_DENIED = "mic_permission_denied";
5
+ /** The microphone is held by another application (`NotReadableError` / `AbortError`). */
6
+ export const MIC_IN_USE = "mic_in_use";
7
+ /** The pre-existing catch-all, kept as the fallback when the cause is unclear. */
8
+ export const MEDIA_ANSWER_FAILED = "media_answer_failed";
9
+ /**
10
+ * Thrown by a {@link Media} implementation when it cannot start audio for a
11
+ * call — e.g. the microphone is missing, permission was denied, or the device
12
+ * is held by another app (CALL-M5). {@link code} is a stable classification the
13
+ * SDK surfaces on `onError` (and hosts receive as `cti.error`) so the UI can
14
+ * show cause-specific help instead of a raw WebRTC string.
15
+ */
16
+ export class MediaError extends Error {
17
+ code;
18
+ constructor(code, message) {
19
+ super(message);
20
+ this.code = code;
21
+ this.name = "MediaError";
22
+ }
23
+ }
24
+ /**
25
+ * Classify a caught `getUserMedia` failure into one of the mic error codes,
26
+ * falling back to {@link MEDIA_ANSWER_FAILED} when the cause can't be told
27
+ * apart. On the web the real `DOMException` is available so we read its `.name`;
28
+ * the string form is also scanned as a fallback (and to stay in step with the
29
+ * Dart classifier, which only has the collapsed `'Unable to getUserMedia: …'`).
30
+ */
31
+ export function micErrorCode(error) {
32
+ const name = typeof DOMException !== "undefined" && error instanceof DOMException
33
+ ? error.name
34
+ : "";
35
+ const s = `${name} ${String(error)}`.toLowerCase();
36
+ if (s.includes("notfound") || s.includes("not found") || s.includes("no device")) {
37
+ return MIC_NOT_FOUND;
38
+ }
39
+ if (s.includes("notallowed") ||
40
+ s.includes("permissiondenied") ||
41
+ s.includes("permission denied") ||
42
+ s.includes("security") ||
43
+ s.includes("denied")) {
44
+ return MIC_PERMISSION_DENIED;
45
+ }
46
+ if (s.includes("notreadable") ||
47
+ s.includes("trackstart") ||
48
+ s.includes("abort") ||
49
+ s.includes("in use") ||
50
+ s.includes("busy")) {
51
+ return MIC_IN_USE;
52
+ }
53
+ return MEDIA_ANSWER_FAILED;
54
+ }
1
55
  /**
2
56
  * The default browser WebRTC backend: capture the mic, answer the server's offer
3
57
  * (non-trickle ICE), and play the remote audio through a hidden `<audio>` element.
@@ -35,8 +89,17 @@ export class BrowserWebrtcMedia {
35
89
  el.srcObject = stream;
36
90
  void el.play().catch(() => { });
37
91
  };
38
- // Capture the microphone and add it to the peer.
39
- const local = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
92
+ // Capture the microphone and add it to the peer. Classify a failure (no mic
93
+ // / permission denied / device busy) into a MediaError the SDK surfaces with
94
+ // a distinct code (CALL-M5) so the UI/host can explain it, instead of the
95
+ // opaque `media_answer_failed`.
96
+ let local;
97
+ try {
98
+ local = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
99
+ }
100
+ catch (e) {
101
+ throw new MediaError(micErrorCode(e), String(e));
102
+ }
40
103
  this.local = local;
41
104
  for (const track of local.getAudioTracks())
42
105
  pc.addTrack(track, local);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babelforce/babelconnect-sdk",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "TypeScript SDK for babelconnect — server-authoritative agent state over gRPC-web, native WebRTC audio, and an embeddable widget (iframe + postMessage) for the Flutter web app.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://babelforce.github.io/babelconnect-sdk/",