@babelforce/babelconnect-sdk 0.9.1 → 0.11.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.d.ts CHANGED
@@ -150,3 +150,8 @@ export declare class BabelconnectClient {
150
150
  private rawSend;
151
151
  private closeAllMedia;
152
152
  }
153
+ /**
154
+ * Stamp the SDK identity (name + build version) onto a request's headers — sent
155
+ * on every unary + streaming call so the server can identify the client (APP-A5).
156
+ */
157
+ export declare function applySdkHeaders(header: Headers): void;
package/dist/client.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { createClient, } from "@connectrpc/connect";
2
2
  import { createGrpcWebTransport } from "@connectrpc/connect-web";
3
+ import { SDK_VERSION } from "./version.js";
3
4
  import { Agent } from "./gen/babelconnect/v1/babelconnect_connect.js";
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 { AddConferenceMember, AnswerCall, CallDirection, CallLifecycle, CallSource, Command, EndConference, Error as BcError, FlagRecording, Hangup, HistoryRequest, SmsThreadRequest, PhonebookRequest, HoldConferenceMember, Hold, KickConferenceMember, LeaveConference, MarkConversationRead, Mute, MuteConferenceMember, PlaceCall, Pong, Register, ResetLineStatus, SendDigits, SendSmsRequest, SetAgentNumber, SetConversationOpen, SetDisplayAs, SetPresence, SetWebrtc, StartConference, StartRecording, StopRecording, SetRecordingTags, SubscribeRequest, Transfer, WrapUpCancel, WrapUpExtend, } from "./gen/babelconnect/v1/babelconnect_pb.js";
6
+ import { browserMediaFactory, MediaError, toRTCIceServers, } from "./media.js";
6
7
  import { StateCache } from "./state-cache.js";
7
8
  /**
8
9
  * The TypeScript "dumb renderer" client: opens the `Subscribe`/`Send` gRPC-web
@@ -346,6 +347,15 @@ export class BabelconnectClient {
346
347
  // pass so a 15s heartbeat doesn't trigger a no-op reconcile every tick.
347
348
  if (u.update.case === "keepalive")
348
349
  return;
350
+ // Ping: reply with a Pong echoing the seq so the server can measure RTT +
351
+ // liveness (CALL-M6). Control-only, like keepalive — short-circuited before the
352
+ // cache and the ready/flush logic so it is never mistaken for the snapshot.
353
+ if (u.update.case === "ping") {
354
+ void this.rawSend(new Command({
355
+ command: { case: "pong", value: new Pong({ seq: u.update.value.seq }) },
356
+ }));
357
+ return;
358
+ }
349
359
  // First message = the snapshot ⇒ the session is registered server-side, so
350
360
  // flush intents queued during connect() (register(), an early dial, …).
351
361
  if (!this.ready) {
@@ -420,9 +430,15 @@ export class BabelconnectClient {
420
430
  }));
421
431
  }
422
432
  catch (e) {
433
+ // CALL-M5: a Media impl classifies a mic-acquisition failure (no mic,
434
+ // permission denied, device busy) into a distinct code via MediaError;
435
+ // anything else stays the opaque catch-all. The distinct code lets the
436
+ // UI/host explain the cause instead of showing a raw WebRTC string.
437
+ const code = e instanceof MediaError ? e.code : "media_answer_failed";
438
+ const message = e instanceof MediaError ? e.message : String(e);
423
439
  this.opts.onError?.(new BcError({
424
- code: "media_answer_failed",
425
- message: String(e),
440
+ code,
441
+ message,
426
442
  callId: call.id,
427
443
  }));
428
444
  }
@@ -458,10 +474,22 @@ export class BabelconnectClient {
458
474
  await Promise.all(all.map((m) => m.close()));
459
475
  }
460
476
  }
461
- /** An interceptor that attaches the bearer token to every gRPC-web call. */
477
+ /**
478
+ * Stamp the SDK identity (name + build version) onto a request's headers — sent
479
+ * on every unary + streaming call so the server can identify the client (APP-A5).
480
+ */
481
+ export function applySdkHeaders(header) {
482
+ header.set("x-babelconnect-sdk", "ts");
483
+ header.set("x-babelconnect-sdk-version", SDK_VERSION);
484
+ }
485
+ /**
486
+ * An interceptor that attaches the bearer token and the SDK identity headers to
487
+ * every gRPC-web call (unary + the long-lived Subscribe stream).
488
+ */
462
489
  function authInterceptor(token) {
463
490
  return (next) => (req) => {
464
491
  req.header.set("Authorization", `Bearer ${token}`);
492
+ applySdkHeaders(req.header);
465
493
  return next(req);
466
494
  };
467
495
  }
