@cosmoops/connect-sdk 0.19.0 → 0.21.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 +158 -0
- package/README.md +16 -2
- package/dist/cjs/client/components/apps-grid.js +112 -23
- package/dist/cjs/client/components/identity.js +24 -0
- package/dist/cjs/client/components/one-tap-state.js +96 -0
- package/dist/cjs/client/components/one-tap.js +106 -0
- package/dist/cjs/client/components/plan-pricing.js +16 -0
- package/dist/cjs/client/components/plans.js +1 -1
- package/dist/cjs/client/components/subscribe-dialog.js +2 -1
- package/dist/cjs/client/components/user-menu.js +2 -6
- package/dist/cjs/client/index.js +11 -3
- package/dist/cjs/client/provider.js +88 -0
- package/dist/cjs/server/client.js +9 -1
- package/dist/cjs/server/handler.js +126 -2
- package/dist/cjs/server/resources/calls.js +57 -0
- package/dist/cjs/server/resources/channels.js +115 -0
- package/dist/cjs/server/resources/identity.js +40 -0
- package/dist/esm/client/components/apps-grid.js +115 -26
- package/dist/esm/client/components/identity.js +21 -0
- package/dist/esm/client/components/one-tap-state.js +90 -0
- package/dist/esm/client/components/one-tap.js +103 -0
- package/dist/esm/client/components/plan-pricing.js +15 -0
- package/dist/esm/client/components/plans.js +3 -3
- package/dist/esm/client/components/subscribe-dialog.js +3 -2
- package/dist/esm/client/components/user-menu.js +1 -5
- package/dist/esm/client/index.js +7 -0
- package/dist/esm/client/provider.js +88 -0
- package/dist/esm/server/client.js +9 -1
- package/dist/esm/server/handler.js +127 -3
- package/dist/esm/server/resources/calls.js +53 -0
- package/dist/esm/server/resources/channels.js +111 -0
- package/dist/esm/server/resources/identity.js +39 -0
- package/dist/types/client/components/apps-grid.d.ts +14 -7
- package/dist/types/client/components/identity.d.ts +17 -0
- package/dist/types/client/components/one-tap-state.d.ts +39 -0
- package/dist/types/client/components/one-tap.d.ts +50 -0
- package/dist/types/client/components/plan-pricing.d.ts +13 -0
- package/dist/types/client/index.d.ts +4 -2
- package/dist/types/client/provider.d.ts +81 -0
- package/dist/types/server/client.d.ts +7 -1
- package/dist/types/server/index.d.ts +2 -0
- package/dist/types/server/resources/calls.d.ts +45 -0
- package/dist/types/server/resources/channels.d.ts +91 -0
- package/dist/types/server/resources/identity.d.ts +35 -0
- package/dist/types/types.d.ts +186 -1
- package/package.json +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Continue as …" - a signed-out visitor who is already signed into Connect is
|
|
3
|
+
* offered their account, and one tap signs them in **without leaving the page**.
|
|
4
|
+
*
|
|
5
|
+
* The prompt is the visible half; what makes it worth having is that finding
|
|
6
|
+
* out costs one `fetch` rather than a redirect. `ConnectProviderProps.silentSignIn`
|
|
7
|
+
* answers the same question by bouncing the browser to Connect and back, which
|
|
8
|
+
* means a visible full-page navigation on the first load of every signed-out
|
|
9
|
+
* visit, nothing shown to the visitor while it happens, and no way to say no.
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <ConnectProvider>
|
|
13
|
+
* <App />
|
|
14
|
+
* <ConnectOneTap />
|
|
15
|
+
* </ConnectProvider>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* ### It only appears when it can actually help
|
|
19
|
+
*
|
|
20
|
+
* Nothing renders unless the visitor is signed out here, signed in to Connect,
|
|
21
|
+
* and has not recently dismissed the prompt or signed out of this application.
|
|
22
|
+
* A visitor with no Connect session sees nothing at all, ever - which is why
|
|
23
|
+
* this is safe to mount unconditionally, unlike `silentSignIn`, whose cost
|
|
24
|
+
* lands on exactly the people it cannot help.
|
|
25
|
+
*
|
|
26
|
+
* ### Two prompts, because consent is not assumed
|
|
27
|
+
*
|
|
28
|
+
* A visitor who has authorised this application before gets their name, email,
|
|
29
|
+
* workspace and photo, because Connect will release those to an application
|
|
30
|
+
* they have already agreed to. A visitor who has not gets an impersonal prompt
|
|
31
|
+
* and a tap that leads to the real consent screen - Connect does not hand over
|
|
32
|
+
* so much as a first name until that has happened once.
|
|
33
|
+
*
|
|
34
|
+
* ### Placement
|
|
35
|
+
*
|
|
36
|
+
* Bottom of the viewport on a phone, bottom-right from `sm:` up. Deliberately
|
|
37
|
+
* not top-right: on a phone that is where the browser chrome and the
|
|
38
|
+
* application's own header live, and this must never cover the thing the
|
|
39
|
+
* visitor came to use.
|
|
40
|
+
*/
|
|
41
|
+
export interface ConnectOneTapProps {
|
|
42
|
+
/**
|
|
43
|
+
* Where to send someone whose tap needs the interactive screen - the first
|
|
44
|
+
* connection to this application, or an account that has an MFA or
|
|
45
|
+
* subscription step outstanding. Defaults to the bridge's own `/signin`.
|
|
46
|
+
*/
|
|
47
|
+
onInteractiveSignIn?: () => void;
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function ConnectOneTap({ onInteractiveSignIn, className }: ConnectOneTapProps): import("react").JSX.Element | null;
|
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
* and nothing in it needs to be.
|
|
17
17
|
*/
|
|
18
18
|
import type { AppliedDiscount, Plan, SubscriptionResponse } from "../../types.js";
|
|
19
|
+
/**
|
|
20
|
+
* Whether a plan costs nothing - the SDK's copy of Connect's `isFreeSelection`
|
|
21
|
+
* (`packages/data/src/payments/free-plan.ts`), which is what the server
|
|
22
|
+
* actually decides the free-activation path with.
|
|
23
|
+
*
|
|
24
|
+
* A copy rather than an import for the reason `types.ts` gives: the published
|
|
25
|
+
* package carries none of Connect's internals. The rule is one line and does
|
|
26
|
+
* not drift, and `conformance.test.ts` pins the shape either side of it.
|
|
27
|
+
*
|
|
28
|
+
* @param addonsTotal Paise of the add-ons *selected*, not of the plan's whole
|
|
29
|
+
* catalog. A free plan with a paid add-on ticked is not free.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isFreePlan(price: number, addonsTotal?: number): boolean;
|
|
19
32
|
/** Paise are the wire unit everywhere in Connect; rupees are the display unit. */
|
|
20
33
|
export declare function formatPrice(paise: number): string;
|
|
21
34
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* change lands identically in all of them.
|
|
12
12
|
*/
|
|
13
13
|
export { ConnectProvider, useConnect, useConnectUser, useConnectWorkspaceType, useConnectAudience, useConnectPlatformAccess, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectCompanyProfile, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
|
|
14
|
-
export type { ConnectProviderProps, ConnectContextValue, ConnectSession } from "./provider.js";
|
|
14
|
+
export type { ConnectProviderProps, ConnectContextValue, ConnectSession, ConnectOneTapHint, ConnectOneTapFailure, } from "./provider.js";
|
|
15
15
|
export { blockingBlockers, normalizePlanFeatures } from "../helpers.js";
|
|
16
16
|
export type { WirePlanFeature } from "../helpers.js";
|
|
17
17
|
export { ConnectError, isConnectError } from "../errors.js";
|
|
@@ -23,6 +23,8 @@ export { ConnectLogo } from "./components/logo.js";
|
|
|
23
23
|
export type { ConnectLogoProps } from "./components/logo.js";
|
|
24
24
|
export { ConnectLoginButton, ConnectRegisterButton } from "./components/auth-buttons.js";
|
|
25
25
|
export type { ConnectAuthButtonProps } from "./components/auth-buttons.js";
|
|
26
|
+
export { ConnectOneTap } from "./components/one-tap.js";
|
|
27
|
+
export type { ConnectOneTapProps } from "./components/one-tap.js";
|
|
26
28
|
export { ConnectUserButton, ConnectProfileModal,
|
|
27
29
|
/** @deprecated Renamed to `ConnectUserButton`. */
|
|
28
30
|
ConnectUserMenu, ConnectManageSubscriptionButton, ConnectInvoicesButton, ConnectProfileButton, ConnectHomeButton, ConnectSignOutButton, } from "./components/user-menu.js";
|
|
@@ -82,7 +84,7 @@ export { ConnectFilterTabs } from "./components/filter-tabs.js";
|
|
|
82
84
|
export type { ConnectFilterTabsProps, ConnectFilterTabsOption } from "./components/filter-tabs.js";
|
|
83
85
|
export { useConnectCheckout, buildCheckoutUrl } from "./use-checkout.js";
|
|
84
86
|
export type { ConnectCheckoutStatus, ConnectCheckoutState, ConnectEntitlements, StartCheckoutOptions, CheckoutUrlOptions, } from "./use-checkout.js";
|
|
85
|
-
export { formatPrice, monthlyOf, resolveAction, annualSaving, discountLabel, ACTION_LABEL, groupPlansByTier, resolvePlanGroup, } from "./components/plan-pricing.js";
|
|
87
|
+
export { formatPrice, monthlyOf, resolveAction, annualSaving, discountLabel, ACTION_LABEL, isFreePlan, groupPlansByTier, resolvePlanGroup, } from "./components/plan-pricing.js";
|
|
86
88
|
export type { PlanAction, PlanTier, ComparablePlan } from "./components/plan-pricing.js";
|
|
87
89
|
export { ConnectWorkspaceSwitcher } from "./components/workspace-switcher.js";
|
|
88
90
|
export type { ConnectWorkspaceSwitcherProps } from "./components/workspace-switcher.js";
|
|
@@ -20,7 +20,52 @@ export interface ConnectSession {
|
|
|
20
20
|
* `null` on a signed-in session.
|
|
21
21
|
*/
|
|
22
22
|
audience: SessionAudience | null;
|
|
23
|
+
/**
|
|
24
|
+
* Where the browser may ask Connect about the visitor, or `null` when this
|
|
25
|
+
* deployment cannot run One Tap - see `IdentityResource.oneTapEndpoints`.
|
|
26
|
+
*
|
|
27
|
+
* Delivered on the session read the provider already makes, so discovering
|
|
28
|
+
* One Tap costs no extra request. Optional because an older bridge will not
|
|
29
|
+
* send it.
|
|
30
|
+
*/
|
|
31
|
+
oneTap?: {
|
|
32
|
+
hintUrl: string;
|
|
33
|
+
authorizeUrl: string;
|
|
34
|
+
} | null;
|
|
23
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* What Connect says about the visitor, before anything has been signed.
|
|
38
|
+
*
|
|
39
|
+
* Note what is *not* here when `consented` is `false`: no name, no email, no
|
|
40
|
+
* photo. Connect withholds all of it until the visitor has authorised this
|
|
41
|
+
* application at least once, so the first prompt an application ever shows is
|
|
42
|
+
* necessarily the impersonal one.
|
|
43
|
+
*/
|
|
44
|
+
export interface ConnectOneTapHint {
|
|
45
|
+
status: "signed_out" | "signed_in";
|
|
46
|
+
consented?: boolean;
|
|
47
|
+
user?: {
|
|
48
|
+
displayName: string | null;
|
|
49
|
+
email: string | null;
|
|
50
|
+
photoUrl: string | null;
|
|
51
|
+
};
|
|
52
|
+
workspace?: {
|
|
53
|
+
name: string;
|
|
54
|
+
} | null;
|
|
55
|
+
application?: {
|
|
56
|
+
name: string;
|
|
57
|
+
iconUrl: string | null;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Why a tap did not end in a session.
|
|
62
|
+
*
|
|
63
|
+
* Every one of these is ordinary traffic rather than a fault, which is why
|
|
64
|
+
* `oneTapSignIn` reports them as a value instead of throwing: `consent_required`
|
|
65
|
+
* and `interaction_required` both mean "this needs the interactive screen", and
|
|
66
|
+
* that is a hand-off, not an error to show anybody.
|
|
67
|
+
*/
|
|
68
|
+
export type ConnectOneTapFailure = "unavailable" | "login_required" | "consent_required" | "interaction_required" | "rate_limited" | "failed";
|
|
24
69
|
export interface ConnectContextValue {
|
|
25
70
|
/**
|
|
26
71
|
* Public OAuth client id, if `clientId` was passed to `<ConnectProvider>` -
|
|
@@ -89,6 +134,42 @@ export interface ConnectContextValue {
|
|
|
89
134
|
streamResource(path: string, body: unknown, options?: {
|
|
90
135
|
signal?: AbortSignal;
|
|
91
136
|
}): Promise<Response>;
|
|
137
|
+
/**
|
|
138
|
+
* Asks Connect whether this visitor is already signed in there, without
|
|
139
|
+
* navigating anywhere.
|
|
140
|
+
*
|
|
141
|
+
* This is the read `<ConnectOneTap>` is built on, and the reason One Tap
|
|
142
|
+
* beats `silentSignIn`: one credentialed `fetch` answers the question that
|
|
143
|
+
* used to cost a full-page redirect to Connect and back.
|
|
144
|
+
*
|
|
145
|
+
* It only works when the application is served from the **same site** as
|
|
146
|
+
* Connect - a `cosmoops.com` subdomain. `SameSite` is judged on the
|
|
147
|
+
* registrable domain, so the visitor's Connect session cookie rides along
|
|
148
|
+
* from `crm.cosmoops.com` and does not from anywhere else. Off-site
|
|
149
|
+
* deployments get `signed_out`, which is why every caller treats that answer
|
|
150
|
+
* as "say nothing" rather than "this visitor has no account".
|
|
151
|
+
*
|
|
152
|
+
* Resolves to `null` when the bridge offers no One Tap endpoints, or when the
|
|
153
|
+
* request itself fails - a Connect that cannot be reached says nothing about
|
|
154
|
+
* whether anyone is signed into it.
|
|
155
|
+
*/
|
|
156
|
+
readOneTapHint(): Promise<ConnectOneTapHint | null>;
|
|
157
|
+
/**
|
|
158
|
+
* Completes a One Tap sign-in, with no navigation at any point.
|
|
159
|
+
*
|
|
160
|
+
* Three requests: the bridge mints a PKCE pair and keeps the verifier, Connect
|
|
161
|
+
* hands back an authorization code, the bridge exchanges it and stores the
|
|
162
|
+
* session. The page only ever holds the code, which is worthless without the
|
|
163
|
+
* verifier it never sees.
|
|
164
|
+
*
|
|
165
|
+
* Refreshes the session on success, so consumers re-render signed in.
|
|
166
|
+
*/
|
|
167
|
+
oneTapSignIn(): Promise<{
|
|
168
|
+
ok: true;
|
|
169
|
+
} | {
|
|
170
|
+
ok: false;
|
|
171
|
+
reason: ConnectOneTapFailure;
|
|
172
|
+
}>;
|
|
92
173
|
}
|
|
93
174
|
export interface ConnectProviderProps {
|
|
94
175
|
/**
|
|
@@ -6,6 +6,8 @@ import { DataResource } from "./resources/data.js";
|
|
|
6
6
|
import { WorkspacesResource } from "./resources/workspaces.js";
|
|
7
7
|
import { SchedulingResource } from "./resources/scheduling.js";
|
|
8
8
|
import { ContactsResource } from "./resources/contacts.js";
|
|
9
|
+
import { ChannelsResource } from "./resources/channels.js";
|
|
10
|
+
import { CallsResource } from "./resources/calls.js";
|
|
9
11
|
import { KvResource } from "./resources/kv.js";
|
|
10
12
|
import { BillingResource } from "./resources/billing.js";
|
|
11
13
|
import { DriveResource } from "./resources/drive.js";
|
|
@@ -81,6 +83,10 @@ export declare class ConnectClient {
|
|
|
81
83
|
readonly scheduling: SchedulingResource;
|
|
82
84
|
/** Who a workspace already knows how to reach, by phone or by id. */
|
|
83
85
|
readonly contacts: ContactsResource;
|
|
86
|
+
/** The phone numbers Connect has assigned to your application, and where each one routes. */
|
|
87
|
+
readonly channels: ChannelsResource;
|
|
88
|
+
/** The calls that arrived on those channels, with their transcripts. */
|
|
89
|
+
readonly calls: CallsResource;
|
|
84
90
|
/** RAG chat and structured extraction. */
|
|
85
91
|
readonly ai: AiResource;
|
|
86
92
|
/** Your application's own document store - see `DataResource`. */
|
|
@@ -115,4 +121,4 @@ export declare class ConnectClient {
|
|
|
115
121
|
constructor(options: ConnectClientOptions);
|
|
116
122
|
}
|
|
117
123
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
118
|
-
export declare const SDK_VERSION = "0.
|
|
124
|
+
export declare const SDK_VERSION = "0.21.0";
|
|
@@ -29,6 +29,8 @@ export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./reso
|
|
|
29
29
|
export type { PlatformResource } from "./resources/platform.js";
|
|
30
30
|
export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, RescheduleBookingInput, } from "./resources/scheduling.js";
|
|
31
31
|
export type { ContactsResource, LookupContactInput } from "./resources/contacts.js";
|
|
32
|
+
export type { ChannelsResource, ListChannelsInput, ClaimChannelInput, } from "./resources/channels.js";
|
|
33
|
+
export type { CallsResource, ListCallsInput } from "./resources/calls.js";
|
|
32
34
|
/**
|
|
33
35
|
* Webhook verification and a ready-made POST route. Server-only for the
|
|
34
36
|
* obvious reason: the signing secret must never reach a browser bundle.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type TransportOptions } from "../http.js";
|
|
2
|
+
import type { CallDetail, CallsPage } from "../../types.js";
|
|
3
|
+
export interface ListCallsInput {
|
|
4
|
+
/** Narrow to one channel. A channel belonging to another application simply matches nothing. */
|
|
5
|
+
channelId?: string;
|
|
6
|
+
/** From a previous page's `nextCursor`. */
|
|
7
|
+
cursor?: string;
|
|
8
|
+
/** 1-100, default 50. */
|
|
9
|
+
limit?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Every call that arrived on a channel assigned to your application.
|
|
13
|
+
*
|
|
14
|
+
* The application is resolved from your API key and is never a parameter, so
|
|
15
|
+
* there is no request that reads another partner's calls.
|
|
16
|
+
*
|
|
17
|
+
* `channelId` is the only filter, and that is a budget decision rather than an
|
|
18
|
+
* oversight: each filter costs a Firestore composite index against a project
|
|
19
|
+
* already near its ceiling. Filter the rest client-side, or read the
|
|
20
|
+
* aggregates from `analytics`.
|
|
21
|
+
*/
|
|
22
|
+
export declare class CallsResource {
|
|
23
|
+
private readonly transport;
|
|
24
|
+
constructor(transport: TransportOptions);
|
|
25
|
+
/**
|
|
26
|
+
* Your calls, newest first, across all of your subscribing workspaces.
|
|
27
|
+
*
|
|
28
|
+
* **Page until `hasMore` is `false`; do not stop on an empty `items`.** A
|
|
29
|
+
* page can come back empty with more behind it.
|
|
30
|
+
*/
|
|
31
|
+
list(input?: ListCallsInput): Promise<CallsPage>;
|
|
32
|
+
/**
|
|
33
|
+
* One call and every transcript turn, ordered by `sequence` - which is the
|
|
34
|
+
* sort to trust, since two turns can share a millisecond.
|
|
35
|
+
*
|
|
36
|
+
* `recordingFileId` and `transcriptFileId` are Drive file ids, readable with
|
|
37
|
+
* a `drive:read` key. Both are `null` until the carrier posts the audio,
|
|
38
|
+
* which is the normal state for a call that has only just ended - branch on
|
|
39
|
+
* `recordingStatus` rather than treating `null` as a failure.
|
|
40
|
+
*
|
|
41
|
+
* Throws `ConnectError` with `code: "not_found"` for a call that does not
|
|
42
|
+
* exist *or* belongs to another application.
|
|
43
|
+
*/
|
|
44
|
+
get(callId: string): Promise<CallDetail>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type TransportOptions } from "../http.js";
|
|
2
|
+
import type { ChannelKind, ChannelRoutingRecord, ChannelsPage, ClaimedChannel, ConnectChannel } from "../../types.js";
|
|
3
|
+
export interface ListChannelsInput {
|
|
4
|
+
/** Narrow to one workspace. Omit to list every channel assigned to your application. */
|
|
5
|
+
workspaceId?: string;
|
|
6
|
+
/** From a previous page's `nextCursor`. */
|
|
7
|
+
cursor?: string;
|
|
8
|
+
/** 1-100, default 50. */
|
|
9
|
+
limit?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ClaimChannelInput {
|
|
12
|
+
workspaceId: string;
|
|
13
|
+
/** Defaults to `"voice"`, the only kind Connect stocks inventory for today. */
|
|
14
|
+
kind?: ChannelKind;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The phone numbers Connect has assigned to your application.
|
|
18
|
+
*
|
|
19
|
+
* **Inventory is platform-owned.** A number is a regulated asset bought under
|
|
20
|
+
* Connect's carrier account and reassignable between workspaces, so your
|
|
21
|
+
* application never provisions, prices or releases one - it claims from a pool
|
|
22
|
+
* it cannot see, and reads back what it was given. That is why `claim` takes a
|
|
23
|
+
* workspace and a kind rather than a number, and why nothing here exposes a
|
|
24
|
+
* carrier, a provider reference or a monthly rate.
|
|
25
|
+
*
|
|
26
|
+
* The application is resolved from your API key and is never a parameter, so
|
|
27
|
+
* no request shape here can reach another partner's channels. A channel
|
|
28
|
+
* belonging to someone else answers `404`, not `403` - confirming it exists
|
|
29
|
+
* would make these reads an oracle for enumerating their channel ids.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ChannelsResource {
|
|
32
|
+
private readonly transport;
|
|
33
|
+
constructor(transport: TransportOptions);
|
|
34
|
+
/**
|
|
35
|
+
* Every channel assigned to your application, across all of your subscribing
|
|
36
|
+
* workspaces.
|
|
37
|
+
*
|
|
38
|
+
* Page until `hasMore` is `false`.
|
|
39
|
+
*/
|
|
40
|
+
list(input?: ListChannelsInput): Promise<ChannelsPage>;
|
|
41
|
+
/**
|
|
42
|
+
* The channel behind an id you already hold - a webhook's `channelId`, or a
|
|
43
|
+
* call record's.
|
|
44
|
+
*
|
|
45
|
+
* Throws `ConnectError` with `code: "not_found"` for an id that does not
|
|
46
|
+
* exist *or* belongs to another application.
|
|
47
|
+
*/
|
|
48
|
+
get(channelId: string): Promise<ConnectChannel>;
|
|
49
|
+
/**
|
|
50
|
+
* Takes a number out of Connect's inventory and binds it to one of your
|
|
51
|
+
* subscribing workspaces.
|
|
52
|
+
*
|
|
53
|
+
* **You do not pick the number.** A workspace may hold exactly one, across
|
|
54
|
+
* every application it uses, and it is then fixed for the life of the
|
|
55
|
+
* engagement - there is no re-claim and no swap. Only a Connect operator can
|
|
56
|
+
* unassign one.
|
|
57
|
+
*
|
|
58
|
+
* Two different `409`s, and they need different handling: the workspace
|
|
59
|
+
* already holds a channel of this kind (fix the call), or the pool is empty
|
|
60
|
+
* right now (retry - Connect is notified, and this is not permanent).
|
|
61
|
+
*
|
|
62
|
+
* The claimed channel comes back without routing, because it has none yet.
|
|
63
|
+
* Call `setRouting` next, or the number refuses every call that reaches it.
|
|
64
|
+
*/
|
|
65
|
+
claim(input: ClaimChannelInput): Promise<ClaimedChannel>;
|
|
66
|
+
/**
|
|
67
|
+
* Points a channel at wherever inbound conversations should go.
|
|
68
|
+
*
|
|
69
|
+
* `destination` is yours: Connect stores it verbatim and never reads into
|
|
70
|
+
* it. It **replaces** the stored record rather than merging, so send the
|
|
71
|
+
* whole destination each time; it must serialize to 16 KB or fewer.
|
|
72
|
+
*
|
|
73
|
+
* Throws `code: "conflict"` when the channel is suspended or released - use
|
|
74
|
+
* `clearRouting` to stop a suspended number answering, which is deliberately
|
|
75
|
+
* still allowed.
|
|
76
|
+
*/
|
|
77
|
+
setRouting(channelId: string, destination: Record<string, unknown>): Promise<ChannelRoutingRecord>;
|
|
78
|
+
/**
|
|
79
|
+
* Takes a number out of service without giving it up - the channel stays
|
|
80
|
+
* assigned to you and stays billable, and inbound calls are refused until
|
|
81
|
+
* you write routing again.
|
|
82
|
+
*
|
|
83
|
+
* This is the only way to say "stop answering": overwriting `destination`
|
|
84
|
+
* can only ever point the number somewhere *else*, and unassigning it is a
|
|
85
|
+
* Console action that returns the number to Connect's inventory.
|
|
86
|
+
*
|
|
87
|
+
* Idempotent - clearing already-clear routing answers `200` with
|
|
88
|
+
* `routing: null`, so a retry after a lost response is safe.
|
|
89
|
+
*/
|
|
90
|
+
clearRouting(channelId: string): Promise<ChannelRoutingRecord>;
|
|
91
|
+
}
|
|
@@ -27,6 +27,24 @@ export interface ExchangeCodeInput {
|
|
|
27
27
|
}
|
|
28
28
|
/** A URL-safe random string, suitable for `state` or a PKCE verifier. */
|
|
29
29
|
export declare function createState(byteLength?: number): string;
|
|
30
|
+
/** One Tap's PKCE pair - `challenge` is `BASE64URL(SHA256(verifier))`, RFC 7636 S256. */
|
|
31
|
+
export interface PkcePair {
|
|
32
|
+
verifier: string;
|
|
33
|
+
challenge: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Mints a PKCE pair for the One Tap flow.
|
|
37
|
+
*
|
|
38
|
+
* The redirect flow can take PKCE or leave it, because the code is delivered
|
|
39
|
+
* to the application's server on a redirect the browser only forwards. One Tap
|
|
40
|
+
* hands the code *to the page*, so the verifier is the thing that makes a
|
|
41
|
+
* stolen code worthless - it stays in an `HttpOnly` cookie on the application's
|
|
42
|
+
* own origin and is never sent to the browser.
|
|
43
|
+
*
|
|
44
|
+
* `crypto.subtle` rather than a Node hash, so this stays isomorphic and the
|
|
45
|
+
* `./next` entry keeps loading on the edge runtime as well as Node.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createPkcePair(): Promise<PkcePair>;
|
|
30
48
|
/**
|
|
31
49
|
* "Login with Connect" - the OAuth2 handshake, and the session that outlives
|
|
32
50
|
* it.
|
|
@@ -42,6 +60,23 @@ export declare class IdentityResource {
|
|
|
42
60
|
constructor(transport: TransportOptions, clientId: string | null, appBaseUrl: string);
|
|
43
61
|
/** Step 1 - the URL to send the user's browser to. */
|
|
44
62
|
authorizeUrl(input: AuthorizeUrlInput): string;
|
|
63
|
+
/**
|
|
64
|
+
* The two Connect endpoints One Tap talks to **from the browser**, or `null`
|
|
65
|
+
* when this client has no `clientId` configured and the flow cannot run.
|
|
66
|
+
*
|
|
67
|
+
* Handed out by the bridge rather than built in the page, so One Tap needs no
|
|
68
|
+
* client-side configuration at all - a `./next` application sets
|
|
69
|
+
* `CONNECT_CLIENT_ID` server-side and nothing else. Both URLs carry only the
|
|
70
|
+
* public `client_id`; neither is a credential.
|
|
71
|
+
*
|
|
72
|
+
* Returning `null` rather than throwing (as `authorizeUrl` does) because this
|
|
73
|
+
* is read on the `/session` route, which must keep answering for an
|
|
74
|
+
* application that has no OAuth client configured at all.
|
|
75
|
+
*/
|
|
76
|
+
oneTapEndpoints(): {
|
|
77
|
+
hintUrl: string;
|
|
78
|
+
authorizeUrl: string;
|
|
79
|
+
} | null;
|
|
45
80
|
/** Step 2 - trade the one-time `code` for a session. Server-side only. */
|
|
46
81
|
exchangeCode(input: ExchangeCodeInput): Promise<TokenResponse>;
|
|
47
82
|
/**
|
package/dist/types/types.d.ts
CHANGED
|
@@ -549,13 +549,26 @@ export interface WorkspaceApp {
|
|
|
549
549
|
iconUrl: string | null;
|
|
550
550
|
/** A `lucide-react` icon name from Connect's curated set. */
|
|
551
551
|
icon: string;
|
|
552
|
+
/** Catalog classification, if you want to group or filter the grid by it. */
|
|
553
|
+
category: WorkspaceAppCategory;
|
|
552
554
|
/** Where a tile navigates. `null` when the application registered no site. */
|
|
553
555
|
launchUrl: string | null;
|
|
556
|
+
/**
|
|
557
|
+
* Whether this workspace holds a launchable subscription.
|
|
558
|
+
*
|
|
559
|
+
* `/apps` answers with the platform's whole active catalog, not only what
|
|
560
|
+
* the workspace bought, so this is the split a launcher renders as two
|
|
561
|
+
* groups: the apps you open, and the apps you could. `false` means the tile
|
|
562
|
+
* belongs in the second - send it to Connect, not to `launchUrl`.
|
|
563
|
+
*/
|
|
564
|
+
subscribed: boolean;
|
|
554
565
|
/** True for the application whose API key made the call - render it as the active tile. */
|
|
555
566
|
isCurrent: boolean;
|
|
556
|
-
/** Launchable but not healthy (paused, or cancelling at period end) - badge it, do not hide it. */
|
|
567
|
+
/** Launchable but not healthy (paused, or cancelling at period end) - badge it, do not hide it. Always `false` when `subscribed` is `false`. */
|
|
557
568
|
needsAttention: boolean;
|
|
558
569
|
}
|
|
570
|
+
/** Connect's closed set of catalog categories - `WorkspaceApp.category`. */
|
|
571
|
+
export type WorkspaceAppCategory = "communication" | "storage" | "analytics" | "security" | "billing" | "productivity" | "sales" | "operations" | "infrastructure" | "other";
|
|
559
572
|
/**
|
|
560
573
|
* The platform's DPDP grievance channel - `companyProfile.grievance`.
|
|
561
574
|
*
|
|
@@ -1269,3 +1282,175 @@ export interface ConnectContact {
|
|
|
1269
1282
|
createdAt: string;
|
|
1270
1283
|
updatedAt: string;
|
|
1271
1284
|
}
|
|
1285
|
+
/**
|
|
1286
|
+
* What kind of conversation a channel carries. `voice` is the only kind
|
|
1287
|
+
* Connect stocks inventory for today - `whatsapp` and `meeting` are modelled
|
|
1288
|
+
* end to end and have no runtime yet, so `claim` refuses them.
|
|
1289
|
+
*/
|
|
1290
|
+
export type ChannelKind = "voice" | "meeting" | "whatsapp";
|
|
1291
|
+
/**
|
|
1292
|
+
* `suspended` and `released` both stop the channel answering. The difference
|
|
1293
|
+
* is whose act it was: a Connect operator suspended it, or the number went
|
|
1294
|
+
* back to inventory.
|
|
1295
|
+
*/
|
|
1296
|
+
export type ChannelStatus = "active" | "suspended" | "released";
|
|
1297
|
+
/**
|
|
1298
|
+
* Where inbound conversations on a channel go.
|
|
1299
|
+
*
|
|
1300
|
+
* `destination` is **yours**: an arbitrary JSON object Connect stores verbatim
|
|
1301
|
+
* and never reads into. Which agent answers, business hours, overflow - all of
|
|
1302
|
+
* it is your application's model, not Connect's.
|
|
1303
|
+
*/
|
|
1304
|
+
export interface ChannelRouting {
|
|
1305
|
+
destination: Record<string, unknown>;
|
|
1306
|
+
updatedAt: string;
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* A number assigned to your application, as your application sees it.
|
|
1310
|
+
*
|
|
1311
|
+
* Two things are deliberately withheld and will not appear later:
|
|
1312
|
+
* `createdBy` names a Connect operator, and the carrier-side record (which
|
|
1313
|
+
* carrier, which provider resource, what Connect pays per month) is Connect's
|
|
1314
|
+
* cost and Connect's credential surface. An application reads what was
|
|
1315
|
+
* assigned to it; it never provisions, prices or releases a number.
|
|
1316
|
+
*/
|
|
1317
|
+
export interface ConnectChannel {
|
|
1318
|
+
id: string;
|
|
1319
|
+
kind: ChannelKind;
|
|
1320
|
+
/** E.164 for a voice channel - the number a caller dials. */
|
|
1321
|
+
address: string;
|
|
1322
|
+
label: string | null;
|
|
1323
|
+
workspaceId: string;
|
|
1324
|
+
status: ChannelStatus;
|
|
1325
|
+
/** `null` when nothing has been routed yet, and after a `clearRouting()`. */
|
|
1326
|
+
routing: ChannelRouting | null;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* What a claim answers with - narrower than `ConnectChannel`, because a
|
|
1330
|
+
* just-claimed number has no label and no routing yet. Read it back with
|
|
1331
|
+
* `channels.get(id)` once you have written routing.
|
|
1332
|
+
*/
|
|
1333
|
+
export interface ClaimedChannel {
|
|
1334
|
+
id: string;
|
|
1335
|
+
kind: ChannelKind;
|
|
1336
|
+
address: string;
|
|
1337
|
+
workspaceId: string;
|
|
1338
|
+
status: ChannelStatus;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* What both routing writes echo back - the stored record, not the whole
|
|
1342
|
+
* channel. `routing` is `null` after `clearRouting()`, which is the same shape
|
|
1343
|
+
* a channel that was never routed has: to the answer path "cleared" and "never
|
|
1344
|
+
* set" are the same fact.
|
|
1345
|
+
*/
|
|
1346
|
+
export interface ChannelRoutingRecord {
|
|
1347
|
+
id: string;
|
|
1348
|
+
routing: ChannelRouting | null;
|
|
1349
|
+
}
|
|
1350
|
+
/** One page of channels. Page until `hasMore` is `false`. */
|
|
1351
|
+
export interface ChannelsPage {
|
|
1352
|
+
items: ConnectChannel[];
|
|
1353
|
+
nextCursor: string | null;
|
|
1354
|
+
hasMore: boolean;
|
|
1355
|
+
}
|
|
1356
|
+
/** Which way the call went. */
|
|
1357
|
+
export type CallDirection = "inbound" | "outbound";
|
|
1358
|
+
/**
|
|
1359
|
+
* `rejected` means Connect never connected it - see `rejectReason`.
|
|
1360
|
+
* `answered` is a call still in progress; `completed` is one that ran and
|
|
1361
|
+
* ended.
|
|
1362
|
+
*/
|
|
1363
|
+
export type CallStatus = "answered" | "rejected" | "completed";
|
|
1364
|
+
/**
|
|
1365
|
+
* Why a call was refused. Widened at the point of use with `(string & {})`,
|
|
1366
|
+
* the same way `ReadinessBlocker.code` is: Connect adds reasons as it learns
|
|
1367
|
+
* to tell failures apart, and a new one should reach you as a value to handle
|
|
1368
|
+
* rather than as a type error on a published package you cannot patch. Handle
|
|
1369
|
+
* the ones you branch on and treat the rest as "refused".
|
|
1370
|
+
*/
|
|
1371
|
+
export type CallRejectReason = "unknown_number" | "channel_suspended" | "channel_released" | "channel_unassigned" | "no_routing" | "workspace_blocked" | "answer_unavailable" | "runtime_unconfigured";
|
|
1372
|
+
/** How a call that actually ran came to an end. Widened at its use site for the same reason as `CallRejectReason`. */
|
|
1373
|
+
export type CallDisconnectReason = "caller_hangup" | "agent_hangup" | "max_duration" | "transferred" | "error" | "abandoned";
|
|
1374
|
+
/**
|
|
1375
|
+
* Where the recording has got to. `pending` is normal for a call that has just
|
|
1376
|
+
* ended - the carrier posts the audio afterwards - and `failed` is kept
|
|
1377
|
+
* distinct from `none` so "we never recorded this" is never read as "we
|
|
1378
|
+
* recorded it and lost it".
|
|
1379
|
+
*/
|
|
1380
|
+
export type CallRecordingStatus = "none" | "pending" | "stored" | "failed";
|
|
1381
|
+
/** What the model leg of a call cost. */
|
|
1382
|
+
export interface CallTokenUsage {
|
|
1383
|
+
promptTokens: number;
|
|
1384
|
+
responseTokens: number;
|
|
1385
|
+
totalTokens: number;
|
|
1386
|
+
}
|
|
1387
|
+
/** One tool the agent called mid-conversation. Arguments and results are JSON strings, not parsed objects. */
|
|
1388
|
+
export interface CallToolInvocation {
|
|
1389
|
+
name: string;
|
|
1390
|
+
argumentsJson: string;
|
|
1391
|
+
resultJson: string | null;
|
|
1392
|
+
durationMs: number | null;
|
|
1393
|
+
/** `null` for calls closed out before the runtime reported it - which is not zero. */
|
|
1394
|
+
startedAt: string | null;
|
|
1395
|
+
error: string | null;
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* One call that arrived on a channel assigned to your application.
|
|
1399
|
+
*
|
|
1400
|
+
* `durationSeconds` and `billDurationSeconds` are the two durations Connect
|
|
1401
|
+
* stands behind - the carrier's own measure of the audio is deliberately not
|
|
1402
|
+
* published, since a third near-identical number only invites a billing
|
|
1403
|
+
* argument settled off the wrong one.
|
|
1404
|
+
*/
|
|
1405
|
+
export interface ConnectCall {
|
|
1406
|
+
id: string;
|
|
1407
|
+
channelId: string;
|
|
1408
|
+
workspaceId: string;
|
|
1409
|
+
direction: CallDirection;
|
|
1410
|
+
fromNumber: string;
|
|
1411
|
+
toNumber: string;
|
|
1412
|
+
status: CallStatus;
|
|
1413
|
+
rejectReason: CallRejectReason | (string & {}) | null;
|
|
1414
|
+
disconnectReason: CallDisconnectReason | (string & {}) | null;
|
|
1415
|
+
startedAt: string;
|
|
1416
|
+
answeredAt: string | null;
|
|
1417
|
+
endedAt: string | null;
|
|
1418
|
+
durationSeconds: number | null;
|
|
1419
|
+
billDurationSeconds: number | null;
|
|
1420
|
+
recordingStatus: CallRecordingStatus;
|
|
1421
|
+
/** A Drive file id - read it with a `drive:read` key. `null` until the carrier posts the audio. */
|
|
1422
|
+
recordingFileId: string | null;
|
|
1423
|
+
transcriptFileId: string | null;
|
|
1424
|
+
turnCount: number | null;
|
|
1425
|
+
tokenUsage: CallTokenUsage | null;
|
|
1426
|
+
toolInvocations: CallToolInvocation[];
|
|
1427
|
+
/** Your agent's own post-call structured output, keyed by its extraction schema. Connect stores it without interpreting it. */
|
|
1428
|
+
extraction: Record<string, unknown> | null;
|
|
1429
|
+
agentId: string | null;
|
|
1430
|
+
agentVersion: number | null;
|
|
1431
|
+
}
|
|
1432
|
+
/** Who spoke. */
|
|
1433
|
+
export type CallTurnRole = "user" | "agent";
|
|
1434
|
+
/**
|
|
1435
|
+
* One transcript turn. Order by `sequence`, never by `startedAt` - two turns
|
|
1436
|
+
* can share a millisecond.
|
|
1437
|
+
*/
|
|
1438
|
+
export interface CallTranscriptTurn {
|
|
1439
|
+
sequence: number;
|
|
1440
|
+
role: CallTurnRole;
|
|
1441
|
+
text: string;
|
|
1442
|
+
startedAt: string | null;
|
|
1443
|
+
/** True only ever for an `agent` turn - the caller talked over it. */
|
|
1444
|
+
interrupted: boolean;
|
|
1445
|
+
}
|
|
1446
|
+
/** A call and its transcript. Long calls are capped at 2,000 turns. */
|
|
1447
|
+
export interface CallDetail {
|
|
1448
|
+
call: ConnectCall;
|
|
1449
|
+
turns: CallTranscriptTurn[];
|
|
1450
|
+
}
|
|
1451
|
+
/** One page of calls, newest first. Page until `hasMore` is `false` - do not stop on an empty `items`. */
|
|
1452
|
+
export interface CallsPage {
|
|
1453
|
+
items: ConnectCall[];
|
|
1454
|
+
nextCursor: string | null;
|
|
1455
|
+
hasMore: boolean;
|
|
1456
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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",
|