@babelforce/babelconnect-sdk 0.1.0 → 0.7.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/README.md +5 -3
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +96 -0
- package/dist/client.d.ts +40 -2
- package/dist/client.js +180 -35
- package/dist/embed/index.d.ts +1 -1
- package/dist/embed/index.js +1 -1
- package/dist/gen/babelconnect/v1/babelconnect_pb.d.ts +38 -1
- package/dist/gen/babelconnect/v1/babelconnect_pb.js +45 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/state-cache.d.ts +1 -1
- package/dist/state-cache.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ messaging) on top of `babelconnect-server`. Two ways to use it:
|
|
|
22
22
|
npm install @babelforce/babelconnect-sdk
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
ESM-only, targets ES2022. Runs in modern browsers; Node
|
|
25
|
+
ESM-only, targets ES2022. Runs in modern browsers; Node 20+ for control-only (no-audio) use. Native WebRTC
|
|
26
26
|
audio requires a browser (microphone permission).
|
|
27
27
|
|
|
28
28
|
## Quickstart — programmatic client (with audio)
|
|
@@ -90,8 +90,10 @@ bc.session.set({ number: "+49301234567" }); // correlate / prefill
|
|
|
90
90
|
(`setPresence`, `setDisplayAs`, `setAgentNumber`, `setWebrtc`), and history/contacts fetches (`getHistory`,
|
|
91
91
|
`getSmsThread`, `getPhonebook`).
|
|
92
92
|
|
|
93
|
-
Full API reference and the embedding guide:
|
|
94
|
-
**[babelconnect
|
|
93
|
+
Full docs — API reference, guides, and the embedding guide:
|
|
94
|
+
**[babelconnect SDK docs](https://babelforce.github.io/babelconnect-sdk/)**. Good starting points:
|
|
95
|
+
the **[tutorial](https://babelforce.github.io/babelconnect-sdk/docs/tutorial/first-softphone)** and,
|
|
96
|
+
if you hit a snag, **[troubleshooting](https://babelforce.github.io/babelconnect-sdk/docs/guides/troubleshooting)**.
|
|
95
97
|
|
|
96
98
|
## How it works
|
|
97
99
|
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
/** The default OAuth public client id for the babelconnect agent app (no secret; PKCE). */
|
|
2
|
+
export declare const DEFAULT_CLIENT_ID = "babelconnect";
|
|
3
|
+
/** A parsed OAuth token response: the bearer plus, when the grant returns them, the rotating refresh token and the access-token lifetime (seconds). */
|
|
4
|
+
export interface TokenResponse {
|
|
5
|
+
access_token: string;
|
|
6
|
+
refresh_token?: string;
|
|
7
|
+
expires_in?: number;
|
|
8
|
+
token_type?: string;
|
|
9
|
+
}
|
|
10
|
+
/** A PKCE code verifier + S256 challenge (RFC 7636). */
|
|
11
|
+
export interface PkceChallenge {
|
|
12
|
+
/** The high-entropy secret to keep client-side and send with {@link authorizationCodeGrant}. */
|
|
13
|
+
codeVerifier: string;
|
|
14
|
+
/** The base64url(SHA-256(codeVerifier)) value to send with {@link buildAuthorizeUrl}. */
|
|
15
|
+
codeChallenge: string;
|
|
16
|
+
codeChallengeMethod: "S256";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate a PKCE code verifier + S256 challenge (RFC 7636). Pass `codeChallenge` to
|
|
20
|
+
* {@link buildAuthorizeUrl} and keep `codeVerifier` to later exchange the returned code via
|
|
21
|
+
* {@link authorizationCodeGrant}. Uses the Web Crypto API (Node 18+ / browsers).
|
|
22
|
+
*/
|
|
23
|
+
export declare function pkceChallenge(): Promise<PkceChallenge>;
|
|
24
|
+
export interface AuthorizeUrlOptions {
|
|
25
|
+
/** babelconnect-server origin (the same one used for gRPC-web); the authorize endpoint lives on the backend behind the same origin. */
|
|
26
|
+
serverUrl: string;
|
|
27
|
+
/** OAuth client id (defaults to {@link DEFAULT_CLIENT_ID} = `"babelconnect"`). */
|
|
28
|
+
clientId?: string;
|
|
29
|
+
/** The registered callback URL the backend redirects to with the code. */
|
|
30
|
+
redirectUri: string;
|
|
31
|
+
/** OAuth scope (e.g. `"*"`). */
|
|
32
|
+
scope: string;
|
|
33
|
+
codeChallenge: string;
|
|
34
|
+
/** Opaque CSRF token echoed back by the backend; verify it on return. */
|
|
35
|
+
state?: string;
|
|
36
|
+
codeChallengeMethod?: "S256" | "plain";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the `GET {serverUrl}/oauth/authorize` URL that starts the Authorization Code + PKCE flow.
|
|
40
|
+
* Redirect the user to it; the backend redirects back to `redirectUri` with a `code` (and `state`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildAuthorizeUrl(opts: AuthorizeUrlOptions): string;
|
|
43
|
+
/**
|
|
44
|
+
* Exchange an authorization `code` (from the PKCE consent redirect) for tokens via the
|
|
45
|
+
* authorization_code grant against `POST {serverUrl}/oauth/token`. Public clients pass only
|
|
46
|
+
* `codeVerifier` (no secret). Returns the access token (and a rotating refresh token when the
|
|
47
|
+
* backend grants offline access) — pass `access_token` to {@link BabelconnectClient.connect}.
|
|
48
|
+
*/
|
|
49
|
+
export declare function authorizationCodeGrant(opts: {
|
|
50
|
+
serverUrl: string;
|
|
51
|
+
code: string;
|
|
52
|
+
redirectUri: string;
|
|
53
|
+
clientId?: string;
|
|
54
|
+
codeVerifier: string;
|
|
55
|
+
clientSecret?: string;
|
|
56
|
+
}): Promise<TokenResponse>;
|
|
1
57
|
/**
|
|
2
58
|
* Exchange an agent email/password for a bearer token via babelconnect-server's
|
|
3
59
|
* OAuth password-grant endpoint (`POST {serverUrl}/oauth/token`).
|
|
@@ -19,3 +75,23 @@ export declare function passwordGrant(opts: {
|
|
|
19
75
|
pass: string;
|
|
20
76
|
clientId?: string;
|
|
21
77
|
}): Promise<string>;
|
|
78
|
+
/**
|
|
79
|
+
* Revoke an OAuth token on sign-out (RFC 7009) via babelconnect-server's
|
|
80
|
+
* `POST {serverUrl}/oauth/revoke` (proxied same-origin like `/oauth/token`).
|
|
81
|
+
*
|
|
82
|
+
* Form-urlencoded `token` (+ `token_type_hint` and optional `clientId`); the
|
|
83
|
+
* bearer is also carried for backends that authenticate the revoke. Best-effort:
|
|
84
|
+
* revocation returns 200 even for an unknown token, so a non-2xx is ignored (the
|
|
85
|
+
* session is ending regardless) — only a transport error rejects.
|
|
86
|
+
*
|
|
87
|
+
* @param opts.serverUrl babelconnect-server origin, e.g. `https://agent.example.com`
|
|
88
|
+
* @param opts.token the token to revoke (also sent as the bearer)
|
|
89
|
+
* @param opts.tokenTypeHint RFC 7009 hint (defaults to `"access_token"`)
|
|
90
|
+
* @param opts.clientId optional OAuth client id
|
|
91
|
+
*/
|
|
92
|
+
export declare function revokeToken(opts: {
|
|
93
|
+
serverUrl: string;
|
|
94
|
+
token: string;
|
|
95
|
+
tokenTypeHint?: string;
|
|
96
|
+
clientId?: string;
|
|
97
|
+
}): Promise<void>;
|
package/dist/auth.js
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
+
/** The default OAuth public client id for the babelconnect agent app (no secret; PKCE). */
|
|
2
|
+
export const DEFAULT_CLIENT_ID = "babelconnect";
|
|
3
|
+
/** base64url-encode bytes without padding (RFC 4648 §5) — the encoding PKCE requires. */
|
|
4
|
+
function base64url(bytes) {
|
|
5
|
+
let bin = "";
|
|
6
|
+
for (const b of bytes)
|
|
7
|
+
bin += String.fromCharCode(b);
|
|
8
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generate a PKCE code verifier + S256 challenge (RFC 7636). Pass `codeChallenge` to
|
|
12
|
+
* {@link buildAuthorizeUrl} and keep `codeVerifier` to later exchange the returned code via
|
|
13
|
+
* {@link authorizationCodeGrant}. Uses the Web Crypto API (Node 18+ / browsers).
|
|
14
|
+
*/
|
|
15
|
+
export async function pkceChallenge() {
|
|
16
|
+
const codeVerifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
|
|
17
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
|
|
18
|
+
return { codeVerifier, codeChallenge: base64url(new Uint8Array(digest)), codeChallengeMethod: "S256" };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build the `GET {serverUrl}/oauth/authorize` URL that starts the Authorization Code + PKCE flow.
|
|
22
|
+
* Redirect the user to it; the backend redirects back to `redirectUri` with a `code` (and `state`).
|
|
23
|
+
*/
|
|
24
|
+
export function buildAuthorizeUrl(opts) {
|
|
25
|
+
const base = opts.serverUrl.replace(/\/+$/, "");
|
|
26
|
+
const params = new URLSearchParams({
|
|
27
|
+
response_type: "code",
|
|
28
|
+
client_id: opts.clientId ?? DEFAULT_CLIENT_ID,
|
|
29
|
+
redirect_uri: opts.redirectUri,
|
|
30
|
+
scope: opts.scope,
|
|
31
|
+
code_challenge: opts.codeChallenge,
|
|
32
|
+
code_challenge_method: opts.codeChallengeMethod ?? "S256",
|
|
33
|
+
});
|
|
34
|
+
if (opts.state !== undefined)
|
|
35
|
+
params.set("state", opts.state);
|
|
36
|
+
return `${base}/oauth/authorize?${params.toString()}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Exchange an authorization `code` (from the PKCE consent redirect) for tokens via the
|
|
40
|
+
* authorization_code grant against `POST {serverUrl}/oauth/token`. Public clients pass only
|
|
41
|
+
* `codeVerifier` (no secret). Returns the access token (and a rotating refresh token when the
|
|
42
|
+
* backend grants offline access) — pass `access_token` to {@link BabelconnectClient.connect}.
|
|
43
|
+
*/
|
|
44
|
+
export async function authorizationCodeGrant(opts) {
|
|
45
|
+
const base = opts.serverUrl.replace(/\/+$/, "");
|
|
46
|
+
const body = new URLSearchParams({
|
|
47
|
+
grant_type: "authorization_code",
|
|
48
|
+
code: opts.code,
|
|
49
|
+
redirect_uri: opts.redirectUri,
|
|
50
|
+
client_id: opts.clientId ?? DEFAULT_CLIENT_ID,
|
|
51
|
+
code_verifier: opts.codeVerifier,
|
|
52
|
+
});
|
|
53
|
+
if (opts.clientSecret !== undefined)
|
|
54
|
+
body.set("client_secret", opts.clientSecret);
|
|
55
|
+
const resp = await fetch(`${base}/oauth/token`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
58
|
+
body,
|
|
59
|
+
});
|
|
60
|
+
const json = (await resp.json().catch(() => ({})));
|
|
61
|
+
if (!resp.ok || !json.access_token) {
|
|
62
|
+
throw new Error(`authorization_code grant failed (status ${resp.status})`);
|
|
63
|
+
}
|
|
64
|
+
return json;
|
|
65
|
+
}
|
|
1
66
|
/**
|
|
2
67
|
* Exchange an agent email/password for a bearer token via babelconnect-server's
|
|
3
68
|
* OAuth password-grant endpoint (`POST {serverUrl}/oauth/token`).
|
|
@@ -32,3 +97,34 @@ export async function passwordGrant(opts) {
|
|
|
32
97
|
}
|
|
33
98
|
return json.access_token;
|
|
34
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Revoke an OAuth token on sign-out (RFC 7009) via babelconnect-server's
|
|
102
|
+
* `POST {serverUrl}/oauth/revoke` (proxied same-origin like `/oauth/token`).
|
|
103
|
+
*
|
|
104
|
+
* Form-urlencoded `token` (+ `token_type_hint` and optional `clientId`); the
|
|
105
|
+
* bearer is also carried for backends that authenticate the revoke. Best-effort:
|
|
106
|
+
* revocation returns 200 even for an unknown token, so a non-2xx is ignored (the
|
|
107
|
+
* session is ending regardless) — only a transport error rejects.
|
|
108
|
+
*
|
|
109
|
+
* @param opts.serverUrl babelconnect-server origin, e.g. `https://agent.example.com`
|
|
110
|
+
* @param opts.token the token to revoke (also sent as the bearer)
|
|
111
|
+
* @param opts.tokenTypeHint RFC 7009 hint (defaults to `"access_token"`)
|
|
112
|
+
* @param opts.clientId optional OAuth client id
|
|
113
|
+
*/
|
|
114
|
+
export async function revokeToken(opts) {
|
|
115
|
+
const base = opts.serverUrl.replace(/\/+$/, "");
|
|
116
|
+
const body = new URLSearchParams({
|
|
117
|
+
token: opts.token,
|
|
118
|
+
token_type_hint: opts.tokenTypeHint ?? "access_token",
|
|
119
|
+
});
|
|
120
|
+
if (opts.clientId)
|
|
121
|
+
body.set("client_id", opts.clientId);
|
|
122
|
+
await fetch(`${base}/oauth/revoke`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
126
|
+
Authorization: `Bearer ${opts.token}`,
|
|
127
|
+
},
|
|
128
|
+
body,
|
|
129
|
+
});
|
|
130
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface ConnectOptions {
|
|
|
8
8
|
token: string;
|
|
9
9
|
/** Per-call WebRTC leg. Defaults to {@link browserMediaFactory}; pass `null` for a control-only client. */
|
|
10
10
|
mediaFactory?: MediaFactory | null;
|
|
11
|
-
/** Auto-answer the agent's own OUTBOUND ringing leg (default `true`). INBOUND waits for {@link answerCall}. */
|
|
11
|
+
/** Auto-answer the agent's own OUTBOUND ringing leg (default `true`). INBOUND waits for {@link BabelconnectClient.answerCall}. */
|
|
12
12
|
autoAnswer?: boolean;
|
|
13
13
|
/** Out-of-band server notices (command rejections) + local media failures. */
|
|
14
14
|
onError?: (err: BcError) => void;
|
|
@@ -34,7 +34,18 @@ export declare class BabelconnectClient {
|
|
|
34
34
|
private readonly pending;
|
|
35
35
|
private closed;
|
|
36
36
|
private constructor();
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Open the session and start mirroring server state. Returns synchronously and queues intents until the
|
|
39
|
+
* stream is live, so you can subscribe and register immediately.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const bc = BabelconnectClient.connect({ serverUrl, token });
|
|
44
|
+
* bc.subscribe(render); // render(AgentView) on every update
|
|
45
|
+
* bc.register(); // announce reachability + load deployment data
|
|
46
|
+
* bc.placeCall("+15551234567"); // the result arrives as a callUpsert patch
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
38
49
|
static connect(opts: ConnectOptions): BabelconnectClient;
|
|
39
50
|
/** The current view (deep copy). */
|
|
40
51
|
get view(): AgentView;
|
|
@@ -42,6 +53,12 @@ export declare class BabelconnectClient {
|
|
|
42
53
|
subscribe(fn: (v: AgentView) => void): () => void;
|
|
43
54
|
/** The first active call, or undefined. */
|
|
44
55
|
activeCall(): CallState | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Announce the agent and load its deployment data (presence options, caller-ID numbers, phonebook, feature
|
|
58
|
+
* config) — call this once after {@link BabelconnectClient.subscribe}. It also marks the agent
|
|
59
|
+
* **WebRTC-reachable** on the backend, so a control-only client that takes calls should follow it with
|
|
60
|
+
* {@link BabelconnectClient.setWebrtc}(false) + {@link BabelconnectClient.setAgentNumber}.
|
|
61
|
+
*/
|
|
45
62
|
register(capabilities?: string[]): void;
|
|
46
63
|
placeCall(to: string, opts?: {
|
|
47
64
|
displayAsTo?: string;
|
|
@@ -51,10 +68,15 @@ export declare class BabelconnectClient {
|
|
|
51
68
|
}): void;
|
|
52
69
|
/** Manually answer a RINGING call by id (no-op under autoAnswer once already answered). */
|
|
53
70
|
answerCall(callId: string): Promise<void>;
|
|
71
|
+
/** End (or reject) a call by id. */
|
|
54
72
|
hangup(callId: string): void;
|
|
73
|
+
/** Mute or unmute the agent's own leg of a call. */
|
|
55
74
|
mute(callId: string, on: boolean): void;
|
|
75
|
+
/** Put a call on hold or retrieve it. */
|
|
56
76
|
hold(callId: string, on: boolean): void;
|
|
77
|
+
/** Send DTMF tones into the call (e.g. an IVR menu choice). Valid characters: `0`–`9`, `*`, `#`, `A`–`D`. */
|
|
57
78
|
sendDigits(callId: string, digits: string): void;
|
|
79
|
+
/** Set the outbound caller ID the consumer sees (choose from `agent.availableNumbers`). */
|
|
58
80
|
setDisplayAs(number: string): void;
|
|
59
81
|
/** Switch presence (the selector): "available" or a configured pause reason (see `AgentInfo.presenceOptions`). */
|
|
60
82
|
setPresence(name: string): void;
|
|
@@ -64,24 +86,40 @@ export declare class BabelconnectClient {
|
|
|
64
86
|
agentId?: string;
|
|
65
87
|
applicationId?: string;
|
|
66
88
|
}): void;
|
|
89
|
+
/** Open a conference around the current call. `hold` parks that call while you add members and consult. */
|
|
67
90
|
startConference(hold?: boolean): void;
|
|
91
|
+
/** Invite a participant — pass exactly one of `agentId` or `number`. Starts a conference first if none is active. */
|
|
68
92
|
addConferenceMember(opts: {
|
|
69
93
|
agentId?: string;
|
|
70
94
|
number?: string;
|
|
71
95
|
}): void;
|
|
96
|
+
/** Remove a member from the conference (moderator only). */
|
|
72
97
|
kickConferenceMember(memberId: string): void;
|
|
98
|
+
/** Hold or unhold an individual conference member (moderator only). */
|
|
73
99
|
holdConferenceMember(memberId: string, on: boolean): void;
|
|
100
|
+
/** Mute or unmute an individual conference member (moderator only). */
|
|
74
101
|
muteConferenceMember(memberId: string, on: boolean): void;
|
|
102
|
+
/** End the whole conference for everyone (moderator only). */
|
|
75
103
|
endConference(): void;
|
|
104
|
+
/** Drop only the agent's own leg; the other members stay connected. */
|
|
76
105
|
leaveConference(): void;
|
|
106
|
+
/** Add seconds to the after-call-work countdown (default 30). Show only when `wrapUp.canExtend`. */
|
|
77
107
|
wrapUpExtend(seconds?: number): void;
|
|
108
|
+
/** End after-call work early. Show only when `wrapUp.canCancel`. */
|
|
78
109
|
wrapUpCancel(): void;
|
|
110
|
+
/** Clear a blocked line (busy / unreachable / declined) so new calls reach the agent again. */
|
|
79
111
|
resetLineStatus(): void;
|
|
112
|
+
/** Begin recording the current call. Gated on `agent.canRecord`. */
|
|
80
113
|
startRecording(callId: string): void;
|
|
114
|
+
/** Stop the call's active recording. */
|
|
81
115
|
stopRecording(callId: string): void;
|
|
116
|
+
/** Toggle the "flagged" mark on the call's active recording. */
|
|
82
117
|
flagRecording(callId: string): void;
|
|
118
|
+
/** Replace the tags on the call's active recording (choose from `agent.availableTags`). */
|
|
83
119
|
setRecordingTags(callId: string, tags: string[]): void;
|
|
120
|
+
/** Turn the in-browser WebRTC phone on or off. With it off, set an agent number so calls bridge there. */
|
|
84
121
|
setWebrtc(on: boolean): void;
|
|
122
|
+
/** Set the agent's external phone number; the backend bridges calls there when WebRTC is off. */
|
|
85
123
|
setAgentNumber(number: string): void;
|
|
86
124
|
/** Send an SMS. `from` may be empty (server picks the default); `session` carries CTI/embedding correlation. */
|
|
87
125
|
sendSms(to: string, text: string, opts?: {
|
package/dist/client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createClient } from "@connectrpc/connect";
|
|
1
|
+
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, 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
|
|
@@ -28,7 +28,18 @@ export class BabelconnectClient {
|
|
|
28
28
|
});
|
|
29
29
|
this.rpc = createClient(Agent, transport);
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Open the session and start mirroring server state. Returns synchronously and queues intents until the
|
|
33
|
+
* stream is live, so you can subscribe and register immediately.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const bc = BabelconnectClient.connect({ serverUrl, token });
|
|
38
|
+
* bc.subscribe(render); // render(AgentView) on every update
|
|
39
|
+
* bc.register(); // announce reachability + load deployment data
|
|
40
|
+
* bc.placeCall("+15551234567"); // the result arrives as a callUpsert patch
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
32
43
|
static connect(opts) {
|
|
33
44
|
const c = new BabelconnectClient(opts);
|
|
34
45
|
void c.runSubscribe();
|
|
@@ -47,8 +58,16 @@ export class BabelconnectClient {
|
|
|
47
58
|
return this.cache.current.activeCalls[0];
|
|
48
59
|
}
|
|
49
60
|
// --- intents ---
|
|
61
|
+
/**
|
|
62
|
+
* Announce the agent and load its deployment data (presence options, caller-ID numbers, phonebook, feature
|
|
63
|
+
* config) — call this once after {@link BabelconnectClient.subscribe}. It also marks the agent
|
|
64
|
+
* **WebRTC-reachable** on the backend, so a control-only client that takes calls should follow it with
|
|
65
|
+
* {@link BabelconnectClient.setWebrtc}(false) + {@link BabelconnectClient.setAgentNumber}.
|
|
66
|
+
*/
|
|
50
67
|
register(capabilities = ["webrtc"]) {
|
|
51
|
-
this.send(new Command({
|
|
68
|
+
this.send(new Command({
|
|
69
|
+
command: { case: "register", value: new Register({ capabilities }) },
|
|
70
|
+
}));
|
|
52
71
|
}
|
|
53
72
|
placeCall(to, opts = {}) {
|
|
54
73
|
this.send(new Command({
|
|
@@ -70,24 +89,41 @@ export class BabelconnectClient {
|
|
|
70
89
|
if (call)
|
|
71
90
|
await this.doAnswer(call);
|
|
72
91
|
}
|
|
92
|
+
/** End (or reject) a call by id. */
|
|
73
93
|
hangup(callId) {
|
|
74
|
-
this.send(new Command({
|
|
94
|
+
this.send(new Command({
|
|
95
|
+
command: { case: "hangup", value: new Hangup({ callId }) },
|
|
96
|
+
}));
|
|
75
97
|
}
|
|
98
|
+
/** Mute or unmute the agent's own leg of a call. */
|
|
76
99
|
mute(callId, on) {
|
|
77
|
-
this.send(new Command({
|
|
100
|
+
this.send(new Command({
|
|
101
|
+
command: { case: "mute", value: new Mute({ callId, on }) },
|
|
102
|
+
}));
|
|
78
103
|
}
|
|
104
|
+
/** Put a call on hold or retrieve it. */
|
|
79
105
|
hold(callId, on) {
|
|
80
|
-
this.send(new Command({
|
|
106
|
+
this.send(new Command({
|
|
107
|
+
command: { case: "hold", value: new Hold({ callId, on }) },
|
|
108
|
+
}));
|
|
81
109
|
}
|
|
110
|
+
/** Send DTMF tones into the call (e.g. an IVR menu choice). Valid characters: `0`–`9`, `*`, `#`, `A`–`D`. */
|
|
82
111
|
sendDigits(callId, digits) {
|
|
83
|
-
this.send(new Command({
|
|
112
|
+
this.send(new Command({
|
|
113
|
+
command: { case: "dtmf", value: new SendDigits({ callId, digits }) },
|
|
114
|
+
}));
|
|
84
115
|
}
|
|
116
|
+
/** Set the outbound caller ID the consumer sees (choose from `agent.availableNumbers`). */
|
|
85
117
|
setDisplayAs(number) {
|
|
86
|
-
this.send(new Command({
|
|
118
|
+
this.send(new Command({
|
|
119
|
+
command: { case: "setDisplayAs", value: new SetDisplayAs({ number }) },
|
|
120
|
+
}));
|
|
87
121
|
}
|
|
88
122
|
/** Switch presence (the selector): "available" or a configured pause reason (see `AgentInfo.presenceOptions`). */
|
|
89
123
|
setPresence(name) {
|
|
90
|
-
this.send(new Command({
|
|
124
|
+
this.send(new Command({
|
|
125
|
+
command: { case: "setPresence", value: new SetPresence({ name }) },
|
|
126
|
+
}));
|
|
91
127
|
}
|
|
92
128
|
/** Transfer to exactly one of `to` (number), `agentId`, or `applicationId`; `warm` = attended. */
|
|
93
129
|
transfer(callId, to, opts = {}) {
|
|
@@ -105,78 +141,165 @@ export class BabelconnectClient {
|
|
|
105
141
|
}));
|
|
106
142
|
}
|
|
107
143
|
// --- Conferences ---
|
|
144
|
+
/** Open a conference around the current call. `hold` parks that call while you add members and consult. */
|
|
108
145
|
startConference(hold = false) {
|
|
109
|
-
this.send(new Command({
|
|
146
|
+
this.send(new Command({
|
|
147
|
+
command: {
|
|
148
|
+
case: "startConference",
|
|
149
|
+
value: new StartConference({ hold }),
|
|
150
|
+
},
|
|
151
|
+
}));
|
|
110
152
|
}
|
|
153
|
+
/** Invite a participant — pass exactly one of `agentId` or `number`. Starts a conference first if none is active. */
|
|
111
154
|
addConferenceMember(opts) {
|
|
112
155
|
this.send(new Command({
|
|
113
156
|
command: {
|
|
114
157
|
case: "addConferenceMember",
|
|
115
|
-
value: new AddConferenceMember({
|
|
158
|
+
value: new AddConferenceMember({
|
|
159
|
+
agentId: opts.agentId ?? "",
|
|
160
|
+
number: opts.number ?? "",
|
|
161
|
+
}),
|
|
116
162
|
},
|
|
117
163
|
}));
|
|
118
164
|
}
|
|
165
|
+
/** Remove a member from the conference (moderator only). */
|
|
119
166
|
kickConferenceMember(memberId) {
|
|
120
|
-
this.send(new Command({
|
|
167
|
+
this.send(new Command({
|
|
168
|
+
command: {
|
|
169
|
+
case: "kickConferenceMember",
|
|
170
|
+
value: new KickConferenceMember({ memberId }),
|
|
171
|
+
},
|
|
172
|
+
}));
|
|
121
173
|
}
|
|
174
|
+
/** Hold or unhold an individual conference member (moderator only). */
|
|
122
175
|
holdConferenceMember(memberId, on) {
|
|
123
|
-
this.send(new Command({
|
|
176
|
+
this.send(new Command({
|
|
177
|
+
command: {
|
|
178
|
+
case: "holdConferenceMember",
|
|
179
|
+
value: new HoldConferenceMember({ memberId, on }),
|
|
180
|
+
},
|
|
181
|
+
}));
|
|
124
182
|
}
|
|
183
|
+
/** Mute or unmute an individual conference member (moderator only). */
|
|
125
184
|
muteConferenceMember(memberId, on) {
|
|
126
|
-
this.send(new Command({
|
|
185
|
+
this.send(new Command({
|
|
186
|
+
command: {
|
|
187
|
+
case: "muteConferenceMember",
|
|
188
|
+
value: new MuteConferenceMember({ memberId, on }),
|
|
189
|
+
},
|
|
190
|
+
}));
|
|
127
191
|
}
|
|
192
|
+
/** End the whole conference for everyone (moderator only). */
|
|
128
193
|
endConference() {
|
|
129
|
-
this.send(new Command({
|
|
194
|
+
this.send(new Command({
|
|
195
|
+
command: { case: "endConference", value: new EndConference({}) },
|
|
196
|
+
}));
|
|
130
197
|
}
|
|
198
|
+
/** Drop only the agent's own leg; the other members stay connected. */
|
|
131
199
|
leaveConference() {
|
|
132
|
-
this.send(new Command({
|
|
200
|
+
this.send(new Command({
|
|
201
|
+
command: { case: "leaveConference", value: new LeaveConference({}) },
|
|
202
|
+
}));
|
|
133
203
|
}
|
|
204
|
+
/** Add seconds to the after-call-work countdown (default 30). Show only when `wrapUp.canExtend`. */
|
|
134
205
|
wrapUpExtend(seconds = 30) {
|
|
135
|
-
this.send(new Command({
|
|
206
|
+
this.send(new Command({
|
|
207
|
+
command: { case: "wrapUpExtend", value: new WrapUpExtend({ seconds }) },
|
|
208
|
+
}));
|
|
136
209
|
}
|
|
210
|
+
/** End after-call work early. Show only when `wrapUp.canCancel`. */
|
|
137
211
|
wrapUpCancel() {
|
|
138
|
-
this.send(new Command({
|
|
212
|
+
this.send(new Command({
|
|
213
|
+
command: { case: "wrapUpCancel", value: new WrapUpCancel({}) },
|
|
214
|
+
}));
|
|
139
215
|
}
|
|
216
|
+
/** Clear a blocked line (busy / unreachable / declined) so new calls reach the agent again. */
|
|
140
217
|
resetLineStatus() {
|
|
141
|
-
this.send(new Command({
|
|
218
|
+
this.send(new Command({
|
|
219
|
+
command: { case: "resetLineStatus", value: new ResetLineStatus({}) },
|
|
220
|
+
}));
|
|
142
221
|
}
|
|
222
|
+
/** Begin recording the current call. Gated on `agent.canRecord`. */
|
|
143
223
|
startRecording(callId) {
|
|
144
|
-
this.send(new Command({
|
|
224
|
+
this.send(new Command({
|
|
225
|
+
command: {
|
|
226
|
+
case: "startRecording",
|
|
227
|
+
value: new StartRecording({ callId }),
|
|
228
|
+
},
|
|
229
|
+
}));
|
|
145
230
|
}
|
|
231
|
+
/** Stop the call's active recording. */
|
|
146
232
|
stopRecording(callId) {
|
|
147
|
-
this.send(new Command({
|
|
233
|
+
this.send(new Command({
|
|
234
|
+
command: {
|
|
235
|
+
case: "stopRecording",
|
|
236
|
+
value: new StopRecording({ callId }),
|
|
237
|
+
},
|
|
238
|
+
}));
|
|
148
239
|
}
|
|
240
|
+
/** Toggle the "flagged" mark on the call's active recording. */
|
|
149
241
|
flagRecording(callId) {
|
|
150
|
-
this.send(new Command({
|
|
242
|
+
this.send(new Command({
|
|
243
|
+
command: {
|
|
244
|
+
case: "flagRecording",
|
|
245
|
+
value: new FlagRecording({ callId }),
|
|
246
|
+
},
|
|
247
|
+
}));
|
|
151
248
|
}
|
|
249
|
+
/** Replace the tags on the call's active recording (choose from `agent.availableTags`). */
|
|
152
250
|
setRecordingTags(callId, tags) {
|
|
153
|
-
this.send(new Command({
|
|
251
|
+
this.send(new Command({
|
|
252
|
+
command: {
|
|
253
|
+
case: "setRecordingTags",
|
|
254
|
+
value: new SetRecordingTags({ callId, tags }),
|
|
255
|
+
},
|
|
256
|
+
}));
|
|
154
257
|
}
|
|
258
|
+
/** Turn the in-browser WebRTC phone on or off. With it off, set an agent number so calls bridge there. */
|
|
155
259
|
setWebrtc(on) {
|
|
156
|
-
this.send(new Command({
|
|
260
|
+
this.send(new Command({
|
|
261
|
+
command: { case: "setWebrtc", value: new SetWebrtc({ on }) },
|
|
262
|
+
}));
|
|
157
263
|
}
|
|
264
|
+
/** Set the agent's external phone number; the backend bridges calls there when WebRTC is off. */
|
|
158
265
|
setAgentNumber(number) {
|
|
159
|
-
this.send(new Command({
|
|
266
|
+
this.send(new Command({
|
|
267
|
+
command: {
|
|
268
|
+
case: "setAgentNumber",
|
|
269
|
+
value: new SetAgentNumber({ number }),
|
|
270
|
+
},
|
|
271
|
+
}));
|
|
160
272
|
}
|
|
161
273
|
/** Send an SMS. `from` may be empty (server picks the default); `session` carries CTI/embedding correlation. */
|
|
162
274
|
sendSms(to, text, opts = {}) {
|
|
163
275
|
this.send(new Command({
|
|
164
276
|
command: {
|
|
165
277
|
case: "sendSms",
|
|
166
|
-
value: new SendSmsRequest({
|
|
278
|
+
value: new SendSmsRequest({
|
|
279
|
+
to,
|
|
280
|
+
text,
|
|
281
|
+
from: opts.from ?? "",
|
|
282
|
+
session: opts.session ?? {},
|
|
283
|
+
}),
|
|
167
284
|
},
|
|
168
285
|
}));
|
|
169
286
|
}
|
|
170
287
|
/** Open (reopen) or close (resolve) an SMS conversation. */
|
|
171
288
|
setConversationOpen(conversationId, open) {
|
|
172
289
|
this.send(new Command({
|
|
173
|
-
command: {
|
|
290
|
+
command: {
|
|
291
|
+
case: "setConversationOpen",
|
|
292
|
+
value: new SetConversationOpen({ conversationId, open }),
|
|
293
|
+
},
|
|
174
294
|
}));
|
|
175
295
|
}
|
|
176
296
|
/** Clear the unread count on an SMS conversation (the agent opened its thread). */
|
|
177
297
|
markConversationRead(conversationId) {
|
|
178
298
|
this.send(new Command({
|
|
179
|
-
command: {
|
|
299
|
+
command: {
|
|
300
|
+
case: "markConversationRead",
|
|
301
|
+
value: new MarkConversationRead({ conversationId }),
|
|
302
|
+
},
|
|
180
303
|
}));
|
|
181
304
|
}
|
|
182
305
|
/** Fetch a page of the agent's call history (the History tab). */
|
|
@@ -207,7 +330,9 @@ export class BabelconnectClient {
|
|
|
207
330
|
// --- internals ---
|
|
208
331
|
async runSubscribe() {
|
|
209
332
|
try {
|
|
210
|
-
for await (const u of this.rpc.subscribe(new SubscribeRequest({}), {
|
|
333
|
+
for await (const u of this.rpc.subscribe(new SubscribeRequest({}), {
|
|
334
|
+
signal: this.abort.signal,
|
|
335
|
+
})) {
|
|
211
336
|
this.onUpdate(u);
|
|
212
337
|
}
|
|
213
338
|
}
|
|
@@ -217,6 +342,10 @@ export class BabelconnectClient {
|
|
|
217
342
|
}
|
|
218
343
|
}
|
|
219
344
|
onUpdate(u) {
|
|
345
|
+
// Keepalive heartbeat: transport-only, no state — skip the cache/reconcile
|
|
346
|
+
// pass so a 15s heartbeat doesn't trigger a no-op reconcile every tick.
|
|
347
|
+
if (u.update.case === "keepalive")
|
|
348
|
+
return;
|
|
220
349
|
// First message = the snapshot ⇒ the session is registered server-side, so
|
|
221
350
|
// flush intents queued during connect() (register(), an early dial, …).
|
|
222
351
|
if (!this.ready) {
|
|
@@ -229,7 +358,8 @@ export class BabelconnectClient {
|
|
|
229
358
|
this.opts.onError?.(u.update.value);
|
|
230
359
|
return;
|
|
231
360
|
}
|
|
232
|
-
if (u.update.case === "patch" &&
|
|
361
|
+
if (u.update.case === "patch" &&
|
|
362
|
+
u.update.value.change.case === "notification") {
|
|
233
363
|
this.opts.onNotification?.(u.update.value.change.value);
|
|
234
364
|
return;
|
|
235
365
|
}
|
|
@@ -263,9 +393,15 @@ export class BabelconnectClient {
|
|
|
263
393
|
async doAnswer(call) {
|
|
264
394
|
if (this.answering.has(call.id) || this.media.has(call.id))
|
|
265
395
|
return;
|
|
266
|
-
const factory = this.opts.mediaFactory === undefined
|
|
396
|
+
const factory = this.opts.mediaFactory === undefined
|
|
397
|
+
? browserMediaFactory
|
|
398
|
+
: this.opts.mediaFactory;
|
|
267
399
|
if (!factory) {
|
|
268
|
-
this.opts.onError?.(new BcError({
|
|
400
|
+
this.opts.onError?.(new BcError({
|
|
401
|
+
code: "no_media",
|
|
402
|
+
message: "no media factory configured",
|
|
403
|
+
callId: call.id,
|
|
404
|
+
}));
|
|
269
405
|
return;
|
|
270
406
|
}
|
|
271
407
|
this.answering.add(call.id);
|
|
@@ -276,10 +412,19 @@ export class BabelconnectClient {
|
|
|
276
412
|
// undefined so the Media's own fallback applies.
|
|
277
413
|
const answerSdp = await media.answer(call.webrtcOffer, toRTCIceServers(call.iceServers));
|
|
278
414
|
this.media.set(call.id, media);
|
|
279
|
-
this.send(new Command({
|
|
415
|
+
this.send(new Command({
|
|
416
|
+
command: {
|
|
417
|
+
case: "answer",
|
|
418
|
+
value: new AnswerCall({ callId: call.id, sdp: answerSdp }),
|
|
419
|
+
},
|
|
420
|
+
}));
|
|
280
421
|
}
|
|
281
422
|
catch (e) {
|
|
282
|
-
this.opts.onError?.(new BcError({
|
|
423
|
+
this.opts.onError?.(new BcError({
|
|
424
|
+
code: "media_answer_failed",
|
|
425
|
+
message: String(e),
|
|
426
|
+
callId: call.id,
|
|
427
|
+
}));
|
|
283
428
|
}
|
|
284
429
|
finally {
|
|
285
430
|
this.answering.delete(call.id);
|
package/dist/embed/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `@babelforce/babelconnect-sdk/embed` — embed the babelconnect agent app (the
|
|
3
3
|
* server-served Flutter web app) into a host page via an `<iframe>` and a two-way
|
|
4
4
|
* `postMessage` bridge. See the Embedding guide at
|
|
5
|
-
* https://babelforce.github.io/babelconnect-sdk
|
|
5
|
+
* https://babelforce.github.io/babelconnect-sdk/ for the full protocol.
|
|
6
6
|
* The embedded app talks only to babelconnect-server.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
package/dist/embed/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `@babelforce/babelconnect-sdk/embed` — embed the babelconnect agent app (the
|
|
3
3
|
* server-served Flutter web app) into a host page via an `<iframe>` and a two-way
|
|
4
4
|
* `postMessage` bridge. See the Embedding guide at
|
|
5
|
-
* https://babelforce.github.io/babelconnect-sdk
|
|
5
|
+
* https://babelforce.github.io/babelconnect-sdk/ for the full protocol.
|
|
6
6
|
* The embedded app talks only to babelconnect-server.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
@@ -707,6 +707,15 @@ export declare class AgentInfo extends Message<AgentInfo> {
|
|
|
707
707
|
* @generated from field: string account_name = 17;
|
|
708
708
|
*/
|
|
709
709
|
accountName: string;
|
|
710
|
+
/**
|
|
711
|
+
* Involuntary line block: the ACD/platform marked the agent busy / unreachable /
|
|
712
|
+
* declined (a recoverable state cleared via ResetLineStatus) — distinct from a
|
|
713
|
+
* chosen "busy" presence the agent (or sign-out-as-busy) selected. Drives the
|
|
714
|
+
* line-blocked banner + Reset; a voluntary busy presence leaves this false.
|
|
715
|
+
*
|
|
716
|
+
* @generated from field: bool line_blocked = 18;
|
|
717
|
+
*/
|
|
718
|
+
lineBlocked: boolean;
|
|
710
719
|
constructor(data?: PartialMessage<AgentInfo>);
|
|
711
720
|
static readonly runtime: typeof proto3;
|
|
712
721
|
static readonly typeName = "babelconnect.v1.AgentInfo";
|
|
@@ -990,7 +999,9 @@ export declare class WrapUpStatus extends Message<WrapUpStatus> {
|
|
|
990
999
|
* open the client receives a snapshot; thereafter partial patches. `seq` is
|
|
991
1000
|
* monotonic across snapshot+patch — a gap means the client should resubscribe
|
|
992
1001
|
* for a fresh snapshot. `error` is an out-of-band notice (command rejection /
|
|
993
|
-
* server warning) and does NOT advance `seq`.
|
|
1002
|
+
* server warning) and does NOT advance `seq`. `keepalive` is an empty transport
|
|
1003
|
+
* heartbeat (see Keepalive) that likewise does NOT advance `seq` and carries no
|
|
1004
|
+
* state — client caches must ignore it.
|
|
994
1005
|
*
|
|
995
1006
|
* @generated from message babelconnect.v1.StateUpdate
|
|
996
1007
|
*/
|
|
@@ -1020,6 +1031,12 @@ export declare class StateUpdate extends Message<StateUpdate> {
|
|
|
1020
1031
|
*/
|
|
1021
1032
|
value: Error;
|
|
1022
1033
|
case: "error";
|
|
1034
|
+
} | {
|
|
1035
|
+
/**
|
|
1036
|
+
* @generated from field: babelconnect.v1.Keepalive keepalive = 5;
|
|
1037
|
+
*/
|
|
1038
|
+
value: Keepalive;
|
|
1039
|
+
case: "keepalive";
|
|
1023
1040
|
} | {
|
|
1024
1041
|
case: undefined;
|
|
1025
1042
|
value?: undefined;
|
|
@@ -1033,6 +1050,26 @@ export declare class StateUpdate extends Message<StateUpdate> {
|
|
|
1033
1050
|
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): StateUpdate;
|
|
1034
1051
|
static equals(a: StateUpdate | PlainMessage<StateUpdate> | undefined, b: StateUpdate | PlainMessage<StateUpdate> | undefined): boolean;
|
|
1035
1052
|
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Keepalive is an empty heartbeat frame sent on the Session/Subscribe streams so
|
|
1055
|
+
* idle connections through proxies/LBs are not closed as idle. Notably the AWS
|
|
1056
|
+
* Classic ELB in front of ingress-nginx (dev/prod EKS) has a 300s connection
|
|
1057
|
+
* idle timeout; without these frames a quiet agent's gRPC-web server-stream is
|
|
1058
|
+
* killed after 5 min. The server sends one every ~15s. It carries no state and
|
|
1059
|
+
* does NOT advance `seq`; client caches treat a keepalive StateUpdate as a no-op.
|
|
1060
|
+
*
|
|
1061
|
+
* @generated from message babelconnect.v1.Keepalive
|
|
1062
|
+
*/
|
|
1063
|
+
export declare class Keepalive extends Message<Keepalive> {
|
|
1064
|
+
constructor(data?: PartialMessage<Keepalive>);
|
|
1065
|
+
static readonly runtime: typeof proto3;
|
|
1066
|
+
static readonly typeName = "babelconnect.v1.Keepalive";
|
|
1067
|
+
static readonly fields: FieldList;
|
|
1068
|
+
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Keepalive;
|
|
1069
|
+
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Keepalive;
|
|
1070
|
+
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Keepalive;
|
|
1071
|
+
static equals(a: Keepalive | PlainMessage<Keepalive> | undefined, b: Keepalive | PlainMessage<Keepalive> | undefined): boolean;
|
|
1072
|
+
}
|
|
1036
1073
|
/**
|
|
1037
1074
|
* Patch is an entity-level delta applied mechanically by the client cache:
|
|
1038
1075
|
* replace the agent block, upsert/remove a call by id, or set wrap-up.
|
|
@@ -998,6 +998,15 @@ export class AgentInfo extends Message {
|
|
|
998
998
|
* @generated from field: string account_name = 17;
|
|
999
999
|
*/
|
|
1000
1000
|
accountName = "";
|
|
1001
|
+
/**
|
|
1002
|
+
* Involuntary line block: the ACD/platform marked the agent busy / unreachable /
|
|
1003
|
+
* declined (a recoverable state cleared via ResetLineStatus) — distinct from a
|
|
1004
|
+
* chosen "busy" presence the agent (or sign-out-as-busy) selected. Drives the
|
|
1005
|
+
* line-blocked banner + Reset; a voluntary busy presence leaves this false.
|
|
1006
|
+
*
|
|
1007
|
+
* @generated from field: bool line_blocked = 18;
|
|
1008
|
+
*/
|
|
1009
|
+
lineBlocked = false;
|
|
1001
1010
|
constructor(data) {
|
|
1002
1011
|
super();
|
|
1003
1012
|
proto3.util.initPartial(data, this);
|
|
@@ -1022,6 +1031,7 @@ export class AgentInfo extends Message {
|
|
|
1022
1031
|
{ no: 15, name: "username", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
1023
1032
|
{ no: 16, name: "account_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
1024
1033
|
{ no: 17, name: "account_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
1034
|
+
{ no: 18, name: "line_blocked", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
|
|
1025
1035
|
]);
|
|
1026
1036
|
static fromBinary(bytes, options) {
|
|
1027
1037
|
return new AgentInfo().fromBinary(bytes, options);
|
|
@@ -1428,7 +1438,9 @@ export class WrapUpStatus extends Message {
|
|
|
1428
1438
|
* open the client receives a snapshot; thereafter partial patches. `seq` is
|
|
1429
1439
|
* monotonic across snapshot+patch — a gap means the client should resubscribe
|
|
1430
1440
|
* for a fresh snapshot. `error` is an out-of-band notice (command rejection /
|
|
1431
|
-
* server warning) and does NOT advance `seq`.
|
|
1441
|
+
* server warning) and does NOT advance `seq`. `keepalive` is an empty transport
|
|
1442
|
+
* heartbeat (see Keepalive) that likewise does NOT advance `seq` and carries no
|
|
1443
|
+
* state — client caches must ignore it.
|
|
1432
1444
|
*
|
|
1433
1445
|
* @generated from message babelconnect.v1.StateUpdate
|
|
1434
1446
|
*/
|
|
@@ -1452,6 +1464,7 @@ export class StateUpdate extends Message {
|
|
|
1452
1464
|
{ no: 2, name: "snapshot", kind: "message", T: AgentView, oneof: "update" },
|
|
1453
1465
|
{ no: 3, name: "patch", kind: "message", T: Patch, oneof: "update" },
|
|
1454
1466
|
{ no: 4, name: "error", kind: "message", T: Error, oneof: "update" },
|
|
1467
|
+
{ no: 5, name: "keepalive", kind: "message", T: Keepalive, oneof: "update" },
|
|
1455
1468
|
]);
|
|
1456
1469
|
static fromBinary(bytes, options) {
|
|
1457
1470
|
return new StateUpdate().fromBinary(bytes, options);
|
|
@@ -1466,6 +1479,37 @@ export class StateUpdate extends Message {
|
|
|
1466
1479
|
return proto3.util.equals(StateUpdate, a, b);
|
|
1467
1480
|
}
|
|
1468
1481
|
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Keepalive is an empty heartbeat frame sent on the Session/Subscribe streams so
|
|
1484
|
+
* idle connections through proxies/LBs are not closed as idle. Notably the AWS
|
|
1485
|
+
* Classic ELB in front of ingress-nginx (dev/prod EKS) has a 300s connection
|
|
1486
|
+
* idle timeout; without these frames a quiet agent's gRPC-web server-stream is
|
|
1487
|
+
* killed after 5 min. The server sends one every ~15s. It carries no state and
|
|
1488
|
+
* does NOT advance `seq`; client caches treat a keepalive StateUpdate as a no-op.
|
|
1489
|
+
*
|
|
1490
|
+
* @generated from message babelconnect.v1.Keepalive
|
|
1491
|
+
*/
|
|
1492
|
+
export class Keepalive extends Message {
|
|
1493
|
+
constructor(data) {
|
|
1494
|
+
super();
|
|
1495
|
+
proto3.util.initPartial(data, this);
|
|
1496
|
+
}
|
|
1497
|
+
static runtime = proto3;
|
|
1498
|
+
static typeName = "babelconnect.v1.Keepalive";
|
|
1499
|
+
static fields = proto3.util.newFieldList(() => []);
|
|
1500
|
+
static fromBinary(bytes, options) {
|
|
1501
|
+
return new Keepalive().fromBinary(bytes, options);
|
|
1502
|
+
}
|
|
1503
|
+
static fromJson(jsonValue, options) {
|
|
1504
|
+
return new Keepalive().fromJson(jsonValue, options);
|
|
1505
|
+
}
|
|
1506
|
+
static fromJsonString(jsonString, options) {
|
|
1507
|
+
return new Keepalive().fromJsonString(jsonString, options);
|
|
1508
|
+
}
|
|
1509
|
+
static equals(a, b) {
|
|
1510
|
+
return proto3.util.equals(Keepalive, a, b);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1469
1513
|
/**
|
|
1470
1514
|
* Patch is an entity-level delta applied mechanically by the client cache:
|
|
1471
1515
|
* replace the agent block, upsert/remove a call by id, or set wrap-up.
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { BabelconnectClient, type ConnectOptions } from "./client.js";
|
|
13
13
|
export { StateCache } from "./state-cache.js";
|
|
14
|
-
export { passwordGrant } from "./auth.js";
|
|
14
|
+
export { passwordGrant, revokeToken, pkceChallenge, buildAuthorizeUrl, authorizationCodeGrant, DEFAULT_CLIENT_ID, type TokenResponse, type PkceChallenge, type AuthorizeUrlOptions, } from "./auth.js";
|
|
15
15
|
export { BrowserWebrtcMedia, browserMediaFactory, 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
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { BabelconnectClient } from "./client.js";
|
|
13
13
|
export { StateCache } from "./state-cache.js";
|
|
14
|
-
export { passwordGrant } from "./auth.js";
|
|
14
|
+
export { passwordGrant, revokeToken, pkceChallenge, buildAuthorizeUrl, authorizationCodeGrant, DEFAULT_CLIENT_ID, } from "./auth.js";
|
|
15
15
|
export { BrowserWebrtcMedia, browserMediaFactory } from "./media.js";
|
|
16
16
|
// The generated babelconnect.v1 messages + enums (AgentView, CallState, Command, …)
|
|
17
17
|
// and the Agent service descriptor.
|
package/dist/state-cache.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentView, type StateUpdate } from "./gen/babelconnect/v1/babelconnect_pb.js";
|
|
2
2
|
/**
|
|
3
|
-
* The client-side mirror of the server-authoritative
|
|
3
|
+
* The client-side mirror of the server-authoritative `AgentView`.
|
|
4
4
|
*
|
|
5
5
|
* It applies `StateUpdate` snapshots and entity-level patches **mechanically** —
|
|
6
6
|
* there is no domain logic here, by design: the server reduces, the client
|
package/dist/state-cache.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentView } from "./gen/babelconnect/v1/babelconnect_pb.js";
|
|
2
2
|
/**
|
|
3
|
-
* The client-side mirror of the server-authoritative
|
|
3
|
+
* The client-side mirror of the server-authoritative `AgentView`.
|
|
4
4
|
*
|
|
5
5
|
* It applies `StateUpdate` snapshots and entity-level patches **mechanically** —
|
|
6
6
|
* there is no domain logic here, by design: the server reduces, the client
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@babelforce/babelconnect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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
|
-
"homepage": "https://babelforce.github.io/babelconnect-sdk
|
|
6
|
+
"homepage": "https://babelforce.github.io/babelconnect-sdk/",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"files": [
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@bufbuild/protoc-gen-es": "^1.10.1",
|
|
41
41
|
"@connectrpc/protoc-gen-connect-es": "^1.6.1",
|
|
42
|
-
"typedoc": "
|
|
43
|
-
"typedoc-plugin-markdown": "
|
|
42
|
+
"typedoc": "~0.27.0",
|
|
43
|
+
"typedoc-plugin-markdown": "4.4.0",
|
|
44
44
|
"typescript": "^5.6.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|