@@ -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";
@@ -1037,6 +1098,12 @@ export declare class StateUpdate extends Message<StateUpdate> {
1037
1098
  */
1038
1099
  value: Keepalive;
1039
1100
  case: "keepalive";
1101
+ } | {
1102
+ /**
1103
+ * @generated from field: babelconnect.v1.Ping ping = 6;
1104
+ */
1105
+ value: Ping;
1106
+ case: "ping";
1040
1107
  } | {
1041
1108
  case: undefined;
1042
1109
  value?: undefined;
@@ -1070,6 +1137,30 @@ export declare class Keepalive extends Message<Keepalive> {
1070
1137
  static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Keepalive;
1071
1138
  static equals(a: Keepalive | PlainMessage<Keepalive> | undefined, b: Keepalive | PlainMessage<Keepalive> | undefined): boolean;
1072
1139
  }
1140
+ /**
1141
+ * Ping is a liveness probe the server sends on the heartbeat tick (replacing the
1142
+ * bare Keepalive frame). The client echoes `seq` back in a Pong command; the server
1143
+ * measures round-trip time (RTT) from the reply and tracks per-session liveness.
1144
+ * Like Keepalive it keeps the connection warm, carries no state, and does NOT
1145
+ * advance `seq` — caches ignore it, and the reply is handled in the client's stream
1146
+ * loop, not the state reducer. (APP-A5/CALL-M6.)
1147
+ *
1148
+ * @generated from message babelconnect.v1.Ping
1149
+ */
1150
+ export declare class Ping extends Message<Ping> {
1151
+ /**
1152
+ * @generated from field: uint64 seq = 1;
1153
+ */
1154
+ seq: bigint;
1155
+ constructor(data?: PartialMessage<Ping>);
1156
+ static readonly runtime: typeof proto3;
1157
+ static readonly typeName = "babelconnect.v1.Ping";
1158
+ static readonly fields: FieldList;
1159
+ static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Ping;
1160
+ static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Ping;
1161
+ static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Ping;
1162
+ static equals(a: Ping | PlainMessage<Ping> | undefined, b: Ping | PlainMessage<Ping> | undefined): boolean;
1163
+ }
1073
1164
  /**
1074
1165
  * Patch is an entity-level delta applied mechanically by the client cache:
1075
1166
  * replace the agent block, upsert/remove a call by id, or set wrap-up.
@@ -1455,6 +1546,14 @@ export declare class Command extends Message<Command> {
1455
1546
  */
1456
1547
  value: MarkConversationRead;
1457
1548
  case: "markConversationRead";
1549
+ } | {
1550
+ /**
1551
+ * reply to a server Ping (liveness + RTT); echoes Ping.seq
1552
+ *
1553
+ * @generated from field: babelconnect.v1.Pong pong = 30;
1554
+ */
1555
+ value: Pong;
1556
+ case: "pong";
1458
1557
  } | {
1459
1558
  case: undefined;
1460
1559
  value?: undefined;
@@ -1468,6 +1567,27 @@ export declare class Command extends Message<Command> {
1468
1567
  static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Command;
1469
1568
  static equals(a: Command | PlainMessage<Command> | undefined, b: Command | PlainMessage<Command> | undefined): boolean;
1470
1569
  }
1570
+ /**
1571
+ * Pong replies to a server Ping (StateUpdate.ping), echoing its `seq` so the server
1572
+ * can match it to the outstanding ping and compute RTT. Sent automatically by the
1573
+ * SDK from its stream-receive loop — not a user intent. (APP-A5/CALL-M6.)
1574
+ *
1575
+ * @generated from message babelconnect.v1.Pong
1576
+ */
1577
+ export declare class Pong extends Message<Pong> {
1578
+ /**
1579
+ * @generated from field: uint64 seq = 1;
1580
+ */
1581
+ seq: bigint;
1582
+ constructor(data?: PartialMessage<Pong>);
1583
+ static readonly runtime: typeof proto3;
1584
+ static readonly typeName = "babelconnect.v1.Pong";
1585
+ static readonly fields: FieldList;
1586
+ static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Pong;
1587
+ static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Pong;
1588
+ static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Pong;
1589
+ static equals(a: Pong | PlainMessage<Pong> | undefined, b: Pong | PlainMessage<Pong> | undefined): boolean;
1590
+ }
1471
1591
  /**
1472
1592
  * @generated from message babelconnect.v1.Register
1473
1593
  */
@@ -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);
@@ -1465,6 +1545,7 @@ export class StateUpdate extends Message {
1465
1545
  { no: 3, name: "patch", kind: "message", T: Patch, oneof: "update" },
1466
1546
  { no: 4, name: "error", kind: "message", T: Error, oneof: "update" },
1467
1547
  { no: 5, name: "keepalive", kind: "message", T: Keepalive, oneof: "update" },
1548
+ { no: 6, name: "ping", kind: "message", T: Ping, oneof: "update" },
1468
1549
  ]);
1469
1550
  static fromBinary(bytes, options) {
1470
1551
  return new StateUpdate().fromBinary(bytes, options);
@@ -1510,6 +1591,43 @@ export class Keepalive extends Message {
1510
1591
  return proto3.util.equals(Keepalive, a, b);
1511
1592
  }
1512
1593
  }
