@cosmoops/connect-sdk 0.21.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 +164 -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/resources/enrichment.js +56 -0
- 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/resources/enrichment.js +52 -0
- 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 +1 -0
- package/dist/types/server/resources/enrichment.d.ts +44 -0
- package/dist/types/server/resources/workspaces.d.ts +63 -3
- package/dist/types/types.d.ts +172 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,170 @@
|
|
|
3
3
|
`@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
|
|
4
4
|
versions may carry breaking changes** and patch versions never do.
|
|
5
5
|
|
|
6
|
+
## 0.23.0
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`CompanyProfile.legalJurisdiction`** - the seat a governing-law clause
|
|
11
|
+
names, e.g. `"Chennai, Tamil Nadu"`.
|
|
12
|
+
|
|
13
|
+
**This is not the registered address**, and that is why it is its own field
|
|
14
|
+
rather than something to derive. An entity registered in one state routinely
|
|
15
|
+
names another as the venue for disputes, and the applications that kept
|
|
16
|
+
their own copies of this had reached for the registered address instead -
|
|
17
|
+
publishing two different venues on two terms pages operated by the same
|
|
18
|
+
company. `null` when the operator has not published one, in which case a
|
|
19
|
+
terms page should omit the venue rather than substitute anything.
|
|
20
|
+
|
|
21
|
+
- **`CompanyProfile.brandName` is now editable by the platform operator.** The
|
|
22
|
+
field is unchanged on the wire; what changed is that Connect served it from
|
|
23
|
+
a hardcoded constant until now, which made it the one company fact nobody
|
|
24
|
+
could correct without a deploy.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **`GET /company-profile` can now answer `503 unavailable`.** Connect used to
|
|
29
|
+
resolve every field against hardcoded defaults, so this endpoint always
|
|
30
|
+
answered - including with a legal name and grievance officer that nobody had
|
|
31
|
+
checked against the operator's own record. Those defaults are gone, and an
|
|
32
|
+
unconfigured platform refuses the read instead.
|
|
33
|
+
|
|
34
|
+
Nothing you wrote needs changing: `<ConnectGrievanceNotice />` already
|
|
35
|
+
renders `null` on failure, and `useConnectCompanyProfile()` surfaces it as
|
|
36
|
+
an error like any other. **Do not add a fallback of your own** - a copy kept
|
|
37
|
+
for outages is still a copy, and going stale is the failure this endpoint
|
|
38
|
+
exists to prevent.
|
|
39
|
+
|
|
40
|
+
- **`connect.enrichment.company()`** - fill an account in from a work email
|
|
41
|
+
address, instead of asking somebody to type it.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const result = await connect.enrichment.company({ email: "priya@acme.test" });
|
|
45
|
+
if (result.status === "enriched") {
|
|
46
|
+
form.setValues(result.profile); // then let the person confirm it
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Connect reads the company's **own website** - the domain your input yields,
|
|
51
|
+
plus About/Contact pages linked from its homepage - and returns what it found
|
|
52
|
+
with `citations` naming the URL each claim came from. It does not search the
|
|
53
|
+
web, and it never answers from a model's recall: a field the pages did not
|
|
54
|
+
state comes back `null`. An invented industry is indistinguishable from a
|
|
55
|
+
real one, and an invented address reaches an invoice.
|
|
56
|
+
|
|
57
|
+
`status: "skipped"` is a normal 200, not an error - most records legitimately
|
|
58
|
+
have nothing to enrich. `consumer_domain` (a `gmail.com` address names a
|
|
59
|
+
person, not an employer) and `blocked_domain` will never succeed on a retry;
|
|
60
|
+
`unreachable` and `no_signal` may. `confidence` is derived from how many
|
|
61
|
+
fields the pages actually supported, not from the model's opinion of itself,
|
|
62
|
+
so `low` means "offer this" and `high` means "safe to pre-fill".
|
|
63
|
+
|
|
64
|
+
Needs an API key scoped to the new **`enrichment:read`**. That is separate
|
|
65
|
+
from `ai:extract` because the two differ in what they touch rather than only
|
|
66
|
+
in what they cost: extraction reads a file you already hold, this makes
|
|
67
|
+
Connect fetch a third-party website on a string that reached you from a
|
|
68
|
+
stranger.
|
|
69
|
+
|
|
70
|
+
- **`connect.workspaces.access()`, `.invite()`, `.setAccess()` and
|
|
71
|
+
`.revokeInvitation()`** - manage the customer's roster from inside your own
|
|
72
|
+
application, instead of sending them to Connect's team page.
|
|
73
|
+
|
|
74
|
+
`members()` has always been a read, so a partner's own team screen could
|
|
75
|
+
show who had access and then had to hand off to Connect for every change.
|
|
76
|
+
These four close that loop, and all of them take `asUser`: the workspace and
|
|
77
|
+
the actor come from the caller's session token, never from a parameter.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const { capacity, viewer } = await connect.workspaces.access({ asUser: token });
|
|
81
|
+
if (viewer.canInvite && capacity.canAdd) {
|
|
82
|
+
await connect.workspaces.invite({ inviteeEmail: "priya@acme.test", roleKey: "rep" }, { asUser: token });
|
|
83
|
+
}
|
|
84
|
+
await connect.workspaces.setAccess({ userId, roleKey: null }, { asUser: token }); // revoke
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**You can only ever move your own application's grant.** The wire carries a
|
|
88
|
+
`roleKey` from your catalog rather than a roles array, and Connect computes
|
|
89
|
+
the member's next roles as *what they hold now, minus any grant naming your
|
|
90
|
+
application, plus yours* - so nothing these methods can express reaches a
|
|
91
|
+
workspace-wide Connect role, another application's access, or removes
|
|
92
|
+
someone from the workspace.
|
|
93
|
+
|
|
94
|
+
Two gates apply on top of the API key's new `members:manage` scope, and both
|
|
95
|
+
are about the person rather than your application: the signed-in caller must
|
|
96
|
+
hold Connect's own `member:invite` / `member:update` on the workspace, and
|
|
97
|
+
they cannot grant access they do not hold themselves. `viewer` on the
|
|
98
|
+
`access()` response is the first of those, resolved, so you can render your
|
|
99
|
+
controls from it instead of letting a button 403.
|
|
100
|
+
|
|
101
|
+
**Seats are the workspace's, not your plan's.** `capacity.included` is a
|
|
102
|
+
baseline plus every active subscription's member allowance folded across
|
|
103
|
+
applications, and `used` counts pending invitations as well as members,
|
|
104
|
+
because an invitation is a seat already spoken for. `reason` separates "no
|
|
105
|
+
plan grants member seats" (`no_allowance` - sell them one) from "every seat
|
|
106
|
+
is taken" (`exhausted` - an upgrade, or a removal); those read alike and
|
|
107
|
+
need different words on screen. The read is advisory - the authoritative
|
|
108
|
+
check runs inside `invite()`, which is where two people claiming the last
|
|
109
|
+
seat at once is settled.
|
|
110
|
+
|
|
111
|
+
`revokeInvitation()` is not optional decoration: an invitation sent to a
|
|
112
|
+
mistyped address holds a seat for its full seven days, and an application
|
|
113
|
+
that can spend one but not release it leaves a customer paying for a seat
|
|
114
|
+
nobody can use.
|
|
115
|
+
|
|
116
|
+
**Server-side only, deliberately.** None of the four is exposed through
|
|
117
|
+
`createConnectHandler`, which stays four fixed reads plus sign-out and the
|
|
118
|
+
workspace switch. A bridge route is a capability handed to any script
|
|
119
|
+
running in your page, and "grant this account admin" is the highest-value
|
|
120
|
+
one there is; call these from your own server, where the API key already
|
|
121
|
+
lives.
|
|
122
|
+
|
|
123
|
+
## 0.22.0
|
|
124
|
+
|
|
125
|
+
### Fixed
|
|
126
|
+
|
|
127
|
+
- **`<ConnectGrievanceNotice />` rendered nothing for a signed-out visitor -
|
|
128
|
+
which is most of the people who read a privacy page.**
|
|
129
|
+
|
|
130
|
+
The bridge route `GET /company-profile` is deliberately unauthenticated: the
|
|
131
|
+
answer is a fact about the platform operator, identical for every caller and
|
|
132
|
+
already on the public web, and requiring a session would hide the redressal
|
|
133
|
+
channel at the one moment it most needs to be legible. The browser half
|
|
134
|
+
never got that exemption. `useConnectCompanyProfile` goes through
|
|
135
|
+
`useConnectResource`, which waits for `session.user` before it reads - right
|
|
136
|
+
for every other route, and the reason this component was blank on exactly
|
|
137
|
+
the public page the docs recommend putting it on.
|
|
138
|
+
|
|
139
|
+
`useConnectResource` now takes `{ requiresSession: false }`, and the company
|
|
140
|
+
profile is its only caller. It widens no access - the bridge decides that,
|
|
141
|
+
not the hook.
|
|
142
|
+
|
|
143
|
+
- **`<ConnectPlans />`: a card reading "Subscribe" opened Connect's *change
|
|
144
|
+
plan* screen, which answered a 404.**
|
|
145
|
+
|
|
146
|
+
The label and the URL were two separate decisions. `resolveAction` needs the
|
|
147
|
+
**current plan** to classify a change and falls back to `"subscribe"`
|
|
148
|
+
without one; the checkout URL was built from the mere presence of a
|
|
149
|
+
subscription *row*. Those disagree in a state Connect reports routinely:
|
|
150
|
+
`SubscriptionResponse` carries `plan: null` beside a live `subscription`
|
|
151
|
+
whenever that plan has been deleted from the application's catalogue - which
|
|
152
|
+
re-seeding an application does. The button then said "Subscribe" and opened
|
|
153
|
+
`/billing/subscriptions/{id}/change-plan/{planId}`, where Connect could not
|
|
154
|
+
read the current price either and rendered its not-found page.
|
|
155
|
+
|
|
156
|
+
Both now come from the same `PlanAction`. When the grid cannot classify a
|
|
157
|
+
change it hands off to the subscribe route, which re-reads the workspace's
|
|
158
|
+
live subscriptions server-side and redirects to the change-plan screen
|
|
159
|
+
itself when there is genuinely one to change - so Connect decides, and the
|
|
160
|
+
grid does not have to be sure.
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
|
|
164
|
+
- **`checkoutSubscriptionId(action, current)`** - the other half of
|
|
165
|
+
`resolveAction`, exported alongside it. A partner writing its own pricing
|
|
166
|
+
grid has to turn that action into a checkout URL, and reading the
|
|
167
|
+
subscription id straight off the entitlements response is exactly the bug
|
|
168
|
+
above. Pass its result as `buildCheckoutUrl`'s `subscriptionId`.
|
|
169
|
+
|
|
6
170
|
## 0.21.0
|
|
7
171
|
|
|
8
172
|
### Added
|
|
@@ -28,6 +28,7 @@ exports.resolvePlanGroup = resolvePlanGroup;
|
|
|
28
28
|
exports.groupPlansByTier = groupPlansByTier;
|
|
29
29
|
exports.annualSaving = annualSaving;
|
|
30
30
|
exports.resolveAction = resolveAction;
|
|
31
|
+
exports.checkoutSubscriptionId = checkoutSubscriptionId;
|
|
31
32
|
/**
|
|
32
33
|
* Whether a plan costs nothing - the SDK's copy of Connect's `isFreeSelection`
|
|
33
34
|
* (`packages/data/src/payments/free-plan.ts`), which is what the server
|
|
@@ -207,3 +208,30 @@ exports.ACTION_LABEL = {
|
|
|
207
208
|
"switch-to-monthly": "Switch to monthly",
|
|
208
209
|
subscribe: "Subscribe",
|
|
209
210
|
};
|
|
211
|
+
/**
|
|
212
|
+
* The subscription id a checkout hand-off should carry for `action` - or
|
|
213
|
+
* `null`, meaning "send them to Connect's *subscribe* screen".
|
|
214
|
+
*
|
|
215
|
+
* Paired with `resolveAction` deliberately: the button's label and the URL it
|
|
216
|
+
* opens are one decision, and deriving the URL from the mere *presence* of a
|
|
217
|
+
* subscription row instead is what sent a card reading "Subscribe" to
|
|
218
|
+
* Connect's change-plan screen, which answered `notFound()`.
|
|
219
|
+
*
|
|
220
|
+
* The gap is real rather than hypothetical. `resolveAction` needs the
|
|
221
|
+
* **current plan** to classify a change and answers `"subscribe"` without
|
|
222
|
+
* one - and `SubscriptionResponse` carries `plan: null` beside a perfectly
|
|
223
|
+
* live `subscription` whenever that plan has been deleted from the catalogue
|
|
224
|
+
* out from under it, which a re-seeded application does. Connect's change-plan
|
|
225
|
+
* route is stuck in the same place for the same reason: it cannot prorate
|
|
226
|
+
* against a price it cannot read.
|
|
227
|
+
*
|
|
228
|
+
* Sending those to `/apps/.../subscribe/...` is a real hand-off, not a guess:
|
|
229
|
+
* that route re-reads the workspace's live subscriptions server-side and
|
|
230
|
+
* redirects to the change-plan screen itself when there genuinely is one to
|
|
231
|
+
* change. Connect decides, so the grid does not have to be sure.
|
|
232
|
+
*/
|
|
233
|
+
function checkoutSubscriptionId(action, current) {
|
|
234
|
+
if (action === "subscribe")
|
|
235
|
+
return null;
|
|
236
|
+
return current?.subscription?.id ?? null;
|
|
237
|
+
}
|
|
@@ -83,8 +83,13 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
83
83
|
* created in `"created"` and only flips to active when the payment webhook
|
|
84
84
|
* lands, which can be minutes later. Holding a customer on a spinner until
|
|
85
85
|
* then would misreport a completed checkout as an abandoned one.
|
|
86
|
+
*
|
|
87
|
+
* `action` is threaded through rather than re-derived: it is the same
|
|
88
|
+
* decision the card's label came from, and `checkoutSubscriptionId` is what
|
|
89
|
+
* keeps the two from disagreeing - see its doc comment for the 404 that
|
|
90
|
+
* disagreement produced.
|
|
86
91
|
*/
|
|
87
|
-
function startCheckout(plan) {
|
|
92
|
+
function startCheckout(plan, action) {
|
|
88
93
|
if (!applicationId)
|
|
89
94
|
return;
|
|
90
95
|
checkout.start({
|
|
@@ -92,7 +97,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
92
97
|
appUrl,
|
|
93
98
|
applicationId,
|
|
94
99
|
planId: plan.id,
|
|
95
|
-
subscriptionId:
|
|
100
|
+
subscriptionId: (0, plan_pricing_js_1.checkoutSubscriptionId)(action, subscription),
|
|
96
101
|
coupon,
|
|
97
102
|
}),
|
|
98
103
|
isSettled: (next) => next.subscription.plan?.id === plan.id,
|
|
@@ -142,7 +147,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
142
147
|
// plan would race the first and one of them would be
|
|
143
148
|
// rejected on arrival.
|
|
144
149
|
isDisabled: isCurrent || checkout.status === "waiting", onPress: () => setPending({ plan, action }), children: [plan_pricing_js_1.ACTION_LABEL[action], !isCurrent && (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }) })] }) }, tier.planGroup));
|
|
145
|
-
}) }), subscription?.subscription && ((0, jsx_runtime_1.jsx)(ConnectUnsubscribeRow, { subscription: subscription.subscription, appUrl: appUrl, checkout: checkout })), (0, jsx_runtime_1.jsx)(subscribe_dialog_js_1.ConnectSubscribeDialog, { plan: pending?.plan ?? null, action: pending?.action ?? "subscribe", current: subscription, coupon: hideCoupon ? undefined : coupon, onClose: () => setPending(null), onConfirm: () => pending && startCheckout(pending.plan) })] }) }));
|
|
150
|
+
}) }), subscription?.subscription && ((0, jsx_runtime_1.jsx)(ConnectUnsubscribeRow, { subscription: subscription.subscription, appUrl: appUrl, checkout: checkout })), (0, jsx_runtime_1.jsx)(subscribe_dialog_js_1.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) })] }) }));
|
|
146
151
|
}
|
|
147
152
|
/**
|
|
148
153
|
* What the page says while the customer is off in Connect, and after.
|
package/dist/cjs/client/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.flattenNav = exports.normalizeNav = exports.useActivePath = exports.ConnectSideNav = exports.ConnectSidebarNav = exports.RAIL_EXPANDED = exports.RAIL_COLLAPSED = exports.sidebarRowClass = exports.useConnectShell = exports.ConnectShellProvider = exports.ConnectSidebarRow = exports.ConnectSidebarFooter = exports.ConnectSidebarBody = exports.ConnectSidebarHeader = exports.ConnectSidebar = exports.AppShell = exports.ConnectAccountSessions = exports.ConnectSignOutButton = exports.ConnectHomeButton = exports.ConnectProfileButton = exports.ConnectInvoicesButton = exports.ConnectManageSubscriptionButton = exports.ConnectUserMenu = exports.ConnectProfileModal = exports.ConnectUserButton = exports.ConnectOneTap = exports.ConnectRegisterButton = exports.ConnectLoginButton = exports.ConnectLogo = exports.ConnectMark = exports.ConnectGrievanceNotice = exports.isConnectError = exports.ConnectError = exports.normalizePlanFeatures = exports.blockingBlockers = exports.CONNECT_APP_URL = exports.useConnectResource = exports.useConnectIntegrations = exports.useConnectSessions = exports.useConnectAddonUsage = exports.useConnectCompanyProfile = exports.useConnectApps = exports.useConnectPlans = exports.useConnectEntitlements = exports.useConnectPlatformAccess = exports.useConnectAudience = exports.useConnectWorkspaceType = exports.useConnectUser = exports.useConnect = exports.ConnectProvider = void 0;
|
|
5
5
|
exports.resolveAction = exports.monthlyOf = exports.formatPrice = exports.buildCheckoutUrl = exports.useConnectCheckout = exports.ConnectFilterTabs = exports.ConnectCursorPagination = exports.ConnectConfirmDialog = exports.ConnectLinkButton = exports.ConnectPlansPage = exports.ConnectUsageMeter = exports.ConnectPlatformGate = exports.ConnectWorkspaceTypeGate = exports.useConnectFeature = exports.ConnectFeatureGate = exports.useConnectAccessGate = exports.ConnectAccessGate = exports.ConnectReadinessGate = exports.ConnectReadinessBanner = exports.resolveSubscriptionState = exports.ConnectSeatUsage = exports.ConnectTrialBanner = exports.ConnectSubscriptionCard = exports.ConnectNoSubscriptionCard = exports.ConnectSubscriptionGate = exports.ConnectSubscribeDialog = exports.ConnectPlans = exports.ConnectIntegrationsModal = exports.AI_TONE_DOT = exports.AI_TONE_CHIP = exports.AI_EMPTY_VALUE = exports.aiStatusTone = exports.isNumericAiFormat = exports.formatAiValue = exports.AiSources = exports.AiSteps = exports.AiDetails = exports.AiTimeline = exports.AiMetrics = exports.AiChart = exports.AiTable = exports.ConnectAskAi = exports.ConnectAppsGrid = exports.isLinkableConnectScreen = exports.matchConnectScreen = exports.filterNavForAudience = exports.gateExemptPaths = exports.isNavItemActive = exports.resolveActivePath = exports.normalizePath = void 0;
|
|
6
|
-
exports.zonedInstant = exports.dayColumns = exports.layoutBookings = exports.useBookingActions = exports.useBookings = exports.useAvailableSlots = exports.useBookableEventTypes = exports.ConnectBookingDetail = exports.ConnectBookingDialog = exports.ConnectBookingCalendar = exports.useConnectWorkspaceMembers = exports.useConnectWorkspaces = exports.ConnectWorkspaceSwitcher = exports.resolvePlanGroup = exports.groupPlansByTier = exports.isFreePlan = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = void 0;
|
|
6
|
+
exports.zonedInstant = exports.dayColumns = exports.layoutBookings = exports.useBookingActions = exports.useBookings = exports.useAvailableSlots = exports.useBookableEventTypes = exports.ConnectBookingDetail = exports.ConnectBookingDialog = exports.ConnectBookingCalendar = exports.useConnectWorkspaceMembers = exports.useConnectWorkspaces = exports.ConnectWorkspaceSwitcher = exports.resolvePlanGroup = exports.groupPlansByTier = exports.isFreePlan = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = exports.checkoutSubscriptionId = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Browser half of `@cosmoops/connect-sdk` - React components and hooks.
|
|
9
9
|
*
|
|
@@ -210,6 +210,12 @@ var plan_pricing_js_1 = require("./components/plan-pricing.js");
|
|
|
210
210
|
Object.defineProperty(exports, "formatPrice", { enumerable: true, get: function () { return plan_pricing_js_1.formatPrice; } });
|
|
211
211
|
Object.defineProperty(exports, "monthlyOf", { enumerable: true, get: function () { return plan_pricing_js_1.monthlyOf; } });
|
|
212
212
|
Object.defineProperty(exports, "resolveAction", { enumerable: true, get: function () { return plan_pricing_js_1.resolveAction; } });
|
|
213
|
+
// The other half of `resolveAction`, and exported with it deliberately: a
|
|
214
|
+
// partner writing its own pricing grid has to turn that action into a
|
|
215
|
+
// checkout URL, and reading the subscription id straight off the
|
|
216
|
+
// entitlements response instead is what sent a "Subscribe" button to
|
|
217
|
+
// Connect's change-plan screen. See its doc comment.
|
|
218
|
+
Object.defineProperty(exports, "checkoutSubscriptionId", { enumerable: true, get: function () { return plan_pricing_js_1.checkoutSubscriptionId; } });
|
|
213
219
|
Object.defineProperty(exports, "annualSaving", { enumerable: true, get: function () { return plan_pricing_js_1.annualSaving; } });
|
|
214
220
|
Object.defineProperty(exports, "discountLabel", { enumerable: true, get: function () { return plan_pricing_js_1.discountLabel; } });
|
|
215
221
|
Object.defineProperty(exports, "ACTION_LABEL", { enumerable: true, get: function () { return plan_pricing_js_1.ACTION_LABEL; } });
|
|
@@ -408,7 +408,7 @@ function useConnectPlans() {
|
|
|
408
408
|
* `CompanyProfile`. `<ConnectGrievanceNotice>` renders it for you.
|
|
409
409
|
*/
|
|
410
410
|
function useConnectCompanyProfile() {
|
|
411
|
-
return useConnectResource("/company-profile");
|
|
411
|
+
return useConnectResource("/company-profile", { requiresSession: false });
|
|
412
412
|
}
|
|
413
413
|
/** The apps this workspace can open. */
|
|
414
414
|
function useConnectApps() {
|
|
@@ -449,10 +449,15 @@ function useConnectSessions() {
|
|
|
449
449
|
/**
|
|
450
450
|
* One bridge read, shared through the provider's cache.
|
|
451
451
|
*
|
|
452
|
-
*
|
|
453
|
-
* firing before one exists just buys a guaranteed 401.
|
|
452
|
+
* Waits for a session by default: every resource route but one needs it, so
|
|
453
|
+
* firing before one exists just buys a guaranteed 401. `requiresSession:
|
|
454
|
+
* false` opts out, and the reason it exists is the grievance notice - a
|
|
455
|
+
* statutory contact is at its most useful on a public page a signed-out
|
|
456
|
+
* visitor is reading, so a hook that answered `null` until someone logged in
|
|
457
|
+
* would render nothing at exactly the moment that matters.
|
|
454
458
|
*/
|
|
455
|
-
function useConnectResource(path) {
|
|
459
|
+
function useConnectResource(path, options) {
|
|
460
|
+
const requiresSession = options?.requiresSession ?? true;
|
|
456
461
|
const { fetchResource, session, isLoading: sessionLoading } = useConnect();
|
|
457
462
|
// Stamped with the `path` it belongs to, so switching paths reports loading
|
|
458
463
|
// without a synchronous `setIsLoading(true)` - and, more usefully, so a
|
|
@@ -463,7 +468,7 @@ function useConnectResource(path) {
|
|
|
463
468
|
// Signed out is handled by derivation below rather than by writing state
|
|
464
469
|
// here - a synchronous setState inside an effect costs an extra render pass
|
|
465
470
|
// and is what `react-hooks/set-state-in-effect` exists to catch.
|
|
466
|
-
if (sessionLoading || !session?.user)
|
|
471
|
+
if (requiresSession && (sessionLoading || !session?.user))
|
|
467
472
|
return;
|
|
468
473
|
let active = true;
|
|
469
474
|
fetchResource(path)
|
|
@@ -482,8 +487,10 @@ function useConnectResource(path) {
|
|
|
482
487
|
// `version` is otherwise unread - it exists purely to force this effect
|
|
483
488
|
// to re-run on `refetch()`, after the caller has already evicted this
|
|
484
489
|
// path's cache entry via `mutateResource`.
|
|
485
|
-
}, [fetchResource, path, session?.user, sessionLoading, version]);
|
|
486
|
-
|
|
490
|
+
}, [fetchResource, path, requiresSession, session?.user, sessionLoading, version]);
|
|
491
|
+
// A public read is never settled by the absence of a session - it is
|
|
492
|
+
// genuinely pending until the fetch lands.
|
|
493
|
+
const signedOut = requiresSession && !sessionLoading && !session?.user;
|
|
487
494
|
const loaded = entry?.path === path ? entry : null;
|
|
488
495
|
return {
|
|
489
496
|
data: loaded?.data ?? null,
|
|
@@ -4,6 +4,7 @@ exports.SDK_VERSION = exports.ConnectClient = exports.DEFAULT_BASE_URL = void 0;
|
|
|
4
4
|
const access_js_1 = require("./resources/access.js");
|
|
5
5
|
const apps_js_1 = require("./resources/apps.js");
|
|
6
6
|
const ai_js_1 = require("./resources/ai.js");
|
|
7
|
+
const enrichment_js_1 = require("./resources/enrichment.js");
|
|
7
8
|
const data_js_1 = require("./resources/data.js");
|
|
8
9
|
const workspaces_js_1 = require("./resources/workspaces.js");
|
|
9
10
|
const scheduling_js_1 = require("./resources/scheduling.js");
|
|
@@ -93,6 +94,7 @@ class ConnectClient {
|
|
|
93
94
|
calls;
|
|
94
95
|
/** RAG chat and structured extraction. */
|
|
95
96
|
ai;
|
|
97
|
+
enrichment;
|
|
96
98
|
/** Your application's own document store - see `DataResource`. */
|
|
97
99
|
data;
|
|
98
100
|
/** Your application's key/value store - see `KvResource`. */
|
|
@@ -144,6 +146,7 @@ class ConnectClient {
|
|
|
144
146
|
this.channels = new channels_js_1.ChannelsResource(transport);
|
|
145
147
|
this.calls = new calls_js_1.CallsResource(transport);
|
|
146
148
|
this.ai = new ai_js_1.AiResource(transport);
|
|
149
|
+
this.enrichment = new enrichment_js_1.EnrichmentResource(transport);
|
|
147
150
|
this.data = new data_js_1.DataResource(transport);
|
|
148
151
|
this.kv = new kv_js_1.KvResource(transport);
|
|
149
152
|
this.otp = new otp_js_1.OtpResource(transport);
|
|
@@ -158,4 +161,4 @@ class ConnectClient {
|
|
|
158
161
|
}
|
|
159
162
|
exports.ConnectClient = ConnectClient;
|
|
160
163
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
161
|
-
exports.SDK_VERSION = "0.
|
|
164
|
+
exports.SDK_VERSION = "0.23.0";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnrichmentResource = void 0;
|
|
4
|
+
const acting_as_js_1 = require("./acting-as.js");
|
|
5
|
+
const http_js_1 = require("../http.js");
|
|
6
|
+
/**
|
|
7
|
+
* What Connect knows about the company at a domain.
|
|
8
|
+
*
|
|
9
|
+
* The thing behind "the account filled itself in": a record holding nothing
|
|
10
|
+
* but `priya@acme.com` can offer a name, an industry, a size band and an
|
|
11
|
+
* address instead of an empty form.
|
|
12
|
+
*
|
|
13
|
+
* Two properties are worth knowing before you wire it in, because they decide
|
|
14
|
+
* how you should render the result:
|
|
15
|
+
*
|
|
16
|
+
* - **Every value is cited, and nothing is recalled.** Connect reads the
|
|
17
|
+
* company's own website and returns the URL each claim came from. A field
|
|
18
|
+
* the pages never stated comes back `null` rather than as a plausible guess.
|
|
19
|
+
* - **It is a suggestion, not a save.** Nothing is written to your store, and
|
|
20
|
+
* nothing should be written to it without the person in front of it
|
|
21
|
+
* agreeing - which is also how HubSpot and Zoho present the same feature.
|
|
22
|
+
*/
|
|
23
|
+
class EnrichmentResource {
|
|
24
|
+
transport;
|
|
25
|
+
constructor(transport) {
|
|
26
|
+
this.transport = transport;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Look a company up from a work email address or a domain.
|
|
30
|
+
*
|
|
31
|
+
* Send whichever you hold; `domain` wins when both are given. A consumer
|
|
32
|
+
* address (`founder@gmail.com`) is answered with
|
|
33
|
+
* `{ status: "skipped", reason: "consumer_domain" }` rather than an error -
|
|
34
|
+
* it names a person, not an employer.
|
|
35
|
+
*
|
|
36
|
+
* `asUser` attributes the generation to the signed-in caller's workspace,
|
|
37
|
+
* resolved from their session token. Without it the call is attributed to
|
|
38
|
+
* your application.
|
|
39
|
+
*/
|
|
40
|
+
company(input, options = {}) {
|
|
41
|
+
return (0, http_js_1.request)(this.transport, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
path: "/enrichment/company",
|
|
44
|
+
// `asUser` never reaches the body - it is a credential, and Connect
|
|
45
|
+
// reads it from the header.
|
|
46
|
+
json: {
|
|
47
|
+
...(input.email ? { email: input.email } : {}),
|
|
48
|
+
...(input.domain ? { domain: input.domain } : {}),
|
|
49
|
+
...(input.refresh ? { refresh: true } : {}),
|
|
50
|
+
},
|
|
51
|
+
headers: (0, acting_as_js_1.actingAs)(input.asUser),
|
|
52
|
+
signal: options.signal,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.EnrichmentResource = EnrichmentResource;
|
|
@@ -4,8 +4,8 @@ exports.WorkspacesResource = void 0;
|
|
|
4
4
|
const acting_as_js_1 = require("./acting-as.js");
|
|
5
5
|
const http_js_1 = require("../http.js");
|
|
6
6
|
/**
|
|
7
|
-
* Which workspaces the signed-in user belongs to, moving between them,
|
|
8
|
-
* else is on the team.
|
|
7
|
+
* Which workspaces the signed-in user belongs to, moving between them, who
|
|
8
|
+
* else is on the team, and inviting or granting access to your application.
|
|
9
9
|
*
|
|
10
10
|
* **Every call here needs `asUser`** - the signed-in user's opaque
|
|
11
11
|
* `sessionToken`. There is no application-wide form of any of these: they take
|
|
@@ -70,5 +70,76 @@ class WorkspacesResource {
|
|
|
70
70
|
});
|
|
71
71
|
return result.members;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Seats, what the caller may do with them, and the invitations already
|
|
75
|
+
* holding one - everything a team screen needs to decide what to draw.
|
|
76
|
+
*
|
|
77
|
+
* Behind `members:manage` rather than `workspaces:read`, and gated on the
|
|
78
|
+
* caller's own `member:invite`: it names people who are not members yet.
|
|
79
|
+
*/
|
|
80
|
+
access(options) {
|
|
81
|
+
return (0, http_js_1.request)(this.transport, {
|
|
82
|
+
method: "GET",
|
|
83
|
+
path: "/workspaces/members/access",
|
|
84
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Invite someone into the workspace, with access to your application.
|
|
89
|
+
*
|
|
90
|
+
* `roleKey` names a role from your own catalog (or the reserved `"admin"`);
|
|
91
|
+
* omit it to add someone to the workspace with no access to your
|
|
92
|
+
* application. You cannot send a Connect role - the grant is built server
|
|
93
|
+
* side as `[member, app:<yourKey>:<roleKey>]`, so nothing this method can
|
|
94
|
+
* express reaches outside your application.
|
|
95
|
+
*
|
|
96
|
+
* Spends a member seat when the invitation is created, not when it is
|
|
97
|
+
* accepted. Throws `ConnectError` with status 409 and a message written to
|
|
98
|
+
* be shown when there is no seat, and 403 when the caller is inviting
|
|
99
|
+
* someone with access they do not hold themselves.
|
|
100
|
+
*/
|
|
101
|
+
invite(input, options) {
|
|
102
|
+
return (0, http_js_1.request)(this.transport, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
path: "/workspaces/members/invite",
|
|
105
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
106
|
+
json: input,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Grant or revoke **your application's** role for an existing member.
|
|
111
|
+
* `roleKey: null` revokes it.
|
|
112
|
+
*
|
|
113
|
+
* Moves only your own grant: Connect recomputes the member's roles as what
|
|
114
|
+
* they hold now, minus any grant naming your application, plus the one you
|
|
115
|
+
* asked for. Their workspace role and every other application they use are
|
|
116
|
+
* untouched, and there is no form of this that removes them from the
|
|
117
|
+
* workspace.
|
|
118
|
+
*
|
|
119
|
+
* Re-sending the role they already have is a no-op rather than an error, so
|
|
120
|
+
* a form that submits whatever is on screen is safe.
|
|
121
|
+
*/
|
|
122
|
+
setAccess(input, options) {
|
|
123
|
+
return (0, http_js_1.request)(this.transport, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
path: "/workspaces/members/access",
|
|
126
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
127
|
+
json: input,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Withdraw a pending invitation your application sent, freeing its seat.
|
|
132
|
+
*
|
|
133
|
+
* Only yours - an invitation carrying another application's grant is a 404,
|
|
134
|
+
* deliberately indistinguishable from one that does not exist.
|
|
135
|
+
*/
|
|
136
|
+
async revokeInvitation(input, options) {
|
|
137
|
+
await (0, http_js_1.request)(this.transport, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
path: "/workspaces/invitations/revoke",
|
|
140
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
141
|
+
json: input,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
73
144
|
}
|
|
74
145
|
exports.WorkspacesResource = WorkspacesResource;
|
|
@@ -194,3 +194,30 @@ export const ACTION_LABEL = {
|
|
|
194
194
|
"switch-to-monthly": "Switch to monthly",
|
|
195
195
|
subscribe: "Subscribe",
|
|
196
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* The subscription id a checkout hand-off should carry for `action` - or
|
|
199
|
+
* `null`, meaning "send them to Connect's *subscribe* screen".
|
|
200
|
+
*
|
|
201
|
+
* Paired with `resolveAction` deliberately: the button's label and the URL it
|
|
202
|
+
* opens are one decision, and deriving the URL from the mere *presence* of a
|
|
203
|
+
* subscription row instead is what sent a card reading "Subscribe" to
|
|
204
|
+
* Connect's change-plan screen, which answered `notFound()`.
|
|
205
|
+
*
|
|
206
|
+
* The gap is real rather than hypothetical. `resolveAction` needs the
|
|
207
|
+
* **current plan** to classify a change and answers `"subscribe"` without
|
|
208
|
+
* one - and `SubscriptionResponse` carries `plan: null` beside a perfectly
|
|
209
|
+
* live `subscription` whenever that plan has been deleted from the catalogue
|
|
210
|
+
* out from under it, which a re-seeded application does. Connect's change-plan
|
|
211
|
+
* route is stuck in the same place for the same reason: it cannot prorate
|
|
212
|
+
* against a price it cannot read.
|
|
213
|
+
*
|
|
214
|
+
* Sending those to `/apps/.../subscribe/...` is a real hand-off, not a guess:
|
|
215
|
+
* that route re-reads the workspace's live subscriptions server-side and
|
|
216
|
+
* redirects to the change-plan screen itself when there genuinely is one to
|
|
217
|
+
* change. Connect decides, so the grid does not have to be sure.
|
|
218
|
+
*/
|
|
219
|
+
export function checkoutSubscriptionId(action, current) {
|
|
220
|
+
if (action === "subscribe")
|
|
221
|
+
return null;
|
|
222
|
+
return current?.subscription?.id ?? null;
|
|
223
|
+
}
|
|
@@ -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.23.0";
|
|
@@ -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
|
+
}
|
|
@@ -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.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";
|
|
@@ -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,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
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -597,13 +597,30 @@ export interface GrievanceChannel {
|
|
|
597
597
|
*/
|
|
598
598
|
export interface CompanyProfile {
|
|
599
599
|
legalName: string;
|
|
600
|
-
/**
|
|
600
|
+
/**
|
|
601
|
+
* Trading name, for prose the full registered name reads oddly in.
|
|
602
|
+
*
|
|
603
|
+
* Editable by the platform operator since SDK 0.23. It was a hardcoded
|
|
604
|
+
* constant on Connect's side before that - the one company fact no
|
|
605
|
+
* operator could correct without a deploy.
|
|
606
|
+
*/
|
|
601
607
|
brandName: string;
|
|
602
608
|
registeredAddress: string;
|
|
603
609
|
contactEmail: string;
|
|
604
610
|
website: string | null;
|
|
605
611
|
/** GSTIN, `null` if the operating entity is not GST-registered. */
|
|
606
612
|
gstNumber: string | null;
|
|
613
|
+
/**
|
|
614
|
+
* The seat a governing-law clause names - `"Chennai, Tamil Nadu"`.
|
|
615
|
+
*
|
|
616
|
+
* **Not the registered address**, and the distinction is the reason it is
|
|
617
|
+
* its own field: an entity registered in one state routinely names another
|
|
618
|
+
* as the venue for disputes, and the two copies of this that applications
|
|
619
|
+
* used to keep disagreed about which. `null` when the operator has not
|
|
620
|
+
* published one, in which case a terms page should omit the venue rather
|
|
621
|
+
* than reach for `registeredAddress` instead.
|
|
622
|
+
*/
|
|
623
|
+
legalJurisdiction: string | null;
|
|
607
624
|
grievance: GrievanceChannel;
|
|
608
625
|
}
|
|
609
626
|
export interface CompanyProfileResponse {
|
|
@@ -1153,6 +1170,71 @@ export interface ConnectWorkspaceMember {
|
|
|
1153
1170
|
granted: string[];
|
|
1154
1171
|
role: string | null;
|
|
1155
1172
|
}
|
|
1173
|
+
/**
|
|
1174
|
+
* How many member seats the workspace has, and whether there is another.
|
|
1175
|
+
*
|
|
1176
|
+
* The number is the **workspace's**, not your plan's: a baseline plus
|
|
1177
|
+
* `allowances.members` from every subscription it holds, folded across
|
|
1178
|
+
* applications - so a seat bought through another product is a seat you may
|
|
1179
|
+
* fill. `included: null` means unlimited. `used` counts active members *and*
|
|
1180
|
+
* pending invitations, because an invitation is a seat already spoken for.
|
|
1181
|
+
*
|
|
1182
|
+
* Advisory. The authoritative check runs inside `invite`, which is where two
|
|
1183
|
+
* people claiming the last seat at once is settled.
|
|
1184
|
+
*/
|
|
1185
|
+
export interface ConnectMemberCapacity {
|
|
1186
|
+
included: number | null;
|
|
1187
|
+
used: number;
|
|
1188
|
+
canAdd: boolean;
|
|
1189
|
+
/**
|
|
1190
|
+
* `no_allowance` - nothing the workspace subscribes to grants member seats,
|
|
1191
|
+
* and the answer is a plan. `exhausted` - every seat is taken, and the
|
|
1192
|
+
* answer is an upgrade or a removal. They read alike and need different
|
|
1193
|
+
* next steps, so word them differently.
|
|
1194
|
+
*/
|
|
1195
|
+
reason: "no_allowance" | "exhausted" | "hard_ceiling" | null;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* What the **signed-in caller** may do with the roster, from their Connect
|
|
1199
|
+
* permissions - not your application's.
|
|
1200
|
+
*
|
|
1201
|
+
* Render your Invite and Change-role controls from this. Without it the only
|
|
1202
|
+
* way to find out is to try, which is a button that exists to fail.
|
|
1203
|
+
*/
|
|
1204
|
+
export interface ConnectMemberViewer {
|
|
1205
|
+
canInvite: boolean;
|
|
1206
|
+
canManageAccess: boolean;
|
|
1207
|
+
}
|
|
1208
|
+
/** An invitation that has been sent and not yet accepted - and is holding a seat. */
|
|
1209
|
+
export interface ConnectPendingInvitation {
|
|
1210
|
+
invitationId: string;
|
|
1211
|
+
inviteeEmail: string;
|
|
1212
|
+
/** The role on *your* application it carries, or `null`. */
|
|
1213
|
+
roleKey: string | null;
|
|
1214
|
+
/** ISO 8601. Seven days from when it was sent. */
|
|
1215
|
+
expiresAt: string;
|
|
1216
|
+
}
|
|
1217
|
+
export interface ConnectMemberAccess {
|
|
1218
|
+
capacity: ConnectMemberCapacity;
|
|
1219
|
+
viewer: ConnectMemberViewer;
|
|
1220
|
+
invitations: ConnectPendingInvitation[];
|
|
1221
|
+
}
|
|
1222
|
+
export interface ConnectInvitation {
|
|
1223
|
+
invitationId: string;
|
|
1224
|
+
inviteeEmail: string;
|
|
1225
|
+
/** ISO 8601. */
|
|
1226
|
+
expiresAt: string;
|
|
1227
|
+
/** Always present, so you can offer copy/share when the email did not go out. */
|
|
1228
|
+
inviteUrl: string;
|
|
1229
|
+
emailSent: boolean;
|
|
1230
|
+
}
|
|
1231
|
+
export interface ConnectMemberAccessResult {
|
|
1232
|
+
userId: string;
|
|
1233
|
+
/** The role on your application after the change - `null` when access was revoked. */
|
|
1234
|
+
roleKey: string | null;
|
|
1235
|
+
/** Your application's permissions this member now holds, re-resolved. */
|
|
1236
|
+
granted: string[];
|
|
1237
|
+
}
|
|
1156
1238
|
/**
|
|
1157
1239
|
* One bookable thing a workspace offers - "15-minute intro call", "site
|
|
1158
1240
|
* survey".
|
|
@@ -1282,6 +1364,95 @@ export interface ConnectContact {
|
|
|
1282
1364
|
createdAt: string;
|
|
1283
1365
|
updatedAt: string;
|
|
1284
1366
|
}
|
|
1367
|
+
/** Coarse on purpose - a headcount read off a homepage is a claim, not a measurement. */
|
|
1368
|
+
export type CompanySizeBand = "1-10" | "11-50" | "51-200" | "201-500" | "500+";
|
|
1369
|
+
/**
|
|
1370
|
+
* Where a value came from.
|
|
1371
|
+
*
|
|
1372
|
+
* - `domain` - the input itself. `url` is `null`, because there is no page.
|
|
1373
|
+
* - `website` - a page on the company's own site, at `url`.
|
|
1374
|
+
* - `registry` - a statutory register. Reserved; nothing returns it yet.
|
|
1375
|
+
*/
|
|
1376
|
+
export type EnrichmentSource = "domain" | "registry" | "website";
|
|
1377
|
+
export interface EnrichmentCitation {
|
|
1378
|
+
source: EnrichmentSource;
|
|
1379
|
+
/** The exact page read. `null` only for `domain`. */
|
|
1380
|
+
url: string | null;
|
|
1381
|
+
/** ISO-8601. */
|
|
1382
|
+
retrievedAt: string;
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Every field nullable, including the ones your own record calls required.
|
|
1386
|
+
*
|
|
1387
|
+
* An enrichment is partial by nature: a site with no address printed on it
|
|
1388
|
+
* yields no address. A shape that demanded a city would only be forcing one to
|
|
1389
|
+
* be invented.
|
|
1390
|
+
*/
|
|
1391
|
+
export interface EnrichedAddress {
|
|
1392
|
+
line1: string | null;
|
|
1393
|
+
line2: string | null;
|
|
1394
|
+
city: string | null;
|
|
1395
|
+
/** The state or region's *name* as written, not a code. */
|
|
1396
|
+
state: string | null;
|
|
1397
|
+
postalCode: string | null;
|
|
1398
|
+
/** ISO 3166-1 alpha-2, uppercase. */
|
|
1399
|
+
country: string | null;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* What Connect read about a company, and where each part came from.
|
|
1403
|
+
*
|
|
1404
|
+
* Every value is a claim from a page named in `citations`. Nothing here is
|
|
1405
|
+
* recalled by a model: a field the pages did not state is `null`, which is why
|
|
1406
|
+
* `null` is common and should be rendered as "not found" rather than as an
|
|
1407
|
+
* empty input somebody has to notice.
|
|
1408
|
+
*/
|
|
1409
|
+
export interface EnrichedCompany {
|
|
1410
|
+
/** Normalised - lowercase, no scheme, no `www.`. */
|
|
1411
|
+
domain: string;
|
|
1412
|
+
name: string | null;
|
|
1413
|
+
/**
|
|
1414
|
+
* The registered entity name.
|
|
1415
|
+
*
|
|
1416
|
+
* Always `null` today. It is reserved for a statutory register, and
|
|
1417
|
+
* deliberately never filled from a website - a footer says "Acme" where the
|
|
1418
|
+
* register says "Acme Industries Private Limited", and this is the one field
|
|
1419
|
+
* here that would end up on a contract.
|
|
1420
|
+
*/
|
|
1421
|
+
legalName: string | null;
|
|
1422
|
+
description: string | null;
|
|
1423
|
+
industry: string | null;
|
|
1424
|
+
sizeBand: CompanySizeBand | null;
|
|
1425
|
+
phone: string | null;
|
|
1426
|
+
email: string | null;
|
|
1427
|
+
website: string | null;
|
|
1428
|
+
address: EnrichedAddress | null;
|
|
1429
|
+
/**
|
|
1430
|
+
* Derived from how many fields the pages actually supported - not the
|
|
1431
|
+
* model's opinion of itself, which reflects the confidence of the prose it
|
|
1432
|
+
* read. Treat `low` as "offer this", `high` as "safe to pre-fill".
|
|
1433
|
+
*/
|
|
1434
|
+
confidence: "high" | "medium" | "low";
|
|
1435
|
+
citations: EnrichmentCitation[];
|
|
1436
|
+
/** ISO-8601. Age the profile against this. */
|
|
1437
|
+
retrievedAt: string;
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Why there is nothing to report. A normal outcome, not an error - most
|
|
1441
|
+
* records legitimately have nothing to enrich.
|
|
1442
|
+
*
|
|
1443
|
+
* `consumer_domain` and `blocked_domain` will never succeed on retry.
|
|
1444
|
+
* `unreachable` and `no_signal` may, later.
|
|
1445
|
+
*/
|
|
1446
|
+
export type EnrichmentSkipReason = "no_domain" | "consumer_domain" | "blocked_domain" | "unreachable" | "no_signal";
|
|
1447
|
+
export type CompanyEnrichmentResult = {
|
|
1448
|
+
status: "enriched";
|
|
1449
|
+
profile: EnrichedCompany;
|
|
1450
|
+
/** Whether Connect served this from its platform-wide cache. The profile is the same either way. */
|
|
1451
|
+
cached: boolean;
|
|
1452
|
+
} | {
|
|
1453
|
+
status: "skipped";
|
|
1454
|
+
reason: EnrichmentSkipReason;
|
|
1455
|
+
};
|
|
1285
1456
|
/**
|
|
1286
1457
|
* What kind of conversation a channel carries. `voice` is the only kind
|
|
1287
1458
|
* Connect stocks inventory for today - `whatsapp` and `meeting` are modelled
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|