@cosmoops/connect-sdk 0.20.0 → 0.23.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 +281 -0
- package/README.md +2 -0
- 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 +28 -0
- package/dist/cjs/client/components/plans.js +8 -3
- package/dist/cjs/client/components/user-menu.js +2 -6
- package/dist/cjs/client/index.js +11 -3
- package/dist/cjs/client/provider.js +102 -7
- package/dist/cjs/server/client.js +12 -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/enrichment.js +56 -0
- package/dist/cjs/server/resources/identity.js +40 -0
- package/dist/cjs/server/resources/workspaces.js +73 -2
- 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 +27 -0
- package/dist/esm/client/components/plans.js +9 -4
- package/dist/esm/client/components/user-menu.js +1 -5
- package/dist/esm/client/index.js +8 -1
- package/dist/esm/client/provider.js +102 -7
- package/dist/esm/server/client.js +12 -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/enrichment.js +52 -0
- package/dist/esm/server/resources/identity.js +39 -0
- package/dist/esm/server/resources/workspaces.js +73 -2
- 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 +23 -0
- package/dist/types/client/index.d.ts +4 -2
- package/dist/types/client/provider.d.ts +101 -3
- package/dist/types/client/use-checkout.d.ts +9 -0
- package/dist/types/server/client.d.ts +9 -1
- package/dist/types/server/index.d.ts +3 -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/enrichment.d.ts +44 -0
- package/dist/types/server/resources/identity.d.ts +35 -0
- package/dist/types/server/resources/workspaces.d.ts +63 -3
- package/dist/types/types.d.ts +344 -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;
|
|
@@ -117,3 +117,26 @@ export declare function annualSaving(monthly: Plan | null | undefined, annual: P
|
|
|
117
117
|
export type PlanAction = "current" | "upgrade" | "downgrade" | "switch-to-annual" | "switch-to-monthly" | "subscribe";
|
|
118
118
|
export declare function resolveAction(plan: Plan, current: SubscriptionResponse | null): PlanAction;
|
|
119
119
|
export declare const ACTION_LABEL: Record<PlanAction, string>;
|
|
120
|
+
/**
|
|
121
|
+
* The subscription id a checkout hand-off should carry for `action` - or
|
|
122
|
+
* `null`, meaning "send them to Connect's *subscribe* screen".
|
|
123
|
+
*
|
|
124
|
+
* Paired with `resolveAction` deliberately: the button's label and the URL it
|
|
125
|
+
* opens are one decision, and deriving the URL from the mere *presence* of a
|
|
126
|
+
* subscription row instead is what sent a card reading "Subscribe" to
|
|
127
|
+
* Connect's change-plan screen, which answered `notFound()`.
|
|
128
|
+
*
|
|
129
|
+
* The gap is real rather than hypothetical. `resolveAction` needs the
|
|
130
|
+
* **current plan** to classify a change and answers `"subscribe"` without
|
|
131
|
+
* one - and `SubscriptionResponse` carries `plan: null` beside a perfectly
|
|
132
|
+
* live `subscription` whenever that plan has been deleted from the catalogue
|
|
133
|
+
* out from under it, which a re-seeded application does. Connect's change-plan
|
|
134
|
+
* route is stuck in the same place for the same reason: it cannot prorate
|
|
135
|
+
* against a price it cannot read.
|
|
136
|
+
*
|
|
137
|
+
* Sending those to `/apps/.../subscribe/...` is a real hand-off, not a guess:
|
|
138
|
+
* that route re-reads the workspace's live subscriptions server-side and
|
|
139
|
+
* redirects to the change-plan screen itself when there genuinely is one to
|
|
140
|
+
* change. Connect decides, so the grid does not have to be sure.
|
|
141
|
+
*/
|
|
142
|
+
export declare function checkoutSubscriptionId(action: PlanAction, current: SubscriptionResponse | null): string | null;
|
|
@@ -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, UseConnectResourceOptions, } 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, isFreePlan, groupPlansByTier, resolvePlanGroup, } from "./components/plan-pricing.js";
|
|
87
|
+
export { formatPrice, monthlyOf, resolveAction, checkoutSubscriptionId, 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
|
/**
|
|
@@ -375,13 +456,30 @@ export declare function useConnectSessions(): {
|
|
|
375
456
|
*/
|
|
376
457
|
refetch(): void;
|
|
377
458
|
};
|
|
459
|
+
/** Options for {@link useConnectResource}. */
|
|
460
|
+
export interface UseConnectResourceOptions {
|
|
461
|
+
/**
|
|
462
|
+
* Whether the read has to wait for a session. `true` by default, and right
|
|
463
|
+
* for every route but one.
|
|
464
|
+
*
|
|
465
|
+
* Pass `false` only for a bridge route that takes neither a session nor a
|
|
466
|
+
* workspace - today that is `GET /company-profile` alone, and its handler
|
|
467
|
+
* says why. Passing it for anything else does not widen access (the bridge
|
|
468
|
+
* still refuses), it just buys a guaranteed 401 sooner.
|
|
469
|
+
*/
|
|
470
|
+
requiresSession?: boolean;
|
|
471
|
+
}
|
|
378
472
|
/**
|
|
379
473
|
* One bridge read, shared through the provider's cache.
|
|
380
474
|
*
|
|
381
|
-
*
|
|
382
|
-
* firing before one exists just buys a guaranteed 401.
|
|
475
|
+
* Waits for a session by default: every resource route but one needs it, so
|
|
476
|
+
* firing before one exists just buys a guaranteed 401. `requiresSession:
|
|
477
|
+
* false` opts out, and the reason it exists is the grievance notice - a
|
|
478
|
+
* statutory contact is at its most useful on a public page a signed-out
|
|
479
|
+
* visitor is reading, so a hook that answered `null` until someone logged in
|
|
480
|
+
* would render nothing at exactly the moment that matters.
|
|
383
481
|
*/
|
|
384
|
-
export declare function useConnectResource<T>(path: string): {
|
|
482
|
+
export declare function useConnectResource<T>(path: string, options?: Readonly<UseConnectResourceOptions>): {
|
|
385
483
|
data: T | null;
|
|
386
484
|
isLoading: boolean;
|
|
387
485
|
error: string | null;
|
|
@@ -64,6 +64,15 @@ export interface CheckoutUrlOptions {
|
|
|
64
64
|
* plan on this subscription"; absent means "subscribe for the first time" -
|
|
65
65
|
* and the two are separate screens in Connect, because only one of them can
|
|
66
66
|
* price proration against a cycle already paid for.
|
|
67
|
+
*
|
|
68
|
+
* Pass it only when you know **which plan the subscription is on today**,
|
|
69
|
+
* which is what makes the change classifiable. `checkoutSubscriptionId`
|
|
70
|
+
* derives it from the action you are already showing on the button; a bare
|
|
71
|
+
* `subscription?.subscription?.id` does not, and a live subscription whose
|
|
72
|
+
* plan has been deleted from the catalogue is the case where the two differ
|
|
73
|
+
* and the change-plan screen answers `notFound()`. Leaving it `null` is
|
|
74
|
+
* always safe: the subscribe route redirects to the change-plan screen
|
|
75
|
+
* itself when Connect finds a subscription to change.
|
|
67
76
|
*/
|
|
68
77
|
subscriptionId?: string | null;
|
|
69
78
|
/** Carried into Connect's checkout, where it is validated against the real cart. */
|
|
@@ -2,10 +2,13 @@ import type { FetchLike } from "./http.js";
|
|
|
2
2
|
import { AccessResource } from "./resources/access.js";
|
|
3
3
|
import { AppsResource } from "./resources/apps.js";
|
|
4
4
|
import { AiResource } from "./resources/ai.js";
|
|
5
|
+
import { EnrichmentResource } from "./resources/enrichment.js";
|
|
5
6
|
import { DataResource } from "./resources/data.js";
|
|
6
7
|
import { WorkspacesResource } from "./resources/workspaces.js";
|
|
7
8
|
import { SchedulingResource } from "./resources/scheduling.js";
|
|
8
9
|
import { ContactsResource } from "./resources/contacts.js";
|
|
10
|
+
import { ChannelsResource } from "./resources/channels.js";
|
|
11
|
+
import { CallsResource } from "./resources/calls.js";
|
|
9
12
|
import { KvResource } from "./resources/kv.js";
|
|
10
13
|
import { BillingResource } from "./resources/billing.js";
|
|
11
14
|
import { DriveResource } from "./resources/drive.js";
|
|
@@ -81,8 +84,13 @@ export declare class ConnectClient {
|
|
|
81
84
|
readonly scheduling: SchedulingResource;
|
|
82
85
|
/** Who a workspace already knows how to reach, by phone or by id. */
|
|
83
86
|
readonly contacts: ContactsResource;
|
|
87
|
+
/** The phone numbers Connect has assigned to your application, and where each one routes. */
|
|
88
|
+
readonly channels: ChannelsResource;
|
|
89
|
+
/** The calls that arrived on those channels, with their transcripts. */
|
|
90
|
+
readonly calls: CallsResource;
|
|
84
91
|
/** RAG chat and structured extraction. */
|
|
85
92
|
readonly ai: AiResource;
|
|
93
|
+
readonly enrichment: EnrichmentResource;
|
|
86
94
|
/** Your application's own document store - see `DataResource`. */
|
|
87
95
|
readonly data: DataResource;
|
|
88
96
|
/** Your application's key/value store - see `KvResource`. */
|
|
@@ -115,4 +123,4 @@ export declare class ConnectClient {
|
|
|
115
123
|
constructor(options: ConnectClientOptions);
|
|
116
124
|
}
|
|
117
125
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
118
|
-
export declare const SDK_VERSION = "0.
|
|
126
|
+
export declare const SDK_VERSION = "0.23.0";
|
|
@@ -18,6 +18,7 @@ export type { AppsResource } from "./resources/apps.js";
|
|
|
18
18
|
export type { EntitlementsResource } from "./resources/entitlements.js";
|
|
19
19
|
export type { BillingResource } from "./resources/billing.js";
|
|
20
20
|
export type { AiResource } from "./resources/ai.js";
|
|
21
|
+
export type { EnrichmentResource } from "./resources/enrichment.js";
|
|
21
22
|
export type { DataResource } from "./resources/data.js";
|
|
22
23
|
export type { KvResource } from "./resources/kv.js";
|
|
23
24
|
export type { WorkspacesResource } from "./resources/workspaces.js";
|
|
@@ -29,6 +30,8 @@ export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./reso
|
|
|
29
30
|
export type { PlatformResource } from "./resources/platform.js";
|
|
30
31
|
export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, RescheduleBookingInput, } from "./resources/scheduling.js";
|
|
31
32
|
export type { ContactsResource, LookupContactInput } from "./resources/contacts.js";
|
|
33
|
+
export type { ChannelsResource, ListChannelsInput, ClaimChannelInput, } from "./resources/channels.js";
|
|
34
|
+
export type { CallsResource, ListCallsInput } from "./resources/calls.js";
|
|
32
35
|
/**
|
|
33
36
|
* Webhook verification and a ready-made POST route. Server-only for the
|
|
34
37
|
* 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
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type TransportOptions } from "../http.js";
|
|
2
|
+
import type { CompanyEnrichmentResult } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* What Connect knows about the company at a domain.
|
|
5
|
+
*
|
|
6
|
+
* The thing behind "the account filled itself in": a record holding nothing
|
|
7
|
+
* but `priya@acme.com` can offer a name, an industry, a size band and an
|
|
8
|
+
* address instead of an empty form.
|
|
9
|
+
*
|
|
10
|
+
* Two properties are worth knowing before you wire it in, because they decide
|
|
11
|
+
* how you should render the result:
|
|
12
|
+
*
|
|
13
|
+
* - **Every value is cited, and nothing is recalled.** Connect reads the
|
|
14
|
+
* company's own website and returns the URL each claim came from. A field
|
|
15
|
+
* the pages never stated comes back `null` rather than as a plausible guess.
|
|
16
|
+
* - **It is a suggestion, not a save.** Nothing is written to your store, and
|
|
17
|
+
* nothing should be written to it without the person in front of it
|
|
18
|
+
* agreeing - which is also how HubSpot and Zoho present the same feature.
|
|
19
|
+
*/
|
|
20
|
+
export declare class EnrichmentResource {
|
|
21
|
+
private readonly transport;
|
|
22
|
+
constructor(transport: TransportOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Look a company up from a work email address or a domain.
|
|
25
|
+
*
|
|
26
|
+
* Send whichever you hold; `domain` wins when both are given. A consumer
|
|
27
|
+
* address (`founder@gmail.com`) is answered with
|
|
28
|
+
* `{ status: "skipped", reason: "consumer_domain" }` rather than an error -
|
|
29
|
+
* it names a person, not an employer.
|
|
30
|
+
*
|
|
31
|
+
* `asUser` attributes the generation to the signed-in caller's workspace,
|
|
32
|
+
* resolved from their session token. Without it the call is attributed to
|
|
33
|
+
* your application.
|
|
34
|
+
*/
|
|
35
|
+
company(input: {
|
|
36
|
+
email?: string;
|
|
37
|
+
domain?: string;
|
|
38
|
+
asUser?: string;
|
|
39
|
+
/** Re-read the site even if Connect has a fresh profile cached. For an operator pressing "refresh" - not for automatic calls, which should take the cache. */
|
|
40
|
+
refresh?: boolean;
|
|
41
|
+
}, options?: {
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
}): Promise<CompanyEnrichmentResult>;
|
|
44
|
+
}
|
|
@@ -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
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { ConnectWorkspaceMember, UserWorkspace, WorkspaceSwitchResult } from "../../types.js";
|
|
2
|
+
import type { ConnectInvitation, ConnectMemberAccess, ConnectMemberAccessResult, ConnectWorkspaceMember, UserWorkspace, WorkspaceSwitchResult } from "../../types.js";
|
|
3
3
|
/**
|
|
4
|
-
* Which workspaces the signed-in user belongs to, moving between them,
|
|
5
|
-
* else is on the team.
|
|
4
|
+
* Which workspaces the signed-in user belongs to, moving between them, who
|
|
5
|
+
* else is on the team, and inviting or granting access to your application.
|
|
6
6
|
*
|
|
7
7
|
* **Every call here needs `asUser`** - the signed-in user's opaque
|
|
8
8
|
* `sessionToken`. There is no application-wide form of any of these: they take
|
|
@@ -52,4 +52,64 @@ export declare class WorkspacesResource {
|
|
|
52
52
|
members(options: {
|
|
53
53
|
asUser: string;
|
|
54
54
|
}): Promise<ConnectWorkspaceMember[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Seats, what the caller may do with them, and the invitations already
|
|
57
|
+
* holding one - everything a team screen needs to decide what to draw.
|
|
58
|
+
*
|
|
59
|
+
* Behind `members:manage` rather than `workspaces:read`, and gated on the
|
|
60
|
+
* caller's own `member:invite`: it names people who are not members yet.
|
|
61
|
+
*/
|
|
62
|
+
access(options: {
|
|
63
|
+
asUser: string;
|
|
64
|
+
}): Promise<ConnectMemberAccess>;
|
|
65
|
+
/**
|
|
66
|
+
* Invite someone into the workspace, with access to your application.
|
|
67
|
+
*
|
|
68
|
+
* `roleKey` names a role from your own catalog (or the reserved `"admin"`);
|
|
69
|
+
* omit it to add someone to the workspace with no access to your
|
|
70
|
+
* application. You cannot send a Connect role - the grant is built server
|
|
71
|
+
* side as `[member, app:<yourKey>:<roleKey>]`, so nothing this method can
|
|
72
|
+
* express reaches outside your application.
|
|
73
|
+
*
|
|
74
|
+
* Spends a member seat when the invitation is created, not when it is
|
|
75
|
+
* accepted. Throws `ConnectError` with status 409 and a message written to
|
|
76
|
+
* be shown when there is no seat, and 403 when the caller is inviting
|
|
77
|
+
* someone with access they do not hold themselves.
|
|
78
|
+
*/
|
|
79
|
+
invite(input: {
|
|
80
|
+
inviteeEmail: string;
|
|
81
|
+
roleKey?: string | null;
|
|
82
|
+
}, options: {
|
|
83
|
+
asUser: string;
|
|
84
|
+
}): Promise<ConnectInvitation>;
|
|
85
|
+
/**
|
|
86
|
+
* Grant or revoke **your application's** role for an existing member.
|
|
87
|
+
* `roleKey: null` revokes it.
|
|
88
|
+
*
|
|
89
|
+
* Moves only your own grant: Connect recomputes the member's roles as what
|
|
90
|
+
* they hold now, minus any grant naming your application, plus the one you
|
|
91
|
+
* asked for. Their workspace role and every other application they use are
|
|
92
|
+
* untouched, and there is no form of this that removes them from the
|
|
93
|
+
* workspace.
|
|
94
|
+
*
|
|
95
|
+
* Re-sending the role they already have is a no-op rather than an error, so
|
|
96
|
+
* a form that submits whatever is on screen is safe.
|
|
97
|
+
*/
|
|
98
|
+
setAccess(input: {
|
|
99
|
+
userId: string;
|
|
100
|
+
roleKey: string | null;
|
|
101
|
+
}, options: {
|
|
102
|
+
asUser: string;
|
|
103
|
+
}): Promise<ConnectMemberAccessResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Withdraw a pending invitation your application sent, freeing its seat.
|
|
106
|
+
*
|
|
107
|
+
* Only yours - an invitation carrying another application's grant is a 404,
|
|
108
|
+
* deliberately indistinguishable from one that does not exist.
|
|
109
|
+
*/
|
|
110
|
+
revokeInvitation(input: {
|
|
111
|
+
invitationId: string;
|
|
112
|
+
}, options: {
|
|
113
|
+
asUser: string;
|
|
114
|
+
}): Promise<void>;
|
|
55
115
|
}
|