1594
+ /**
1595
+ * Ping is a liveness probe the server sends on the heartbeat tick (replacing the
1596
+ * bare Keepalive frame). The client echoes `seq` back in a Pong command; the server
1597
+ * measures round-trip time (RTT) from the reply and tracks per-session liveness.
1598
+ * Like Keepalive it keeps the connection warm, carries no state, and does NOT
1599
+ * advance `seq` — caches ignore it, and the reply is handled in the client's stream
1600
+ * loop, not the state reducer. (APP-A5/CALL-M6.)
1601
+ *
1602
+ * @generated from message babelconnect.v1.Ping
1603
+ */
1604
+ export class Ping extends Message {
1605
+ /**
1606
+ * @generated from field: uint64 seq = 1;
1607
+ */
1608
+ seq = protoInt64.zero;
1609
+ constructor(data) {
1610
+ super();
1611
+ proto3.util.initPartial(data, this);
1612
+ }
1613
+ static runtime = proto3;
1614
+ static typeName = "babelconnect.v1.Ping";
1615
+ static fields = proto3.util.newFieldList(() => [
1616
+ { no: 1, name: "seq", kind: "scalar", T: 4 /* ScalarType.UINT64 */ },
1617
+ ]);
1618
+ static fromBinary(bytes, options) {
1619
+ return new Ping().fromBinary(bytes, options);
1620
+ }
1621
+ static fromJson(jsonValue, options) {
1622
+ return new Ping().fromJson(jsonValue, options);
1623
+ }
1624
+ static fromJsonString(jsonString, options) {
1625
+ return new Ping().fromJsonString(jsonString, options);
1626
+ }
1627
+ static equals(a, b) {
1628
+ return proto3.util.equals(Ping, a, b);
1629
+ }
1630
+ }
1513
1631
  /**
1514
1632
  * Patch is an entity-level delta applied mechanically by the client cache:
1515
1633
  * replace the agent block, upsert/remove a call by id, or set wrap-up.
@@ -1695,6 +1813,7 @@ export class Command extends Message {
1695
1813
  { no: 27, name: "mute_conference_member", kind: "message", T: MuteConferenceMember, oneof: "command" },
1696
1814
  { no: 28, name: "set_conversation_open", kind: "message", T: SetConversationOpen, oneof: "command" },
1697
1815
  { no: 29, name: "mark_conversation_read", kind: "message", T: MarkConversationRead, oneof: "command" },
1816
+ { no: 30, name: "pong", kind: "message", T: Pong, oneof: "command" },
1698
1817
  ]);
1699
1818
  static fromBinary(bytes, options) {
1700
1819
  return new Command().fromBinary(bytes, options);
@@ -1709,6 +1828,40 @@ export class Command extends Message {
1709
1828
  return proto3.util.equals(Command, a, b);
1710
1829
  }
1711
1830
  }
1831
+ /**
1832
+ * Pong replies to a server Ping (StateUpdate.ping), echoing its `seq` so the server
1833
+ * can match it to the outstanding ping and compute RTT. Sent automatically by the
1834
+ * SDK from its stream-receive loop — not a user intent. (APP-A5/CALL-M6.)
1835
+ *
1836
+ * @generated from message babelconnect.v1.Pong
1837
+ */
1838
+ export class Pong extends Message {
1839
+ /**
1840
+ * @generated from field: uint64 seq = 1;
1841
+ */
1842
+ seq = protoInt64.zero;
1843
+ constructor(data) {
1844
+ super();
1845
+ proto3.util.initPartial(data, this);
1846
+ }
1847
+ static runtime = proto3;
1848
+ static typeName = "babelconnect.v1.Pong";
1849
+ static fields = proto3.util.newFieldList(() => [
1850
+ { no: 1, name: "seq", kind: "scalar", T: 4 /* ScalarType.UINT64 */ },
1851
+ ]);
1852
+ static fromBinary(bytes, options) {
1853
+ return new Pong().fromBinary(bytes, options);
1854
+ }
1855
+ static fromJson(jsonValue, options) {
1856
+ return new Pong().fromJson(jsonValue, options);
1857
+ }
1858
+ static fromJsonString(jsonString, options) {
1859
+ return new Pong().fromJsonString(jsonString, options);
1860
+ }
1861
+ static equals(a, b) {
1862
+ return proto3.util.equals(Pong, a, b);
1863
+ }
1864
+ }
1712
1865
  /**
1713
1866
  * @generated from message babelconnect.v1.Register
1714
1867
  */
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);
@@ -0,0 +1 @@
1
+ export declare const SDK_VERSION = "0.11.0";
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/gen-version.mjs (npm prebuild) from package.json.
2
+ // Do not edit by hand.
3
+ export const SDK_VERSION = "0.11.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babelforce/babelconnect-sdk",
3
- "version": "0.9.1",
3
+ "version": "0.11.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/",
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "scripts": {
26
26
  "generate": "echo 'run `task gen` at the repo root (buf generate emits src/gen)'",
27
+ "prebuild": "node scripts/gen-version.mjs",
27
28
  "build": "tsc -p tsconfig.json",
28
29
  "typecheck": "tsc -p tsconfig.json --noEmit",
29
30
  "test": "npm run build && node --test test/*.test.mjs",