@cosmoops/connect-sdk 0.46.0 → 0.48.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/CHANGELOG.md +52 -0
- package/dist/cjs/client/components/call-button.js +3 -2
- package/dist/cjs/client/portal-links.js +1 -1
- package/dist/cjs/errors.js +9 -0
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/server/handler.js +6 -1
- package/dist/cjs/server/http.js +4 -1
- package/dist/cjs/server/resources/calls.js +3 -2
- package/dist/cjs/server/resources/integrations.js +16 -0
- package/dist/cjs/server/resources/workspaces.js +9 -0
- package/dist/esm/client/components/call-button.js +3 -2
- package/dist/esm/client/portal-links.js +1 -1
- package/dist/esm/errors.js +9 -0
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/server/handler.js +6 -1
- package/dist/esm/server/http.js +4 -1
- package/dist/esm/server/resources/calls.js +3 -2
- package/dist/esm/server/resources/integrations.js +16 -0
- package/dist/esm/server/resources/workspaces.js +9 -0
- package/dist/types/client/components/call-button.d.ts +10 -3
- package/dist/types/client/portal-links.d.ts +1 -1
- package/dist/types/errors.d.ts +7 -0
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/server/resources/calls.d.ts +4 -2
- package/dist/types/server/resources/integrations.d.ts +33 -1
- package/dist/types/server/resources/workspaces.d.ts +9 -0
- package/dist/types/types.d.ts +20 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,58 @@
|
|
|
3
3
|
`@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
|
|
4
4
|
versions may carry breaking changes** and patch versions never do.
|
|
5
5
|
|
|
6
|
+
## 0.48.0
|
|
7
|
+
|
|
8
|
+
### Calls declare a purpose
|
|
9
|
+
|
|
10
|
+
`<ConnectCallButton purpose="promotional">`, `startCall({ phone, purpose })`
|
|
11
|
+
and `connect.calls.startBrowserCall({ phone, asUser, purpose })` take an
|
|
12
|
+
optional `ConnectCallPurpose` - `"service"`, `"transactional"` or
|
|
13
|
+
`"promotional"`. Connect records it on the call and places the call from a
|
|
14
|
+
workspace number allowed to carry it: in India a regular number carries
|
|
15
|
+
service and transactional calls only, and promotional calls need a 140-series
|
|
16
|
+
number with an approved DLT header. Omit it and the call is placed as before.
|
|
17
|
+
|
|
18
|
+
While the workspace's Connect deployment runs `CALL_PURPOSE_ENFORCEMENT=shadow`
|
|
19
|
+
(the default), a call no number may carry is still placed and only logged;
|
|
20
|
+
under `enforce` it is refused with a `412` whose message names the number
|
|
21
|
+
series needed. The bridge drops a purpose that is not one of the three
|
|
22
|
+
rather than forwarding it to be refused.
|
|
23
|
+
|
|
24
|
+
## 0.47.0
|
|
25
|
+
|
|
26
|
+
### `connect.integrations.request()` reaches the endpoints a toolkit has no action for
|
|
27
|
+
|
|
28
|
+
A Composio toolkit's action catalogue is not the provider's API: Zoho Books
|
|
29
|
+
ships no action to create a sales order, an estimate or an invoice.
|
|
30
|
+
`integrations.request(workspaceId, { toolkitSlug, method, endpoint, query, body })`
|
|
31
|
+
sends one request to the provider through the workspace's connected account,
|
|
32
|
+
under the `integrations:execute` scope - Connect still holds the credential.
|
|
33
|
+
|
|
34
|
+
`endpoint` is a path relative to the provider's API base (`/salesorders`).
|
|
35
|
+
Connect refuses an absolute URL, a query string in the path and dot segments,
|
|
36
|
+
and there is no way to set a header: whoever chooses the host chooses where
|
|
37
|
+
the credential goes. The call resolves whenever Connect relayed it - read the
|
|
38
|
+
provider's own `status` and `data` from the result.
|
|
39
|
+
|
|
40
|
+
### `initiateConnect` takes `connectionData`
|
|
41
|
+
|
|
42
|
+
Pre-fills a toolkit's own connection fields on the provider's screen.
|
|
43
|
+
`{ extension: "in" }` puts a Zoho connection on the India data centre, which
|
|
44
|
+
otherwise defaults to `zoho.com`.
|
|
45
|
+
|
|
46
|
+
### A 503 without `Retry-After` is no longer retried
|
|
47
|
+
|
|
48
|
+
The SDK retried every 503 on an idempotent request, twice, sleeping the
|
|
49
|
+
server's `Retry-After` between. Connect now leaves that header off a 503 it
|
|
50
|
+
knows will not heal - a dependency rejecting Connect's own credential, such
|
|
51
|
+
as the provider behind `connect.integrations.list()` - and the SDK now takes
|
|
52
|
+
that at its word: no `Retry-After`, no retry, and `ConnectError.isRetryable`
|
|
53
|
+
is `false`. A misconfigured dependency used to cost every read three round
|
|
54
|
+
trips and about four seconds of sleeping to return the same error. Every 503
|
|
55
|
+
Connect means to be retried still carries the header, so nothing that
|
|
56
|
+
recovers today stops recovering.
|
|
57
|
+
|
|
6
58
|
## 0.46.0
|
|
7
59
|
|
|
8
60
|
### `secondary` buttons are soft indigo
|
|
@@ -243,6 +243,7 @@ function ConnectCallProvider({ children, onCallEnded, onError, className, }) {
|
|
|
243
243
|
const client = await signIn();
|
|
244
244
|
const started = await mutateResource("/calls/start", {
|
|
245
245
|
phone: target.phone,
|
|
246
|
+
...(target.purpose ? { purpose: target.purpose } : {}),
|
|
246
247
|
});
|
|
247
248
|
callIdRef.current = started.callId;
|
|
248
249
|
setCall((current) => current ? { ...current, callId: started.callId, number: started.toNumber } : current);
|
|
@@ -308,9 +309,9 @@ function ConnectCallProvider({ children, onCallEnded, onError, className, }) {
|
|
|
308
309
|
* above it, and a member with "Make calls" - render it only for them, since a
|
|
309
310
|
* button the server refuses for everyone else helps nobody.
|
|
310
311
|
*/
|
|
311
|
-
function ConnectCallButton({ phone, name, onEnded, variant = "default", children, isDisabled, className, }) {
|
|
312
|
+
function ConnectCallButton({ phone, name, purpose, onEnded, variant = "default", children, isDisabled, className, }) {
|
|
312
313
|
const { isBusy, startCall, availability } = useConnectCall();
|
|
313
|
-
const press = () => void startCall({ phone, name, onEnded });
|
|
314
|
+
const press = () => void startCall({ phone, name, purpose, onEnded });
|
|
314
315
|
// Not offered at all to someone who may not call - a button that always
|
|
315
316
|
// refuses helps nobody.
|
|
316
317
|
if (availability?.status === "no_permission")
|
|
@@ -11,7 +11,7 @@ const provider_js_1 = require("./provider.js");
|
|
|
11
11
|
*
|
|
12
12
|
* Connect answers any of these paths for a signed-in user on the bare URL -
|
|
13
13
|
* these two params only add the `"Back to <your app>"` banner
|
|
14
|
-
* (`
|
|
14
|
+
* (the hand-off strip in `ShellAnnouncements` on Connect's side). It validates `return_to`
|
|
15
15
|
* against this `client_id`'s registered redirect origins and silently drops
|
|
16
16
|
* the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
|
|
17
17
|
* pairing degrades to what linking there did before rather than erroring.
|
package/dist/cjs/errors.js
CHANGED
|
@@ -37,8 +37,17 @@ class ConnectError extends Error {
|
|
|
37
37
|
* already retries these internally (see `ConnectClientOptions.maxRetries`);
|
|
38
38
|
* this is for callers deciding whether to queue their own retry after the
|
|
39
39
|
* SDK has given up.
|
|
40
|
+
*
|
|
41
|
+
* **A 503 is retryable only when it carries `Retry-After`.** Connect sends
|
|
42
|
+
* that header on every 503 it means to be retried - a dependency blip, a
|
|
43
|
+
* contended transaction - and leaves it off when the dependency is
|
|
44
|
+
* *misconfigured* (a revoked provider key, say), where every retry gets the
|
|
45
|
+
* identical answer. Retrying those anyway cost a page three round trips and
|
|
46
|
+
* two sleeps per read to learn nothing.
|
|
40
47
|
*/
|
|
41
48
|
get isRetryable() {
|
|
49
|
+
if (this.status === 503)
|
|
50
|
+
return this.retryAfterMs !== null;
|
|
42
51
|
return this.status === 0 || this.status === 429 || this.status >= 500;
|
|
43
52
|
}
|
|
44
53
|
/**
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createConnectHandler = createConnectHandler;
|
|
4
4
|
const errors_js_1 = require("../errors.js");
|
|
5
5
|
const identity_js_1 = require("./resources/identity.js");
|
|
6
|
+
/** The purposes `/calls/start` accepts - see `ConnectCallPurpose`. */
|
|
7
|
+
const CALL_PURPOSES = ["service", "transactional", "promotional"];
|
|
6
8
|
/**
|
|
7
9
|
* A sentinel origin to resolve a candidate destination against. Never reached -
|
|
8
10
|
* `.invalid` is reserved by RFC 2606 precisely so it cannot resolve.
|
|
@@ -1260,7 +1262,10 @@ function createConnectHandler(options) {
|
|
|
1260
1262
|
endpoint: "/calls/start",
|
|
1261
1263
|
});
|
|
1262
1264
|
}
|
|
1263
|
-
|
|
1265
|
+
// Passed on only when it is one of the three - Connect validates it
|
|
1266
|
+
// again, and an unknown value is dropped rather than sent to be refused.
|
|
1267
|
+
const purpose = CALL_PURPOSES.find((value) => value === body.purpose);
|
|
1268
|
+
return json(await client.calls.startBrowserCall({ phone: body.phone, asUser, purpose }), 201);
|
|
1264
1269
|
},
|
|
1265
1270
|
"POST /calls/cancel": async (request) => {
|
|
1266
1271
|
await requireWorkspace(request);
|
package/dist/cjs/server/http.js
CHANGED
|
@@ -189,7 +189,10 @@ async function request(options, spec) {
|
|
|
189
189
|
return (await readBody(response));
|
|
190
190
|
}
|
|
191
191
|
lastError = toConnectError(response, await readBody(response), endpoint);
|
|
192
|
-
|
|
192
|
+
// The status decides, and then the error's own verdict - which is what
|
|
193
|
+
// keeps a 503 sent without `Retry-After` from being replayed. See
|
|
194
|
+
// `ConnectError.isRetryable`.
|
|
195
|
+
if (!RETRYABLE_STATUS.has(response.status) || !lastError.isRetryable)
|
|
193
196
|
throw lastError;
|
|
194
197
|
}
|
|
195
198
|
finally {
|
|
@@ -118,13 +118,14 @@ class CallsResource {
|
|
|
118
118
|
*
|
|
119
119
|
* Throws `ConnectError` with status `412` when the workspace has no active
|
|
120
120
|
* number, is not allowed to place calls, or the member is already on one -
|
|
121
|
-
* the message is written to be shown to them.
|
|
121
|
+
* the message is written to be shown to them. With a `purpose`, also when
|
|
122
|
+
* no number may carry it and the workspace enforces purposes.
|
|
122
123
|
*/
|
|
123
124
|
startBrowserCall(input) {
|
|
124
125
|
return (0, http_js_1.request)(this.transport, {
|
|
125
126
|
method: "POST",
|
|
126
127
|
path: "/calls/start",
|
|
127
|
-
json: { phone: input.phone },
|
|
128
|
+
json: input.purpose ? { phone: input.phone, purpose: input.purpose } : { phone: input.phone },
|
|
128
129
|
headers: (0, acting_as_js_1.actingAs)(input.asUser),
|
|
129
130
|
});
|
|
130
131
|
}
|
|
@@ -56,6 +56,22 @@ class IntegrationsResource {
|
|
|
56
56
|
json: { workspaceId, toolkitSlug },
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* One raw request to a connected provider's own API, for an endpoint the
|
|
61
|
+
* toolkit has no action for (Zoho Books ships no "create sales order").
|
|
62
|
+
*
|
|
63
|
+
* **Resolves whenever Connect relayed the request**, whatever the provider
|
|
64
|
+
* answered: read `status` and `data` for that. A provider-side 4xx is a
|
|
65
|
+
* business answer ("that GSTIN is invalid") rather than a transport
|
|
66
|
+
* failure, so it is not thrown as one.
|
|
67
|
+
*/
|
|
68
|
+
async request(workspaceId, input) {
|
|
69
|
+
return (0, http_js_1.request)(this.transport, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
path: "/integrations/proxy",
|
|
72
|
+
json: { workspaceId, ...input },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
59
75
|
/** Calls a Composio action against this workspace's connected account for the named toolkit. */
|
|
60
76
|
async execute(workspaceId, input) {
|
|
61
77
|
return (0, http_js_1.request)(this.transport, {
|
|
@@ -113,11 +113,20 @@ class WorkspacesResource {
|
|
|
113
113
|
* Connect roles - so you never reimplement Connect's role resolution, and
|
|
114
114
|
* you are told nothing about the customer's Connect account beyond what your
|
|
115
115
|
* application needs.
|
|
116
|
+
*
|
|
117
|
+
* **Seat holders only, by default.** A workspace member with no seat on your
|
|
118
|
+
* application cannot sign in to it, so they are not part of your team and
|
|
119
|
+
* do not belong in an owner picker or an assignee list. Pass
|
|
120
|
+
* `includeUnassigned: true` from the screen where an admin *gives* a seat -
|
|
121
|
+
* it needs the people who do not have one yet. That form needs the signed-in
|
|
122
|
+
* member to be able to change access (a 403 otherwise), and every row
|
|
123
|
+
* carries `assigned` so the screen can tell the two apart.
|
|
116
124
|
*/
|
|
117
125
|
async members(options) {
|
|
118
126
|
const result = await (0, http_js_1.request)(this.transport, {
|
|
119
127
|
method: "GET",
|
|
120
128
|
path: "/workspaces/members",
|
|
129
|
+
query: { include: options.includeUnassigned ? "unassigned" : undefined },
|
|
121
130
|
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
122
131
|
});
|
|
123
132
|
return result.members;
|
|
@@ -203,6 +203,7 @@ export function ConnectCallProvider({ children, onCallEnded, onError, className,
|
|
|
203
203
|
const client = await signIn();
|
|
204
204
|
const started = await mutateResource("/calls/start", {
|
|
205
205
|
phone: target.phone,
|
|
206
|
+
...(target.purpose ? { purpose: target.purpose } : {}),
|
|
206
207
|
});
|
|
207
208
|
callIdRef.current = started.callId;
|
|
208
209
|
setCall((current) => current ? { ...current, callId: started.callId, number: started.toNumber } : current);
|
|
@@ -268,9 +269,9 @@ export function ConnectCallProvider({ children, onCallEnded, onError, className,
|
|
|
268
269
|
* above it, and a member with "Make calls" - render it only for them, since a
|
|
269
270
|
* button the server refuses for everyone else helps nobody.
|
|
270
271
|
*/
|
|
271
|
-
export function ConnectCallButton({ phone, name, onEnded, variant = "default", children, isDisabled, className, }) {
|
|
272
|
+
export function ConnectCallButton({ phone, name, purpose, onEnded, variant = "default", children, isDisabled, className, }) {
|
|
272
273
|
const { isBusy, startCall, availability } = useConnectCall();
|
|
273
|
-
const press = () => void startCall({ phone, name, onEnded });
|
|
274
|
+
const press = () => void startCall({ phone, name, purpose, onEnded });
|
|
274
275
|
// Not offered at all to someone who may not call - a button that always
|
|
275
276
|
// refuses helps nobody.
|
|
276
277
|
if (availability?.status === "no_permission")
|
|
@@ -7,7 +7,7 @@ import { useConnect } from "./provider.js";
|
|
|
7
7
|
*
|
|
8
8
|
* Connect answers any of these paths for a signed-in user on the bare URL -
|
|
9
9
|
* these two params only add the `"Back to <your app>"` banner
|
|
10
|
-
* (`
|
|
10
|
+
* (the hand-off strip in `ShellAnnouncements` on Connect's side). It validates `return_to`
|
|
11
11
|
* against this `client_id`'s registered redirect origins and silently drops
|
|
12
12
|
* the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
|
|
13
13
|
* pairing degrades to what linking there did before rather than erroring.
|
package/dist/esm/errors.js
CHANGED
|
@@ -33,8 +33,17 @@ export class ConnectError extends Error {
|
|
|
33
33
|
* already retries these internally (see `ConnectClientOptions.maxRetries`);
|
|
34
34
|
* this is for callers deciding whether to queue their own retry after the
|
|
35
35
|
* SDK has given up.
|
|
36
|
+
*
|
|
37
|
+
* **A 503 is retryable only when it carries `Retry-After`.** Connect sends
|
|
38
|
+
* that header on every 503 it means to be retried - a dependency blip, a
|
|
39
|
+
* contended transaction - and leaves it off when the dependency is
|
|
40
|
+
* *misconfigured* (a revoked provider key, say), where every retry gets the
|
|
41
|
+
* identical answer. Retrying those anyway cost a page three round trips and
|
|
42
|
+
* two sleeps per read to learn nothing.
|
|
36
43
|
*/
|
|
37
44
|
get isRetryable() {
|
|
45
|
+
if (this.status === 503)
|
|
46
|
+
return this.retryAfterMs !== null;
|
|
38
47
|
return this.status === 0 || this.status === 429 || this.status >= 500;
|
|
39
48
|
}
|
|
40
49
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ConnectError } from "../errors.js";
|
|
2
2
|
import { createPkcePair, createState } from "./resources/identity.js";
|
|
3
|
+
/** The purposes `/calls/start` accepts - see `ConnectCallPurpose`. */
|
|
4
|
+
const CALL_PURPOSES = ["service", "transactional", "promotional"];
|
|
3
5
|
/**
|
|
4
6
|
* A sentinel origin to resolve a candidate destination against. Never reached -
|
|
5
7
|
* `.invalid` is reserved by RFC 2606 precisely so it cannot resolve.
|
|
@@ -1257,7 +1259,10 @@ export function createConnectHandler(options) {
|
|
|
1257
1259
|
endpoint: "/calls/start",
|
|
1258
1260
|
});
|
|
1259
1261
|
}
|
|
1260
|
-
|
|
1262
|
+
// Passed on only when it is one of the three - Connect validates it
|
|
1263
|
+
// again, and an unknown value is dropped rather than sent to be refused.
|
|
1264
|
+
const purpose = CALL_PURPOSES.find((value) => value === body.purpose);
|
|
1265
|
+
return json(await client.calls.startBrowserCall({ phone: body.phone, asUser, purpose }), 201);
|
|
1261
1266
|
},
|
|
1262
1267
|
"POST /calls/cancel": async (request) => {
|
|
1263
1268
|
await requireWorkspace(request);
|
package/dist/esm/server/http.js
CHANGED
|
@@ -184,7 +184,10 @@ export async function request(options, spec) {
|
|
|
184
184
|
return (await readBody(response));
|
|
185
185
|
}
|
|
186
186
|
lastError = toConnectError(response, await readBody(response), endpoint);
|
|
187
|
-
|
|
187
|
+
// The status decides, and then the error's own verdict - which is what
|
|
188
|
+
// keeps a 503 sent without `Retry-After` from being replayed. See
|
|
189
|
+
// `ConnectError.isRetryable`.
|
|
190
|
+
if (!RETRYABLE_STATUS.has(response.status) || !lastError.isRetryable)
|
|
188
191
|
throw lastError;
|
|
189
192
|
}
|
|
190
193
|
finally {
|
|
@@ -115,13 +115,14 @@ export class CallsResource {
|
|
|
115
115
|
*
|
|
116
116
|
* Throws `ConnectError` with status `412` when the workspace has no active
|
|
117
117
|
* number, is not allowed to place calls, or the member is already on one -
|
|
118
|
-
* the message is written to be shown to them.
|
|
118
|
+
* the message is written to be shown to them. With a `purpose`, also when
|
|
119
|
+
* no number may carry it and the workspace enforces purposes.
|
|
119
120
|
*/
|
|
120
121
|
startBrowserCall(input) {
|
|
121
122
|
return request(this.transport, {
|
|
122
123
|
method: "POST",
|
|
123
124
|
path: "/calls/start",
|
|
124
|
-
json: { phone: input.phone },
|
|
125
|
+
json: input.purpose ? { phone: input.phone, purpose: input.purpose } : { phone: input.phone },
|
|
125
126
|
headers: actingAs(input.asUser),
|
|
126
127
|
});
|
|
127
128
|
}
|
|
@@ -53,6 +53,22 @@ export class IntegrationsResource {
|
|
|
53
53
|
json: { workspaceId, toolkitSlug },
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* One raw request to a connected provider's own API, for an endpoint the
|
|
58
|
+
* toolkit has no action for (Zoho Books ships no "create sales order").
|
|
59
|
+
*
|
|
60
|
+
* **Resolves whenever Connect relayed the request**, whatever the provider
|
|
61
|
+
* answered: read `status` and `data` for that. A provider-side 4xx is a
|
|
62
|
+
* business answer ("that GSTIN is invalid") rather than a transport
|
|
63
|
+
* failure, so it is not thrown as one.
|
|
64
|
+
*/
|
|
65
|
+
async request(workspaceId, input) {
|
|
66
|
+
return request(this.transport, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
path: "/integrations/proxy",
|
|
69
|
+
json: { workspaceId, ...input },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
56
72
|
/** Calls a Composio action against this workspace's connected account for the named toolkit. */
|
|
57
73
|
async execute(workspaceId, input) {
|
|
58
74
|
return request(this.transport, {
|
|
@@ -110,11 +110,20 @@ export class WorkspacesResource {
|
|
|
110
110
|
* Connect roles - so you never reimplement Connect's role resolution, and
|
|
111
111
|
* you are told nothing about the customer's Connect account beyond what your
|
|
112
112
|
* application needs.
|
|
113
|
+
*
|
|
114
|
+
* **Seat holders only, by default.** A workspace member with no seat on your
|
|
115
|
+
* application cannot sign in to it, so they are not part of your team and
|
|
116
|
+
* do not belong in an owner picker or an assignee list. Pass
|
|
117
|
+
* `includeUnassigned: true` from the screen where an admin *gives* a seat -
|
|
118
|
+
* it needs the people who do not have one yet. That form needs the signed-in
|
|
119
|
+
* member to be able to change access (a 403 otherwise), and every row
|
|
120
|
+
* carries `assigned` so the screen can tell the two apart.
|
|
113
121
|
*/
|
|
114
122
|
async members(options) {
|
|
115
123
|
const result = await request(this.transport, {
|
|
116
124
|
method: "GET",
|
|
117
125
|
path: "/workspaces/members",
|
|
126
|
+
query: { include: options.includeUnassigned ? "unassigned" : undefined },
|
|
118
127
|
headers: actingAs(options.asUser),
|
|
119
128
|
});
|
|
120
129
|
return result.members;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { CallingAvailability } from "../../types.js";
|
|
2
|
+
import type { CallingAvailability, ConnectCallPurpose } from "../../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Calling from the browser, through the workspace's own number.
|
|
5
5
|
*
|
|
@@ -25,7 +25,9 @@ import type { CallingAvailability } from "../../types.js";
|
|
|
25
25
|
* a number and the member must have "Make calls". What comes back lets this
|
|
26
26
|
* browser place exactly this call and no other.
|
|
27
27
|
* 3. The browser places it. Connect dials the number from the workspace's own
|
|
28
|
-
* number
|
|
28
|
+
* number. It is recorded only when the workspace has turned recording on
|
|
29
|
+
* (`connect.calls.getCallSettings`), and then the other party hears a
|
|
30
|
+
* disclosure before the conversation starts.
|
|
29
31
|
*
|
|
30
32
|
* ## Logging a call
|
|
31
33
|
*
|
|
@@ -58,6 +60,11 @@ export interface ConnectCallTarget {
|
|
|
58
60
|
phone: string;
|
|
59
61
|
/** Shown on the in-call bar. Defaults to the number. */
|
|
60
62
|
name?: string;
|
|
63
|
+
/**
|
|
64
|
+
* What the call is for - Connect places it from a number allowed to carry
|
|
65
|
+
* it. See `ConnectCallPurpose`; omit it to place the call as before.
|
|
66
|
+
*/
|
|
67
|
+
purpose?: ConnectCallPurpose;
|
|
61
68
|
/**
|
|
62
69
|
* Called once the call is over, with Connect's id for it - or `null` when it
|
|
63
70
|
* never got as far as being approved. Read the outcome on your server with
|
|
@@ -116,7 +123,7 @@ export interface ConnectCallButtonProps extends ConnectCallTarget {
|
|
|
116
123
|
* above it, and a member with "Make calls" - render it only for them, since a
|
|
117
124
|
* button the server refuses for everyone else helps nobody.
|
|
118
125
|
*/
|
|
119
|
-
export declare function ConnectCallButton({ phone, name, onEnded, variant, children, isDisabled, className, }: ConnectCallButtonProps): import("react").JSX.Element | null;
|
|
126
|
+
export declare function ConnectCallButton({ phone, name, purpose, onEnded, variant, children, isDisabled, className, }: ConnectCallButtonProps): import("react").JSX.Element | null;
|
|
120
127
|
export interface ConnectCallingStatusProps {
|
|
121
128
|
/** Merged with `cn`. */
|
|
122
129
|
className?: string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Connect answers any of these paths for a signed-in user on the bare URL -
|
|
7
7
|
* these two params only add the `"Back to <your app>"` banner
|
|
8
|
-
* (`
|
|
8
|
+
* (the hand-off strip in `ShellAnnouncements` on Connect's side). It validates `return_to`
|
|
9
9
|
* against this `client_id`'s registered redirect origins and silently drops
|
|
10
10
|
* the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
|
|
11
11
|
* pairing degrades to what linking there did before rather than erroring.
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export declare class ConnectError extends Error {
|
|
|
34
34
|
* already retries these internally (see `ConnectClientOptions.maxRetries`);
|
|
35
35
|
* this is for callers deciding whether to queue their own retry after the
|
|
36
36
|
* SDK has given up.
|
|
37
|
+
*
|
|
38
|
+
* **A 503 is retryable only when it carries `Retry-After`.** Connect sends
|
|
39
|
+
* that header on every 503 it means to be retried - a dependency blip, a
|
|
40
|
+
* contended transaction - and leaves it off when the dependency is
|
|
41
|
+
* *misconfigured* (a revoked provider key, say), where every retry gets the
|
|
42
|
+
* identical answer. Retrying those anyway cost a page three round trips and
|
|
43
|
+
* two sleeps per read to learn nothing.
|
|
37
44
|
*/
|
|
38
45
|
get isRetryable(): boolean;
|
|
39
46
|
/**
|
|
@@ -26,7 +26,7 @@ export type { OtpResource } from "./resources/otp.js";
|
|
|
26
26
|
export type { LogsResource } from "./resources/logs.js";
|
|
27
27
|
export type { AnalyticsResource } from "./resources/analytics.js";
|
|
28
28
|
export type { SessionsResource } from "./resources/sessions.js";
|
|
29
|
-
export type { ExecuteIntegrationActionInput, InitiateIntegrationConnectionInput, IntegrationsResource, } from "./resources/integrations.js";
|
|
29
|
+
export type { ExecuteIntegrationActionInput, InitiateIntegrationConnectionInput, IntegrationsResource, ProxyIntegrationRequestInput, } from "./resources/integrations.js";
|
|
30
30
|
export type { PlatformResource } from "./resources/platform.js";
|
|
31
31
|
export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, RescheduleBookingInput, } from "./resources/scheduling.js";
|
|
32
32
|
export type { ContactsResource, LookupContactInput } from "./resources/contacts.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { BrowserCallingToken, CallingAvailability, CallSettings, CallDetail, CallsPage, StartedBrowserCall } from "../../types.js";
|
|
2
|
+
import type { BrowserCallingToken, CallingAvailability, CallSettings, CallDetail, CallsPage, ConnectCallPurpose, StartedBrowserCall } from "../../types.js";
|
|
3
3
|
export interface ListCallsInput {
|
|
4
4
|
/** Narrow to one channel. A channel belonging to another application simply matches nothing. */
|
|
5
5
|
channelId?: string;
|
|
@@ -84,11 +84,13 @@ export declare class CallsResource {
|
|
|
84
84
|
*
|
|
85
85
|
* Throws `ConnectError` with status `412` when the workspace has no active
|
|
86
86
|
* number, is not allowed to place calls, or the member is already on one -
|
|
87
|
-
* the message is written to be shown to them.
|
|
87
|
+
* the message is written to be shown to them. With a `purpose`, also when
|
|
88
|
+
* no number may carry it and the workspace enforces purposes.
|
|
88
89
|
*/
|
|
89
90
|
startBrowserCall(input: {
|
|
90
91
|
phone: string;
|
|
91
92
|
asUser: string;
|
|
93
|
+
purpose?: ConnectCallPurpose;
|
|
92
94
|
}): Promise<StartedBrowserCall>;
|
|
93
95
|
/**
|
|
94
96
|
* Closes a call the browser never managed to place - recorded as
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationsListResponse } from "../../types.js";
|
|
2
|
+
import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationProxyMethod, IntegrationsListResponse, ProxyIntegrationRequestResponse } from "../../types.js";
|
|
3
3
|
export interface InitiateIntegrationConnectionInput {
|
|
4
4
|
toolkitSlug: string;
|
|
5
5
|
returnUrl: string;
|
|
@@ -13,6 +13,13 @@ export interface InitiateIntegrationConnectionInput {
|
|
|
13
13
|
* is present to finish the OAuth screen either.
|
|
14
14
|
*/
|
|
15
15
|
userId: string;
|
|
16
|
+
/**
|
|
17
|
+
* Pre-fills the toolkit's own connection fields on the provider screen -
|
|
18
|
+
* `{ extension: "in" }` puts a Zoho connection on the India data centre,
|
|
19
|
+
* which otherwise defaults to `zoho.com`. Which keys a toolkit reads is
|
|
20
|
+
* Composio's catalogue; Connect passes them through.
|
|
21
|
+
*/
|
|
22
|
+
connectionData?: Record<string, string>;
|
|
16
23
|
}
|
|
17
24
|
export interface ExecuteIntegrationActionInput {
|
|
18
25
|
toolkitSlug: string;
|
|
@@ -20,6 +27,21 @@ export interface ExecuteIntegrationActionInput {
|
|
|
20
27
|
/** Passed through to Composio verbatim - its shape is defined by the toolkit's own action schema, which Connect does not model. */
|
|
21
28
|
params?: Record<string, unknown>;
|
|
22
29
|
}
|
|
30
|
+
export interface ProxyIntegrationRequestInput {
|
|
31
|
+
toolkitSlug: string;
|
|
32
|
+
method: IntegrationProxyMethod;
|
|
33
|
+
/**
|
|
34
|
+
* A path relative to the provider's API base - `/salesorders`, not a URL.
|
|
35
|
+
* Connect refuses anything with a scheme, a host, a `?` or a dot segment,
|
|
36
|
+
* because whoever picks the host picks where the workspace's credential
|
|
37
|
+
* goes. Parameters belong in `query`.
|
|
38
|
+
*/
|
|
39
|
+
endpoint: string;
|
|
40
|
+
query?: Record<string, string>;
|
|
41
|
+
body?: unknown;
|
|
42
|
+
/** Whose connection to act through - omitted or `null` for the workspace's shared one. */
|
|
43
|
+
onBehalfOfUserId?: string | null;
|
|
44
|
+
}
|
|
23
45
|
/**
|
|
24
46
|
* Composio-hosted third-party connections (Google Calendar, Slack,
|
|
25
47
|
* Twitter/X, ...) a workspace has connected. Connect proxies every call
|
|
@@ -51,6 +73,16 @@ export declare class IntegrationsResource {
|
|
|
51
73
|
initiateConnect(workspaceId: string, input: InitiateIntegrationConnectionInput): Promise<InitiateIntegrationConnectionResponse>;
|
|
52
74
|
/** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
|
|
53
75
|
disconnect(workspaceId: string, toolkitSlug: string): Promise<DisconnectIntegrationResponse>;
|
|
76
|
+
/**
|
|
77
|
+
* One raw request to a connected provider's own API, for an endpoint the
|
|
78
|
+
* toolkit has no action for (Zoho Books ships no "create sales order").
|
|
79
|
+
*
|
|
80
|
+
* **Resolves whenever Connect relayed the request**, whatever the provider
|
|
81
|
+
* answered: read `status` and `data` for that. A provider-side 4xx is a
|
|
82
|
+
* business answer ("that GSTIN is invalid") rather than a transport
|
|
83
|
+
* failure, so it is not thrown as one.
|
|
84
|
+
*/
|
|
85
|
+
request(workspaceId: string, input: ProxyIntegrationRequestInput): Promise<ProxyIntegrationRequestResponse>;
|
|
54
86
|
/** Calls a Composio action against this workspace's connected account for the named toolkit. */
|
|
55
87
|
execute(workspaceId: string, input: ExecuteIntegrationActionInput): Promise<ExecuteIntegrationActionResponse>;
|
|
56
88
|
}
|
|
@@ -104,9 +104,18 @@ export declare class WorkspacesResource {
|
|
|
104
104
|
* Connect roles - so you never reimplement Connect's role resolution, and
|
|
105
105
|
* you are told nothing about the customer's Connect account beyond what your
|
|
106
106
|
* application needs.
|
|
107
|
+
*
|
|
108
|
+
* **Seat holders only, by default.** A workspace member with no seat on your
|
|
109
|
+
* application cannot sign in to it, so they are not part of your team and
|
|
110
|
+
* do not belong in an owner picker or an assignee list. Pass
|
|
111
|
+
* `includeUnassigned: true` from the screen where an admin *gives* a seat -
|
|
112
|
+
* it needs the people who do not have one yet. That form needs the signed-in
|
|
113
|
+
* member to be able to change access (a 403 otherwise), and every row
|
|
114
|
+
* carries `assigned` so the screen can tell the two apart.
|
|
107
115
|
*/
|
|
108
116
|
members(options: {
|
|
109
117
|
asUser: string;
|
|
118
|
+
includeUnassigned?: boolean;
|
|
110
119
|
}): Promise<ConnectWorkspaceMember[]>;
|
|
111
120
|
/**
|
|
112
121
|
* Seats, what the caller may do with them, and the invitations already
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1052,6 +1052,12 @@ export interface ExecuteIntegrationActionResponse {
|
|
|
1052
1052
|
data: unknown;
|
|
1053
1053
|
successful: boolean;
|
|
1054
1054
|
}
|
|
1055
|
+
export type IntegrationProxyMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
1056
|
+
/** See `IntegrationsResource.request` - `status` is the provider's, not Connect's. */
|
|
1057
|
+
export interface ProxyIntegrationRequestResponse {
|
|
1058
|
+
status: number;
|
|
1059
|
+
data: unknown;
|
|
1060
|
+
}
|
|
1055
1061
|
/**
|
|
1056
1062
|
* The metrics Connect can report for your application.
|
|
1057
1063
|
*
|
|
@@ -1912,6 +1918,12 @@ export interface ConnectWorkspaceMember {
|
|
|
1912
1918
|
*/
|
|
1913
1919
|
memberId: string;
|
|
1914
1920
|
userId: string;
|
|
1921
|
+
/**
|
|
1922
|
+
* Whether this member holds a seat on your application. Only assigned
|
|
1923
|
+
* members can sign in to it; unassigned rows appear only when you asked
|
|
1924
|
+
* for them with `members({ includeUnassigned: true })`, to assign from.
|
|
1925
|
+
*/
|
|
1926
|
+
assigned: boolean;
|
|
1915
1927
|
email: string;
|
|
1916
1928
|
displayName: string | null;
|
|
1917
1929
|
photoUrl: string | null;
|
|
@@ -2899,6 +2911,14 @@ export interface ConnectCallAnalysis {
|
|
|
2899
2911
|
* carry a one-time key, valid for two minutes, that lets the call be placed
|
|
2900
2912
|
* once and by nobody else.
|
|
2901
2913
|
*/
|
|
2914
|
+
/**
|
|
2915
|
+
* What a call is for, declared by the caller - Connect places it from a
|
|
2916
|
+
* workspace number allowed to carry it. In India the series decides: regular
|
|
2917
|
+
* numbers carry `service` and `transactional`, 140-series numbers carry
|
|
2918
|
+
* `promotional` only. Omit it and the call is placed as before and recorded
|
|
2919
|
+
* as undeclared.
|
|
2920
|
+
*/
|
|
2921
|
+
export type ConnectCallPurpose = "service" | "transactional" | "promotional";
|
|
2902
2922
|
export interface StartedBrowserCall {
|
|
2903
2923
|
callId: string;
|
|
2904
2924
|
/** The number being rung, E.164. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|