@cosmoops/connect-sdk 0.21.0 → 0.24.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 +201 -0
- package/dist/cjs/client/components/plan-pricing.js +28 -0
- package/dist/cjs/client/components/plans.js +8 -3
- package/dist/cjs/client/index.js +7 -1
- package/dist/cjs/client/provider.js +14 -7
- package/dist/cjs/server/client.js +4 -1
- package/dist/cjs/server/handler.js +12 -2
- package/dist/cjs/server/resources/enrichment.js +56 -0
- package/dist/cjs/server/resources/integrations.js +7 -5
- package/dist/cjs/server/resources/workspaces.js +73 -2
- package/dist/esm/client/components/plan-pricing.js +27 -0
- package/dist/esm/client/components/plans.js +9 -4
- package/dist/esm/client/index.js +7 -1
- package/dist/esm/client/provider.js +14 -7
- package/dist/esm/server/client.js +4 -1
- package/dist/esm/server/handler.js +12 -2
- package/dist/esm/server/resources/enrichment.js +52 -0
- package/dist/esm/server/resources/integrations.js +7 -5
- package/dist/esm/server/resources/workspaces.js +73 -2
- package/dist/types/client/components/plan-pricing.d.ts +23 -0
- package/dist/types/client/index.d.ts +2 -2
- package/dist/types/client/provider.d.ts +20 -3
- package/dist/types/client/use-checkout.d.ts +9 -0
- package/dist/types/server/client.d.ts +3 -1
- package/dist/types/server/index.d.ts +2 -1
- package/dist/types/server/resources/enrichment.d.ts +44 -0
- package/dist/types/server/resources/integrations.d.ts +20 -4
- package/dist/types/server/resources/workspaces.d.ts +63 -3
- package/dist/types/types.d.ts +172 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { useConnect, useConnectEntitlements, useConnectPlans } from "../provider
|
|
|
7
7
|
import { buildCancelUrl, buildCheckoutUrl, useConnectCheckout, } from "../use-checkout.js";
|
|
8
8
|
import { AnimatePresence, ENTER, m, MotionProvider } from "./motion.js";
|
|
9
9
|
import { ConnectSubscribeDialog } from "./subscribe-dialog.js";
|
|
10
|
-
import { ACTION_LABEL, discountLabel, formatPrice, groupPlansByTier, intervalLabel, isFreePlan, pricePerLabel, resolveAction, } from "./plan-pricing.js";
|
|
10
|
+
import { ACTION_LABEL, checkoutSubscriptionId, discountLabel, formatPrice, groupPlansByTier, intervalLabel, isFreePlan, pricePerLabel, resolveAction, } from "./plan-pricing.js";
|
|
11
11
|
/**
|
|
12
12
|
* The plan table: monthly/annual toggle, features, add-ons, the discount
|
|
13
13
|
* actually in effect, and the subscribe / upgrade / downgrade flows.
|
|
@@ -80,8 +80,13 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
80
80
|
* created in `"created"` and only flips to active when the payment webhook
|
|
81
81
|
* lands, which can be minutes later. Holding a customer on a spinner until
|
|
82
82
|
* then would misreport a completed checkout as an abandoned one.
|
|
83
|
+
*
|
|
84
|
+
* `action` is threaded through rather than re-derived: it is the same
|
|
85
|
+
* decision the card's label came from, and `checkoutSubscriptionId` is what
|
|
86
|
+
* keeps the two from disagreeing - see its doc comment for the 404 that
|
|
87
|
+
* disagreement produced.
|
|
83
88
|
*/
|
|
84
|
-
function startCheckout(plan) {
|
|
89
|
+
function startCheckout(plan, action) {
|
|
85
90
|
if (!applicationId)
|
|
86
91
|
return;
|
|
87
92
|
checkout.start({
|
|
@@ -89,7 +94,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
89
94
|
appUrl,
|
|
90
95
|
applicationId,
|
|
91
96
|
planId: plan.id,
|
|
92
|
-
subscriptionId: subscription
|
|
97
|
+
subscriptionId: checkoutSubscriptionId(action, subscription),
|
|
93
98
|
coupon,
|
|
94
99
|
}),
|
|
95
100
|
isSettled: (next) => next.subscription.plan?.id === plan.id,
|
|
@@ -139,7 +144,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
139
144
|
// plan would race the first and one of them would be
|
|
140
145
|
// rejected on arrival.
|
|
141
146
|
isDisabled: isCurrent || checkout.status === "waiting", onPress: () => setPending({ plan, action }), children: [ACTION_LABEL[action], !isCurrent && _jsx(ExternalLink, { className: "size-3.5" })] }) })] }) }, tier.planGroup));
|
|
142
|
-
}) }), subscription?.subscription && (_jsx(ConnectUnsubscribeRow, { subscription: subscription.subscription, appUrl: appUrl, checkout: checkout })), _jsx(ConnectSubscribeDialog, { plan: pending?.plan ?? null, action: pending?.action ?? "subscribe", current: subscription, coupon: hideCoupon ? undefined : coupon, onClose: () => setPending(null), onConfirm: () => pending && startCheckout(pending.plan) })] }) }));
|
|
147
|
+
}) }), subscription?.subscription && (_jsx(ConnectUnsubscribeRow, { subscription: subscription.subscription, appUrl: appUrl, checkout: checkout })), _jsx(ConnectSubscribeDialog, { plan: pending?.plan ?? null, action: pending?.action ?? "subscribe", current: subscription, coupon: hideCoupon ? undefined : coupon, onClose: () => setPending(null), onConfirm: () => pending && startCheckout(pending.plan, pending.action) })] }) }));
|
|
143
148
|
}
|
|
144
149
|
/**
|
|
145
150
|
* What the page says while the customer is off in Connect, and after.
|
package/dist/esm/client/index.js
CHANGED
|
@@ -105,7 +105,13 @@ export { ConnectConfirmDialog } from "./components/confirm-dialog.js";
|
|
|
105
105
|
export { ConnectCursorPagination } from "./components/cursor-pagination.js";
|
|
106
106
|
export { ConnectFilterTabs } from "./components/filter-tabs.js";
|
|
107
107
|
export { useConnectCheckout, buildCheckoutUrl } from "./use-checkout.js";
|
|
108
|
-
export { formatPrice, monthlyOf, resolveAction,
|
|
108
|
+
export { formatPrice, monthlyOf, resolveAction,
|
|
109
|
+
// The other half of `resolveAction`, and exported with it deliberately: a
|
|
110
|
+
// partner writing its own pricing grid has to turn that action into a
|
|
111
|
+
// checkout URL, and reading the subscription id straight off the
|
|
112
|
+
// entitlements response instead is what sent a "Subscribe" button to
|
|
113
|
+
// Connect's change-plan screen. See its doc comment.
|
|
114
|
+
checkoutSubscriptionId, annualSaving, discountLabel, ACTION_LABEL,
|
|
109
115
|
// Exported for the same reason `groupPlansByTier` is: a partner writing its
|
|
110
116
|
// own pricing grid has to draw a ₹0 plan as "Free" rather than as a price
|
|
111
117
|
// with a cadence and a GST rate attached, and re-deriving "is this free"
|
|
@@ -389,7 +389,7 @@ export function useConnectPlans() {
|
|
|
389
389
|
* `CompanyProfile`. `<ConnectGrievanceNotice>` renders it for you.
|
|
390
390
|
*/
|
|
391
391
|
export function useConnectCompanyProfile() {
|
|
392
|
-
return useConnectResource("/company-profile");
|
|
392
|
+
return useConnectResource("/company-profile", { requiresSession: false });
|
|
393
393
|
}
|
|
394
394
|
/** The apps this workspace can open. */
|
|
395
395
|
export function useConnectApps() {
|
|
@@ -430,10 +430,15 @@ export function useConnectSessions() {
|
|
|
430
430
|
/**
|
|
431
431
|
* One bridge read, shared through the provider's cache.
|
|
432
432
|
*
|
|
433
|
-
*
|
|
434
|
-
* firing before one exists just buys a guaranteed 401.
|
|
433
|
+
* Waits for a session by default: every resource route but one needs it, so
|
|
434
|
+
* firing before one exists just buys a guaranteed 401. `requiresSession:
|
|
435
|
+
* false` opts out, and the reason it exists is the grievance notice - a
|
|
436
|
+
* statutory contact is at its most useful on a public page a signed-out
|
|
437
|
+
* visitor is reading, so a hook that answered `null` until someone logged in
|
|
438
|
+
* would render nothing at exactly the moment that matters.
|
|
435
439
|
*/
|
|
436
|
-
export function useConnectResource(path) {
|
|
440
|
+
export function useConnectResource(path, options) {
|
|
441
|
+
const requiresSession = options?.requiresSession ?? true;
|
|
437
442
|
const { fetchResource, session, isLoading: sessionLoading } = useConnect();
|
|
438
443
|
// Stamped with the `path` it belongs to, so switching paths reports loading
|
|
439
444
|
// without a synchronous `setIsLoading(true)` - and, more usefully, so a
|
|
@@ -444,7 +449,7 @@ export function useConnectResource(path) {
|
|
|
444
449
|
// Signed out is handled by derivation below rather than by writing state
|
|
445
450
|
// here - a synchronous setState inside an effect costs an extra render pass
|
|
446
451
|
// and is what `react-hooks/set-state-in-effect` exists to catch.
|
|
447
|
-
if (sessionLoading || !session?.user)
|
|
452
|
+
if (requiresSession && (sessionLoading || !session?.user))
|
|
448
453
|
return;
|
|
449
454
|
let active = true;
|
|
450
455
|
fetchResource(path)
|
|
@@ -463,8 +468,10 @@ export function useConnectResource(path) {
|
|
|
463
468
|
// `version` is otherwise unread - it exists purely to force this effect
|
|
464
469
|
// to re-run on `refetch()`, after the caller has already evicted this
|
|
465
470
|
// path's cache entry via `mutateResource`.
|
|
466
|
-
}, [fetchResource, path, session?.user, sessionLoading, version]);
|
|
467
|
-
|
|
471
|
+
}, [fetchResource, path, requiresSession, session?.user, sessionLoading, version]);
|
|
472
|
+
// A public read is never settled by the absence of a session - it is
|
|
473
|
+
// genuinely pending until the fetch lands.
|
|
474
|
+
const signedOut = requiresSession && !sessionLoading && !session?.user;
|
|
468
475
|
const loaded = entry?.path === path ? entry : null;
|
|
469
476
|
return {
|
|
470
477
|
data: loaded?.data ?? null,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AccessResource } from "./resources/access.js";
|
|
2
2
|
import { AppsResource } from "./resources/apps.js";
|
|
3
3
|
import { AiResource } from "./resources/ai.js";
|
|
4
|
+
import { EnrichmentResource } from "./resources/enrichment.js";
|
|
4
5
|
import { DataResource, storePassthrough } from "./resources/data.js";
|
|
5
6
|
import { WorkspacesResource } from "./resources/workspaces.js";
|
|
6
7
|
import { SchedulingResource } from "./resources/scheduling.js";
|
|
@@ -90,6 +91,7 @@ export class ConnectClient {
|
|
|
90
91
|
calls;
|
|
91
92
|
/** RAG chat and structured extraction. */
|
|
92
93
|
ai;
|
|
94
|
+
enrichment;
|
|
93
95
|
/** Your application's own document store - see `DataResource`. */
|
|
94
96
|
data;
|
|
95
97
|
/** Your application's key/value store - see `KvResource`. */
|
|
@@ -141,6 +143,7 @@ export class ConnectClient {
|
|
|
141
143
|
this.channels = new ChannelsResource(transport);
|
|
142
144
|
this.calls = new CallsResource(transport);
|
|
143
145
|
this.ai = new AiResource(transport);
|
|
146
|
+
this.enrichment = new EnrichmentResource(transport);
|
|
144
147
|
this.data = new DataResource(transport);
|
|
145
148
|
this.kv = new KvResource(transport);
|
|
146
149
|
this.otp = new OtpResource(transport);
|
|
@@ -154,4 +157,4 @@ export class ConnectClient {
|
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
157
|
-
export const SDK_VERSION = "0.
|
|
160
|
+
export const SDK_VERSION = "0.24.0";
|
|
@@ -602,8 +602,14 @@ export function createConnectHandler(options) {
|
|
|
602
602
|
const { workspaceId } = await requireWorkspace(request);
|
|
603
603
|
return json({ toolkits: await client.integrations.list(workspaceId) });
|
|
604
604
|
},
|
|
605
|
+
/*
|
|
606
|
+
* `userId` comes from the session alongside `workspaceId`, never from the
|
|
607
|
+
* body - it is stored as `connectedByUserId`, so letting the browser name
|
|
608
|
+
* it would let anyone sign a workspace-shared connection with someone
|
|
609
|
+
* else's name.
|
|
610
|
+
*/
|
|
605
611
|
"POST /integrations/connect": async (request) => {
|
|
606
|
-
const { workspaceId } = await requireWorkspace(request);
|
|
612
|
+
const { workspaceId, user } = await requireWorkspace(request);
|
|
607
613
|
const body = (await request.json().catch(() => null));
|
|
608
614
|
if (!body?.toolkitSlug || !body?.returnUrl) {
|
|
609
615
|
throw new ConnectError({
|
|
@@ -613,7 +619,11 @@ export function createConnectHandler(options) {
|
|
|
613
619
|
endpoint: "POST /integrations/connect",
|
|
614
620
|
});
|
|
615
621
|
}
|
|
616
|
-
return json(await client.integrations.initiateConnect(workspaceId,
|
|
622
|
+
return json(await client.integrations.initiateConnect(workspaceId, {
|
|
623
|
+
toolkitSlug: body.toolkitSlug,
|
|
624
|
+
returnUrl: body.returnUrl,
|
|
625
|
+
userId: user.userId,
|
|
626
|
+
}));
|
|
617
627
|
},
|
|
618
628
|
"POST /integrations/disconnect": async (request) => {
|
|
619
629
|
const { workspaceId } = await requireWorkspace(request);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { actingAs } from "./acting-as.js";
|
|
2
|
+
import { request } from "../http.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 class EnrichmentResource {
|
|
21
|
+
transport;
|
|
22
|
+
constructor(transport) {
|
|
23
|
+
this.transport = transport;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Look a company up from a work email address or a domain.
|
|
27
|
+
*
|
|
28
|
+
* Send whichever you hold; `domain` wins when both are given. A consumer
|
|
29
|
+
* address (`founder@gmail.com`) is answered with
|
|
30
|
+
* `{ status: "skipped", reason: "consumer_domain" }` rather than an error -
|
|
31
|
+
* it names a person, not an employer.
|
|
32
|
+
*
|
|
33
|
+
* `asUser` attributes the generation to the signed-in caller's workspace,
|
|
34
|
+
* resolved from their session token. Without it the call is attributed to
|
|
35
|
+
* your application.
|
|
36
|
+
*/
|
|
37
|
+
company(input, options = {}) {
|
|
38
|
+
return request(this.transport, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
path: "/enrichment/company",
|
|
41
|
+
// `asUser` never reaches the body - it is a credential, and Connect
|
|
42
|
+
// reads it from the header.
|
|
43
|
+
json: {
|
|
44
|
+
...(input.email ? { email: input.email } : {}),
|
|
45
|
+
...(input.domain ? { domain: input.domain } : {}),
|
|
46
|
+
...(input.refresh ? { refresh: true } : {}),
|
|
47
|
+
},
|
|
48
|
+
headers: actingAs(input.asUser),
|
|
49
|
+
signal: options.signal,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -32,15 +32,17 @@ export class IntegrationsResource {
|
|
|
32
32
|
* itself. Reconnecting a disconnected/failed toolkit calls this same
|
|
33
33
|
* method - there is no separate reconnect.
|
|
34
34
|
*
|
|
35
|
-
* Meant to be called on behalf of a signed-in workspace admin (this is what
|
|
36
|
-
* bridge's fixed `POST /integrations/connect` route does), not as a bare
|
|
37
|
-
* background action with no admin present to complete the OAuth screen.
|
|
35
|
+
* Meant to be called on behalf of a signed-in workspace admin (this is what
|
|
36
|
+
* the bridge's fixed `POST /integrations/connect` route does), not as a bare
|
|
37
|
+
* background action with no admin present to complete the OAuth screen. That
|
|
38
|
+
* is why `userId` is part of the input rather than inferred - see
|
|
39
|
+
* `InitiateIntegrationConnectionInput`.
|
|
38
40
|
*/
|
|
39
|
-
async initiateConnect(workspaceId,
|
|
41
|
+
async initiateConnect(workspaceId, input) {
|
|
40
42
|
return request(this.transport, {
|
|
41
43
|
method: "POST",
|
|
42
44
|
path: "/integrations/connect",
|
|
43
|
-
json: { workspaceId,
|
|
45
|
+
json: { workspaceId, ...input },
|
|
44
46
|
});
|
|
45
47
|
}
|
|
46
48
|
/** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { actingAs } from "./acting-as.js";
|
|
2
2
|
import { request } from "../http.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
|
|
@@ -67,4 +67,75 @@ export class WorkspacesResource {
|
|
|
67
67
|
});
|
|
68
68
|
return result.members;
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Seats, what the caller may do with them, and the invitations already
|
|
72
|
+
* holding one - everything a team screen needs to decide what to draw.
|
|
73
|
+
*
|
|
74
|
+
* Behind `members:manage` rather than `workspaces:read`, and gated on the
|
|
75
|
+
* caller's own `member:invite`: it names people who are not members yet.
|
|
76
|
+
*/
|
|
77
|
+
access(options) {
|
|
78
|
+
return request(this.transport, {
|
|
79
|
+
method: "GET",
|
|
80
|
+
path: "/workspaces/members/access",
|
|
81
|
+
headers: actingAs(options.asUser),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Invite someone into the workspace, with access to your application.
|
|
86
|
+
*
|
|
87
|
+
* `roleKey` names a role from your own catalog (or the reserved `"admin"`);
|
|
88
|
+
* omit it to add someone to the workspace with no access to your
|
|
89
|
+
* application. You cannot send a Connect role - the grant is built server
|
|
90
|
+
* side as `[member, app:<yourKey>:<roleKey>]`, so nothing this method can
|
|
91
|
+
* express reaches outside your application.
|
|
92
|
+
*
|
|
93
|
+
* Spends a member seat when the invitation is created, not when it is
|
|
94
|
+
* accepted. Throws `ConnectError` with status 409 and a message written to
|
|
95
|
+
* be shown when there is no seat, and 403 when the caller is inviting
|
|
96
|
+
* someone with access they do not hold themselves.
|
|
97
|
+
*/
|
|
98
|
+
invite(input, options) {
|
|
99
|
+
return request(this.transport, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
path: "/workspaces/members/invite",
|
|
102
|
+
headers: actingAs(options.asUser),
|
|
103
|
+
json: input,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Grant or revoke **your application's** role for an existing member.
|
|
108
|
+
* `roleKey: null` revokes it.
|
|
109
|
+
*
|
|
110
|
+
* Moves only your own grant: Connect recomputes the member's roles as what
|
|
111
|
+
* they hold now, minus any grant naming your application, plus the one you
|
|
112
|
+
* asked for. Their workspace role and every other application they use are
|
|
113
|
+
* untouched, and there is no form of this that removes them from the
|
|
114
|
+
* workspace.
|
|
115
|
+
*
|
|
116
|
+
* Re-sending the role they already have is a no-op rather than an error, so
|
|
117
|
+
* a form that submits whatever is on screen is safe.
|
|
118
|
+
*/
|
|
119
|
+
setAccess(input, options) {
|
|
120
|
+
return request(this.transport, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
path: "/workspaces/members/access",
|
|
123
|
+
headers: actingAs(options.asUser),
|
|
124
|
+
json: input,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Withdraw a pending invitation your application sent, freeing its seat.
|
|
129
|
+
*
|
|
130
|
+
* Only yours - an invitation carrying another application's grant is a 404,
|
|
131
|
+
* deliberately indistinguishable from one that does not exist.
|
|
132
|
+
*/
|
|
133
|
+
async revokeInvitation(input, options) {
|
|
134
|
+
await request(this.transport, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
path: "/workspaces/invitations/revoke",
|
|
137
|
+
headers: actingAs(options.asUser),
|
|
138
|
+
json: input,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
70
141
|
}
|
|
@@ -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, ConnectOneTapHint, ConnectOneTapFailure, } 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";
|
|
@@ -84,7 +84,7 @@ export { ConnectFilterTabs } from "./components/filter-tabs.js";
|
|
|
84
84
|
export type { ConnectFilterTabsProps, ConnectFilterTabsOption } from "./components/filter-tabs.js";
|
|
85
85
|
export { useConnectCheckout, buildCheckoutUrl } from "./use-checkout.js";
|
|
86
86
|
export type { ConnectCheckoutStatus, ConnectCheckoutState, ConnectEntitlements, StartCheckoutOptions, CheckoutUrlOptions, } from "./use-checkout.js";
|
|
87
|
-
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";
|
|
88
88
|
export type { PlanAction, PlanTier, ComparablePlan } from "./components/plan-pricing.js";
|
|
89
89
|
export { ConnectWorkspaceSwitcher } from "./components/workspace-switcher.js";
|
|
90
90
|
export type { ConnectWorkspaceSwitcherProps } from "./components/workspace-switcher.js";
|
|
@@ -456,13 +456,30 @@ export declare function useConnectSessions(): {
|
|
|
456
456
|
*/
|
|
457
457
|
refetch(): void;
|
|
458
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
|
+
}
|
|
459
472
|
/**
|
|
460
473
|
* One bridge read, shared through the provider's cache.
|
|
461
474
|
*
|
|
462
|
-
*
|
|
463
|
-
* 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.
|
|
464
481
|
*/
|
|
465
|
-
export declare function useConnectResource<T>(path: string): {
|
|
482
|
+
export declare function useConnectResource<T>(path: string, options?: Readonly<UseConnectResourceOptions>): {
|
|
466
483
|
data: T | null;
|
|
467
484
|
isLoading: boolean;
|
|
468
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,6 +2,7 @@ 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";
|
|
@@ -89,6 +90,7 @@ export declare class ConnectClient {
|
|
|
89
90
|
readonly calls: CallsResource;
|
|
90
91
|
/** RAG chat and structured extraction. */
|
|
91
92
|
readonly ai: AiResource;
|
|
93
|
+
readonly enrichment: EnrichmentResource;
|
|
92
94
|
/** Your application's own document store - see `DataResource`. */
|
|
93
95
|
readonly data: DataResource;
|
|
94
96
|
/** Your application's key/value store - see `KvResource`. */
|
|
@@ -121,4 +123,4 @@ export declare class ConnectClient {
|
|
|
121
123
|
constructor(options: ConnectClientOptions);
|
|
122
124
|
}
|
|
123
125
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
124
|
-
export declare const SDK_VERSION = "0.
|
|
126
|
+
export declare const SDK_VERSION = "0.24.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";
|
|
@@ -25,7 +26,7 @@ export type { OtpResource } from "./resources/otp.js";
|
|
|
25
26
|
export type { LogsResource } from "./resources/logs.js";
|
|
26
27
|
export type { AnalyticsResource } from "./resources/analytics.js";
|
|
27
28
|
export type { SessionsResource } from "./resources/sessions.js";
|
|
28
|
-
export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./resources/integrations.js";
|
|
29
|
+
export type { ExecuteIntegrationActionInput, InitiateIntegrationConnectionInput, IntegrationsResource, } from "./resources/integrations.js";
|
|
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";
|
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
2
|
import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationsListResponse } from "../../types.js";
|
|
3
|
+
export interface InitiateIntegrationConnectionInput {
|
|
4
|
+
toolkitSlug: string;
|
|
5
|
+
returnUrl: string;
|
|
6
|
+
/**
|
|
7
|
+
* The signed-in member starting the connection, recorded by Connect as
|
|
8
|
+
* `connectedByUserId`.
|
|
9
|
+
*
|
|
10
|
+
* Required, and deliberately not defaulted: the connection is *shared by
|
|
11
|
+
* the whole workspace*, so "who turned this on" is the only attribution
|
|
12
|
+
* anyone gets - and a call that cannot name a person is one where nobody
|
|
13
|
+
* is present to finish the OAuth screen either.
|
|
14
|
+
*/
|
|
15
|
+
userId: string;
|
|
16
|
+
}
|
|
3
17
|
export interface ExecuteIntegrationActionInput {
|
|
4
18
|
toolkitSlug: string;
|
|
5
19
|
actionSlug: string;
|
|
@@ -28,11 +42,13 @@ export declare class IntegrationsResource {
|
|
|
28
42
|
* itself. Reconnecting a disconnected/failed toolkit calls this same
|
|
29
43
|
* method - there is no separate reconnect.
|
|
30
44
|
*
|
|
31
|
-
* Meant to be called on behalf of a signed-in workspace admin (this is what
|
|
32
|
-
* bridge's fixed `POST /integrations/connect` route does), not as a bare
|
|
33
|
-
* background action with no admin present to complete the OAuth screen.
|
|
45
|
+
* Meant to be called on behalf of a signed-in workspace admin (this is what
|
|
46
|
+
* the bridge's fixed `POST /integrations/connect` route does), not as a bare
|
|
47
|
+
* background action with no admin present to complete the OAuth screen. That
|
|
48
|
+
* is why `userId` is part of the input rather than inferred - see
|
|
49
|
+
* `InitiateIntegrationConnectionInput`.
|
|
34
50
|
*/
|
|
35
|
-
initiateConnect(workspaceId: string,
|
|
51
|
+
initiateConnect(workspaceId: string, input: InitiateIntegrationConnectionInput): Promise<InitiateIntegrationConnectionResponse>;
|
|
36
52
|
/** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
|
|
37
53
|
disconnect(workspaceId: string, toolkitSlug: string): Promise<DisconnectIntegrationResponse>;
|
|
38
54
|
/** Calls a Composio action against this workspace's connected account for the named toolkit. */
|
|
@@ -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
|
}
|