@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +281 -0
  2. package/README.md +2 -0
  3. package/dist/cjs/client/components/identity.js +24 -0
  4. package/dist/cjs/client/components/one-tap-state.js +96 -0
  5. package/dist/cjs/client/components/one-tap.js +106 -0
  6. package/dist/cjs/client/components/plan-pricing.js +28 -0
  7. package/dist/cjs/client/components/plans.js +8 -3
  8. package/dist/cjs/client/components/user-menu.js +2 -6
  9. package/dist/cjs/client/index.js +11 -3
  10. package/dist/cjs/client/provider.js +102 -7
  11. package/dist/cjs/server/client.js +12 -1
  12. package/dist/cjs/server/handler.js +126 -2
  13. package/dist/cjs/server/resources/calls.js +57 -0
  14. package/dist/cjs/server/resources/channels.js +115 -0
  15. package/dist/cjs/server/resources/enrichment.js +56 -0
  16. package/dist/cjs/server/resources/identity.js +40 -0
  17. package/dist/cjs/server/resources/workspaces.js +73 -2
  18. package/dist/esm/client/components/identity.js +21 -0
  19. package/dist/esm/client/components/one-tap-state.js +90 -0
  20. package/dist/esm/client/components/one-tap.js +103 -0
  21. package/dist/esm/client/components/plan-pricing.js +27 -0
  22. package/dist/esm/client/components/plans.js +9 -4
  23. package/dist/esm/client/components/user-menu.js +1 -5
  24. package/dist/esm/client/index.js +8 -1
  25. package/dist/esm/client/provider.js +102 -7
  26. package/dist/esm/server/client.js +12 -1
  27. package/dist/esm/server/handler.js +127 -3
  28. package/dist/esm/server/resources/calls.js +53 -0
  29. package/dist/esm/server/resources/channels.js +111 -0
  30. package/dist/esm/server/resources/enrichment.js +52 -0
  31. package/dist/esm/server/resources/identity.js +39 -0
  32. package/dist/esm/server/resources/workspaces.js +73 -2
  33. package/dist/types/client/components/identity.d.ts +17 -0
  34. package/dist/types/client/components/one-tap-state.d.ts +39 -0
  35. package/dist/types/client/components/one-tap.d.ts +50 -0
  36. package/dist/types/client/components/plan-pricing.d.ts +23 -0
  37. package/dist/types/client/index.d.ts +4 -2
  38. package/dist/types/client/provider.d.ts +101 -3
  39. package/dist/types/client/use-checkout.d.ts +9 -0
  40. package/dist/types/server/client.d.ts +9 -1
  41. package/dist/types/server/index.d.ts +3 -0
  42. package/dist/types/server/resources/calls.d.ts +45 -0
  43. package/dist/types/server/resources/channels.d.ts +91 -0
  44. package/dist/types/server/resources/enrichment.d.ts +44 -0
  45. package/dist/types/server/resources/identity.d.ts +35 -0
  46. package/dist/types/server/resources/workspaces.d.ts +63 -3
  47. package/dist/types/types.d.ts +344 -1
  48. 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
- * Only fetches once a user is known: every resource route needs a session, so
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.20.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, and who
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
  }