@cosmoops/connect-sdk 0.6.0 → 0.7.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/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +3 -3
- package/dist/cjs/client/components/account-sessions.js +3 -3
- package/dist/cjs/client/components/app-shell.js +2 -2
- package/dist/cjs/client/components/apps-grid.js +3 -3
- package/dist/cjs/client/components/feature-gate.js +2 -2
- package/dist/cjs/client/components/integrations-modal.js +1 -1
- package/dist/cjs/client/components/logo.js +3 -3
- package/dist/cjs/client/components/plan-pricing.js +1 -1
- package/dist/cjs/client/components/plans.js +6 -6
- package/dist/cjs/client/components/readiness.js +3 -3
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/usage-meter.js +1 -1
- package/dist/cjs/client/components/user-menu.js +4 -4
- package/dist/cjs/client/index.js +1 -1
- package/dist/cjs/client/mark.js +1 -1
- package/dist/cjs/client/provider.js +7 -7
- package/dist/cjs/client/use-checkout.js +3 -3
- package/dist/cjs/errors.js +4 -4
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/next/index.js +2 -2
- package/dist/cjs/server/client.js +15 -15
- package/dist/cjs/server/handler.js +13 -13
- package/dist/cjs/server/http.js +1 -1
- package/dist/cjs/server/resources/access.js +3 -3
- package/dist/cjs/server/resources/ai.js +1 -1
- package/dist/cjs/server/resources/analytics.js +2 -2
- package/dist/cjs/server/resources/apps.js +4 -4
- package/dist/cjs/server/resources/billing.js +5 -5
- package/dist/cjs/server/resources/drive.js +4 -4
- package/dist/cjs/server/resources/entitlements.js +7 -7
- package/dist/cjs/server/resources/identity.js +4 -4
- package/dist/cjs/server/resources/integrations.js +13 -13
- package/dist/cjs/server/resources/logs.js +3 -3
- package/dist/cjs/server/resources/sessions.js +6 -6
- package/dist/cjs/server/webhooks.js +4 -4
- package/dist/cjs/types.js +1 -1
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +3 -3
- package/dist/esm/client/components/account-sessions.js +3 -3
- package/dist/esm/client/components/app-shell.js +2 -2
- package/dist/esm/client/components/apps-grid.js +3 -3
- package/dist/esm/client/components/feature-gate.js +2 -2
- package/dist/esm/client/components/integrations-modal.js +1 -1
- package/dist/esm/client/components/logo.js +3 -3
- package/dist/esm/client/components/plan-pricing.js +1 -1
- package/dist/esm/client/components/plans.js +6 -6
- package/dist/esm/client/components/readiness.js +3 -3
- package/dist/esm/client/components/subscribe-dialog.js +2 -2
- package/dist/esm/client/components/subscription.js +1 -1
- package/dist/esm/client/components/usage-meter.js +1 -1
- package/dist/esm/client/components/user-menu.js +4 -4
- package/dist/esm/client/index.js +1 -1
- package/dist/esm/client/mark.js +1 -1
- package/dist/esm/client/provider.js +7 -7
- package/dist/esm/client/use-checkout.js +3 -3
- package/dist/esm/errors.js +4 -4
- package/dist/esm/helpers.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/next/index.js +2 -2
- package/dist/esm/server/client.js +15 -15
- package/dist/esm/server/handler.js +13 -13
- package/dist/esm/server/http.js +1 -1
- package/dist/esm/server/resources/access.js +3 -3
- package/dist/esm/server/resources/ai.js +1 -1
- package/dist/esm/server/resources/analytics.js +2 -2
- package/dist/esm/server/resources/apps.js +4 -4
- package/dist/esm/server/resources/billing.js +5 -5
- package/dist/esm/server/resources/drive.js +4 -4
- package/dist/esm/server/resources/entitlements.js +7 -7
- package/dist/esm/server/resources/identity.js +4 -4
- package/dist/esm/server/resources/integrations.js +13 -13
- package/dist/esm/server/resources/logs.js +3 -3
- package/dist/esm/server/resources/sessions.js +6 -6
- package/dist/esm/server/webhooks.js +4 -4
- package/dist/esm/types.js +1 -1
- package/dist/types/client/components/access-gate.d.ts +1 -1
- package/dist/types/client/components/account-sessions.d.ts +1 -1
- package/dist/types/client/components/app-shell.d.ts +4 -4
- package/dist/types/client/components/apps-grid.d.ts +1 -1
- package/dist/types/client/components/feature-gate.d.ts +1 -1
- package/dist/types/client/components/integrations-modal.d.ts +1 -1
- package/dist/types/client/components/logo.d.ts +5 -5
- package/dist/types/client/components/plan-pricing.d.ts +2 -2
- package/dist/types/client/components/readiness.d.ts +2 -2
- package/dist/types/client/components/subscribe-dialog.d.ts +3 -3
- package/dist/types/client/components/usage-meter.d.ts +1 -1
- package/dist/types/client/components/user-menu.d.ts +2 -2
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/mark.d.ts +1 -1
- package/dist/types/client/provider.d.ts +9 -9
- package/dist/types/client/use-checkout.d.ts +3 -3
- package/dist/types/errors.d.ts +4 -4
- package/dist/types/helpers.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/next/index.d.ts +1 -1
- package/dist/types/server/client.d.ts +13 -13
- package/dist/types/server/handler.d.ts +2 -2
- package/dist/types/server/resources/access.d.ts +3 -3
- package/dist/types/server/resources/ai.d.ts +2 -2
- package/dist/types/server/resources/analytics.d.ts +2 -2
- package/dist/types/server/resources/apps.d.ts +4 -4
- package/dist/types/server/resources/billing.d.ts +4 -4
- package/dist/types/server/resources/drive.d.ts +4 -4
- package/dist/types/server/resources/entitlements.d.ts +4 -4
- package/dist/types/server/resources/identity.d.ts +2 -2
- package/dist/types/server/resources/integrations.d.ts +9 -9
- package/dist/types/server/resources/logs.d.ts +3 -3
- package/dist/types/server/resources/sessions.d.ts +5 -5
- package/dist/types/server/webhooks.d.ts +4 -4
- package/dist/types/types.d.ts +61 -61
- package/package.json +1 -1
|
@@ -41,7 +41,7 @@ function ordered(sessions) {
|
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* One row's revoke action, confirmed before it fires - revoking a session
|
|
44
|
-
* ends every partner-
|
|
44
|
+
* ends every partner-application sign-in from that same browser, not just this
|
|
45
45
|
* one. Closes on confirm, not on response - the same convention every
|
|
46
46
|
* destructive confirmation in this codebase follows - so the dialog itself
|
|
47
47
|
* needs no async/pending state of its own; the row simply disappears once
|
|
@@ -55,11 +55,11 @@ function ordered(sessions) {
|
|
|
55
55
|
*/
|
|
56
56
|
function RevokeRow({ session, onRevoke }) {
|
|
57
57
|
const label = deviceLabel(session);
|
|
58
|
-
return (_jsxs(AlertDialog, { children: [_jsx(Button, { isIconOnly: true, size: "sm", variant: "tertiary", className: "shrink-0 text-muted hover:text-danger", "aria-label": `Sign out ${label}`, children: _jsx(LogOut, { className: "size-4" }) }), _jsx(AlertDialog.Backdrop, { children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { className: "sm:max-w-100", children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger" }), _jsx(AlertDialog.Heading, { children: "Sign out this device?" })] }), _jsx(AlertDialog.Body, { children: _jsxs("p", { className: "text-sm text-muted", children: ["This ends every sign-in from ", label, ", on every
|
|
58
|
+
return (_jsxs(AlertDialog, { children: [_jsx(Button, { isIconOnly: true, size: "sm", variant: "tertiary", className: "shrink-0 text-muted hover:text-danger", "aria-label": `Sign out ${label}`, children: _jsx(LogOut, { className: "size-4" }) }), _jsx(AlertDialog.Backdrop, { children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { className: "sm:max-w-100", children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger" }), _jsx(AlertDialog.Heading, { children: "Sign out this device?" })] }), _jsx(AlertDialog.Body, { children: _jsxs("p", { className: "text-sm text-muted", children: ["This ends every sign-in from ", label, ", on every application, not just this one."] }) }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "secondary", className: "text-xs", children: "Cancel" }), _jsx(Button, { slot: "close", variant: "danger", className: "text-xs", onPress: onRevoke, children: "Sign out" })] })] }) }) })] }));
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Every device signed into this Connect account - not scoped to this
|
|
62
|
-
*
|
|
62
|
+
* application, see `ConnectUserSession`'s doc comment. Owns the one
|
|
63
63
|
* `useConnectSessions()` call for this modal; every revoke action here
|
|
64
64
|
* mutates through it and re-reads the list itself, since nothing else
|
|
65
65
|
* invalidates it automatically - see `mutateResource`'s doc comment.
|
|
@@ -64,12 +64,12 @@ function BrandLink({ name, icon, homeHref, collapsed, renderLink, }) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
|
-
* The application frame every CosmoOps
|
|
67
|
+
* The application frame every CosmoOps application platform runs inside: a
|
|
68
68
|
* collapsible sidebar, a top bar carrying the app launcher and the account
|
|
69
69
|
* menu, and your pages in the middle.
|
|
70
70
|
*
|
|
71
71
|
* This is the piece each partner was otherwise writing by hand, and writing
|
|
72
|
-
* slightly differently - which is how a family of
|
|
72
|
+
* slightly differently - which is how a family of applications stops looking like
|
|
73
73
|
* one product. Everything that should be identical across platforms (the
|
|
74
74
|
* lockup, the rail geometry, the active-route treatment, the launcher, the
|
|
75
75
|
* account menu, the access gate) lives here; everything that genuinely
|
|
@@ -19,7 +19,7 @@ function AppTile({ app }) {
|
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* The Google-style app launcher: a grid icon in your top bar that opens a
|
|
22
|
-
* modal of every Connect
|
|
22
|
+
* modal of every Connect application this workspace can actually open.
|
|
23
23
|
*
|
|
24
24
|
* Only launchable apps appear, so no tile is a dead end - a paused
|
|
25
25
|
* subscription still shows, badged, because a dunning cycle should not make a
|
|
@@ -36,9 +36,9 @@ export function ConnectAppsGrid({ className, label = "Switch app" }) {
|
|
|
36
36
|
if (sessionLoading || !session?.user)
|
|
37
37
|
return null;
|
|
38
38
|
const apps = data?.apps ?? [];
|
|
39
|
-
return (_jsx(MotionProvider, { children: _jsxs(Modal, { children: [_jsx(Button, { isIconOnly: true, variant: "primary", "aria-label": label, className: className, children: _jsx(LayoutGrid, { className: "size-5" }) }), _jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { children: _jsxs(Modal.Dialog, { className: "sm:max-w-md", children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Your apps" }) }), _jsxs(Modal.Body, { children: [isLoading && (_jsx("div", { className: "grid grid-cols-3 gap-2", children: Array.from({ length: 6 }, (_, index) => (_jsxs("div", { className: "flex flex-col items-center gap-2 p-3", children: [_jsx(Skeleton, { className: "size-12 rounded-xl" }), _jsx(Skeleton, { className: "h-3 w-14" })] }, index))) })), !isLoading && error && (_jsx("p", { className: "py-6 text-center text-sm text-danger", children: "Could not load your apps." })), !isLoading && !error && apps.length === 0 && (_jsx("p", { className: "py-6 text-center text-sm text-muted", children: "No other apps yet. Anything this workspace subscribes to shows up here." })), !isLoading && !error && apps.length > 0 && (_jsx(m.div, { className: "grid grid-cols-3 gap-2", variants: { show: { transition: { staggerChildren: 0.04 } } }, initial: "hidden", animate: "show", children: apps.map((app) => (_jsx(m.div, { variants: { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0, transition: ENTER } }, children: _jsx(AppTile, { app: app }) }, app.
|
|
39
|
+
return (_jsx(MotionProvider, { children: _jsxs(Modal, { children: [_jsx(Button, { isIconOnly: true, variant: "primary", "aria-label": label, className: className, children: _jsx(LayoutGrid, { className: "size-5" }) }), _jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { children: _jsxs(Modal.Dialog, { className: "sm:max-w-md", children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Your apps" }) }), _jsxs(Modal.Body, { children: [isLoading && (_jsx("div", { className: "grid grid-cols-3 gap-2", children: Array.from({ length: 6 }, (_, index) => (_jsxs("div", { className: "flex flex-col items-center gap-2 p-3", children: [_jsx(Skeleton, { className: "size-12 rounded-xl" }), _jsx(Skeleton, { className: "h-3 w-14" })] }, index))) })), !isLoading && error && (_jsx("p", { className: "py-6 text-center text-sm text-danger", children: "Could not load your apps." })), !isLoading && !error && apps.length === 0 && (_jsx("p", { className: "py-6 text-center text-sm text-muted", children: "No other apps yet. Anything this workspace subscribes to shows up here." })), !isLoading && !error && apps.length > 0 && (_jsx(m.div, { className: "grid grid-cols-3 gap-2", variants: { show: { transition: { staggerChildren: 0.04 } } }, initial: "hidden", animate: "show", children: apps.map((app) => (_jsx(m.div, { variants: { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0, transition: ENTER } }, children: _jsx(AppTile, { app: app }) }, app.applicationId))) }))] }), _jsx(Modal.Footer, { children: _jsx(ConnectBrowseAllLink, {}) })] }) }) })] }) }));
|
|
40
40
|
}
|
|
41
41
|
function ConnectBrowseAllLink() {
|
|
42
42
|
const { appUrl } = useConnect();
|
|
43
|
-
return (_jsxs(Button, { variant: "tertiary", className: "w-full", onPress: () => window.open(`${appUrl}/
|
|
43
|
+
return (_jsxs(Button, { variant: "tertiary", className: "w-full", onPress: () => window.open(`${appUrl}/applications`, "_blank", "noopener,noreferrer"), children: ["Browse all applications", _jsx(ExternalLink, { className: "size-3.5 text-muted" })] }));
|
|
44
44
|
}
|
|
@@ -38,7 +38,7 @@ export function useConnectFeature(feature) {
|
|
|
38
38
|
*
|
|
39
39
|
* Like every gate in this package, it is a **UX gate, not a security
|
|
40
40
|
* boundary**: the entitlement is read through your own origin and the browser
|
|
41
|
-
* can ignore it. Check `client.entitlements.hasFeature(
|
|
41
|
+
* can ignore it. Check `client.entitlements.hasFeature(workspaceId, feature)` on the
|
|
42
42
|
* server before doing the thing the feature unlocks.
|
|
43
43
|
*
|
|
44
44
|
* Renders `loading` - not `fallback` - while the answer is unknown, so a paying
|
|
@@ -53,5 +53,5 @@ export function ConnectFeatureGate({ feature, children, fallback, loading = null
|
|
|
53
53
|
return _jsx(_Fragment, { children: children });
|
|
54
54
|
if (fallback !== undefined)
|
|
55
55
|
return _jsx(_Fragment, { children: fallback });
|
|
56
|
-
return (_jsx(ConnectNoSubscriptionCard, { plansHref: plansHref, title: title ?? "Not included in your plan", description: description ?? "This part of the
|
|
56
|
+
return (_jsx(ConnectNoSubscriptionCard, { plansHref: plansHref, title: title ?? "Not included in your plan", description: description ?? "This part of the application is on a higher plan. Compare plans to see what unlocks it.", className: className }));
|
|
57
57
|
}
|
|
@@ -42,7 +42,7 @@ function ToolkitTile({ toolkit, isPending, onConnect, onDisconnect }) {
|
|
|
42
42
|
_jsx("img", { src: toolkit.logoUrl, alt: "", className: "size-full object-cover" })) : (_jsx(Puzzle, { className: "size-6 text-muted", "aria-hidden": "true" })), toolkit.status === "failed" && (_jsx("span", { className: "absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-danger text-danger-foreground", children: _jsx(AlertCircle, { className: "size-3", "aria-hidden": "true" }) }))] }), _jsx("span", { className: "line-clamp-2 text-center text-xs font-medium text-foreground", children: toolkit.name }), _jsx(Chip, { size: "sm", variant: "soft", color: STATUS_COLOR[toolkit.status], children: STATUS_LABEL[toolkit.status] }), _jsxs("div", { className: "flex w-full flex-col gap-1.5 pt-1", children: [CAN_CONNECT.has(toolkit.status) && (_jsx(Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Connect" })), toolkit.status === "active" && (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Reconnect" }), _jsxs(AlertDialog, { isOpen: confirmingDisconnect, onOpenChange: setConfirmingDisconnect, children: [_jsx(Button, { size: "sm", variant: "tertiary", className: "w-full text-xs text-danger", isDisabled: isPending, children: "Disconnect" }), _jsx(AlertDialog.Backdrop, { children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { className: "sm:max-w-100", children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger" }), _jsxs(AlertDialog.Heading, { children: ["Disconnect ", toolkit.name, "?"] })] }), _jsx(AlertDialog.Body, { children: _jsx("p", { className: "text-sm text-muted", children: "Every member loses this connection immediately - it can be reconnected later." }) }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "secondary", className: "text-xs", children: "Cancel" }), _jsx(Button, { slot: "close", variant: "danger", className: "text-xs", onPress: onDisconnect, children: "Disconnect" })] })] }) }) })] })] })), toolkit.status === "pending" && (_jsx(Button, { size: "sm", variant: "tertiary", className: "w-full text-xs text-danger", isDisabled: isPending, onPress: onDisconnect, children: "Cancel" }))] })] }));
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* A grid of every Composio-hosted toolkit this
|
|
45
|
+
* A grid of every Composio-hosted toolkit this workspace can connect -
|
|
46
46
|
* Google Calendar, Slack, Twitter/X, whatever a platform admin enabled in
|
|
47
47
|
* Connect's own Console. Each tile carries its own connect/reconnect/
|
|
48
48
|
* disconnect action and its own pending state, independent of every other
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ConnectMark } from "../mark.js";
|
|
4
4
|
/**
|
|
5
|
-
* The shared platform lockup:
|
|
6
|
-
* `cosmo·ops` beside it, and the
|
|
5
|
+
* The shared platform lockup: an application's own glyph on the CosmoOps badge,
|
|
6
|
+
* `cosmo·ops` beside it, and the application name underneath.
|
|
7
7
|
*
|
|
8
|
-
* This is what makes a family of
|
|
8
|
+
* This is what makes a family of application platforms look like one product. Each
|
|
9
9
|
* platform passes only what differs - its `name` and its `icon` - and inherits
|
|
10
10
|
* the mark geometry, the wordmark treatment, the separator, and the accent from
|
|
11
11
|
* one place. Change the lockup here and every platform follows.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* an upgrade while the card behind it said downgrade would be worse than
|
|
5
5
|
* either label being wrong on its own.
|
|
6
6
|
*
|
|
7
|
-
* Mirrors `packages/data/src/
|
|
7
|
+
* Mirrors `packages/data/src/applications/catalog.ts` and
|
|
8
8
|
* `packages/data/src/payments/plan-change.ts`, which are the copies Connect's
|
|
9
9
|
* own UI uses and which decide which use-case actually runs. The duplication
|
|
10
10
|
* is deliberate and load-bearing: this package carries no dependency on
|
|
@@ -44,7 +44,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
44
44
|
/** The plan under review in the confirmation dialog, or `null` when it is closed. */
|
|
45
45
|
const [pending, setPending] = useState(null);
|
|
46
46
|
const subscription = entitlements?.subscription ?? null;
|
|
47
|
-
const
|
|
47
|
+
const applicationId = subscription?.applicationId ?? null;
|
|
48
48
|
const allPlans = useMemo(() => {
|
|
49
49
|
const plans = catalog?.plans ?? [];
|
|
50
50
|
if (!planIds)
|
|
@@ -82,12 +82,12 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
82
82
|
* then would misreport a completed checkout as an abandoned one.
|
|
83
83
|
*/
|
|
84
84
|
function startCheckout(plan) {
|
|
85
|
-
if (!
|
|
85
|
+
if (!applicationId)
|
|
86
86
|
return;
|
|
87
87
|
checkout.start({
|
|
88
88
|
url: buildCheckoutUrl({
|
|
89
89
|
appUrl,
|
|
90
|
-
|
|
90
|
+
applicationId,
|
|
91
91
|
planId: plan.id,
|
|
92
92
|
subscriptionId: subscription?.subscription?.id ?? null,
|
|
93
93
|
coupon,
|
|
@@ -116,7 +116,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
116
116
|
return null;
|
|
117
117
|
const action = resolveAction(plan, subscription);
|
|
118
118
|
// "Current" belongs to the tier, not just the exact price point: an
|
|
119
|
-
//
|
|
119
|
+
// workspace on Growth annual looking at monthly pricing is still on the
|
|
120
120
|
// Growth tier, and marking only the row it literally subscribes to
|
|
121
121
|
// would leave its own card unmarked half the time.
|
|
122
122
|
const isCurrentTier = subscription?.plan != null &&
|
|
@@ -134,8 +134,8 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
134
134
|
? "primary"
|
|
135
135
|
: "secondary",
|
|
136
136
|
// Disabled while a hand-off is in flight, not just on the
|
|
137
|
-
// card that started it: the
|
|
138
|
-
//
|
|
137
|
+
// card that started it: the workspace has one subscription per
|
|
138
|
+
// application, so a second checkout opened against a different
|
|
139
139
|
// plan would race the first and one of them would be
|
|
140
140
|
// rejected on arrival.
|
|
141
141
|
isDisabled: isCurrent || checkout.status === "waiting", onPress: () => setPending({ plan, action }), children: [ACTION_LABEL[action], !isCurrent && _jsx(ExternalLink, { className: "size-3.5" })] }) })] }) }, tier.planGroup));
|
|
@@ -29,7 +29,7 @@ function actionLabel(blocker) {
|
|
|
29
29
|
return ACTION_LABELS[blocker.code] ?? "Resolve on Connect";
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Every gate between this workspace and your
|
|
32
|
+
* Every gate between this workspace and your application, with the way out of each.
|
|
33
33
|
*
|
|
34
34
|
* This is the component that keeps a blocked customer from staring at a broken
|
|
35
35
|
* app. `getReadiness` resolves subscription state, KYC/KYB, a chargeable
|
|
@@ -37,7 +37,7 @@ function actionLabel(blocker) {
|
|
|
37
37
|
* `resolveUrl` into the exact Connect screen that fixes it** - so being blocked
|
|
38
38
|
* comes with somewhere to go rather than a refusal.
|
|
39
39
|
*
|
|
40
|
-
* Render it high on any page your
|
|
40
|
+
* Render it high on any page your application gates. It draws nothing at all when
|
|
41
41
|
* the workspace is ready, so it is safe to leave mounted permanently.
|
|
42
42
|
*
|
|
43
43
|
* Two deliberate refusals:
|
|
@@ -72,7 +72,7 @@ function BlockerRow({ blocker }) {
|
|
|
72
72
|
return (_jsxs("div", { className: `flex flex-wrap items-center justify-between gap-3 rounded-lg px-3 py-2 text-sm ${inProgress || !isBlocking ? "bg-surface text-muted" : "bg-danger-soft text-danger"}`, children: [_jsxs("span", { className: "flex items-center gap-2", children: [inProgress || !isBlocking ? (_jsx(Info, { className: "size-4 shrink-0", "aria-hidden": "true" })) : (_jsx(AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" })), blocker.message] }), blocker.resolveUrl && !inProgress && (_jsxs(Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: [actionLabel(blocker), _jsx(ExternalLink, { className: "size-3.5" })] }))] }));
|
|
73
73
|
}
|
|
74
74
|
function ReadyRow({ className }) {
|
|
75
|
-
return (_jsx("p", { className: `text-sm text-muted ${className ?? ""}`, children: "This workspace is cleared to use every part of the
|
|
75
|
+
return (_jsx("p", { className: `text-sm text-muted ${className ?? ""}`, children: "This workspace is cleared to use every part of the application." }));
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
78
|
* Renders `children` only once the workspace clears every blocking gate.
|
|
@@ -34,7 +34,7 @@ const HEADING = {
|
|
|
34
34
|
* The authoritative price is computed by Connect against the real cart:
|
|
35
35
|
* proration is priced against the actual cycle, a coupon is validated
|
|
36
36
|
* against the plan it is being applied to, trial eligibility depends on
|
|
37
|
-
* whether this
|
|
37
|
+
* whether this workspace has ever subscribed to this application before, and
|
|
38
38
|
* an accumulated credit is deducted. None of that is knowable from the
|
|
39
39
|
* partner's origin, so this component states the list price and says where
|
|
40
40
|
* the final figure comes from rather than quietly guessing at one.
|
|
@@ -47,7 +47,7 @@ export function ConnectSubscribeDialog({ plan, action, current, coupon, onClose,
|
|
|
47
47
|
// member and the one most in need of it: the two prices differ by an order
|
|
48
48
|
// of magnitude, and showing only the new one reads as a price rise.
|
|
49
49
|
const isChange = action === "upgrade" || action === "downgrade" || isIntervalSwitch;
|
|
50
|
-
return (_jsx(Modal.Backdrop, { isOpen: plan !== null, onOpenChange: (open) => !open && onClose(), children: _jsx(Modal.Container, { size: "sm", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, { onPress: onClose }), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: HEADING[action] }) }), plan && (_jsxs(_Fragment, { children: [_jsxs(Modal.Body, { className: "flex flex-col gap-4", children: [isChange && currentPlan ? (_jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-muted", children: currentPlan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(currentPlan.price), "/", currentPlan.billingInterval === "annual" ? "yr" : "mo"] })] }), _jsx(ArrowRight, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate font-medium text-foreground", children: plan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(plan.price), "/", plan.billingInterval === "annual" ? "yr" : "mo"] })] })] })) : (_jsxs("div", { children: [_jsx("p", { className: "font-medium text-foreground", children: plan.name }), plan.description && _jsx("p", { className: "text-xs text-muted", children: plan.description })] })), _jsxs("div", { className: "flex flex-col gap-1 border-t border-separator pt-3 text-sm", children: [_jsxs("div", { className: "flex justify-between text-muted", children: [_jsx("span", { children: plan.name }), _jsx("span", { children: formatPrice(plan.price) })] }), _jsxs("div", { className: "flex justify-between text-muted", children: [_jsxs("span", { children: ["GST (", plan.gstRate, "%)"] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate) - plan.price) })] }), _jsxs("div", { className: "flex justify-between border-t border-separator pt-1 font-semibold text-foreground", children: [_jsxs("span", { children: ["Per ", cadenceOf(plan)] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate)) })] }), plan.billingInterval === "annual" && (_jsxs("p", { className: "text-xs text-muted", children: ["Works out to ", formatPrice(monthlyOf(withGst(plan.price, plan.gstRate), "annual")), " a month, billed yearly."] }))] }), plan.trialDays ? (_jsxs("p", { className: "text-xs text-accent", children: [plan.trialDays, "-day free trial, if this workspace has not subscribed to this
|
|
50
|
+
return (_jsx(Modal.Backdrop, { isOpen: plan !== null, onOpenChange: (open) => !open && onClose(), children: _jsx(Modal.Container, { size: "sm", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, { onPress: onClose }), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: HEADING[action] }) }), plan && (_jsxs(_Fragment, { children: [_jsxs(Modal.Body, { className: "flex flex-col gap-4", children: [isChange && currentPlan ? (_jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-muted", children: currentPlan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(currentPlan.price), "/", currentPlan.billingInterval === "annual" ? "yr" : "mo"] })] }), _jsx(ArrowRight, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate font-medium text-foreground", children: plan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(plan.price), "/", plan.billingInterval === "annual" ? "yr" : "mo"] })] })] })) : (_jsxs("div", { children: [_jsx("p", { className: "font-medium text-foreground", children: plan.name }), plan.description && _jsx("p", { className: "text-xs text-muted", children: plan.description })] })), _jsxs("div", { className: "flex flex-col gap-1 border-t border-separator pt-3 text-sm", children: [_jsxs("div", { className: "flex justify-between text-muted", children: [_jsx("span", { children: plan.name }), _jsx("span", { children: formatPrice(plan.price) })] }), _jsxs("div", { className: "flex justify-between text-muted", children: [_jsxs("span", { children: ["GST (", plan.gstRate, "%)"] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate) - plan.price) })] }), _jsxs("div", { className: "flex justify-between border-t border-separator pt-1 font-semibold text-foreground", children: [_jsxs("span", { children: ["Per ", cadenceOf(plan)] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate)) })] }), plan.billingInterval === "annual" && (_jsxs("p", { className: "text-xs text-muted", children: ["Works out to ", formatPrice(monthlyOf(withGst(plan.price, plan.gstRate), "annual")), " a month, billed yearly."] }))] }), plan.trialDays ? (_jsxs("p", { className: "text-xs text-accent", children: [plan.trialDays, "-day free trial, if this workspace has not subscribed to this application before."] })) : null, coupon?.trim() ? (_jsxs("p", { className: "text-xs text-muted", children: ["Coupon ", _jsx("span", { className: "font-mono text-foreground", children: coupon.trim() }), " is carried over and validated on Connect."] })) : null, _jsxs("div", { className: "flex gap-2 rounded-lg bg-surface p-3 text-xs text-muted", children: [_jsx(Info, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": "true" }), _jsxs("div", { className: "flex flex-col gap-1", children: [action === "subscribe" && (_jsx("p", { children: "Payment is completed on Connect, where your card or UPI mandate is set up and the tax invoice is issued. This page updates by itself once it is done." })), action === "upgrade" && (_jsxs("p", { children: ["Connect prices the difference for the days left in your current cycle and collects just that amount now. From the next cycle you are billed the ", plan.name, " rate."] })), action === "downgrade" && (_jsx("p", { children: "You keep what you have paid for. Connect works out the unused part of this cycle and applies it as credit or a refund, depending on how this application is configured." })), isIntervalSwitch && (
|
|
51
51
|
// Not a proration. The cycle you are on ends, so this is
|
|
52
52
|
// the one action where the amount collected can exceed a
|
|
53
53
|
// single cycle's list price - or be nothing at all, with
|
|
@@ -55,7 +55,7 @@ function useSubscriptionState() {
|
|
|
55
55
|
* Rendered on its own it is just a card; `ConnectSubscriptionGate` is the usual
|
|
56
56
|
* way in, since it picks this only when there is genuinely nothing to show.
|
|
57
57
|
*/
|
|
58
|
-
export function ConnectNoSubscriptionCard({ plansHref = DEFAULT_PLANS_HREF, title = "You're not on a plan yet", description = "Pick a plan to unlock the full
|
|
58
|
+
export function ConnectNoSubscriptionCard({ plansHref = DEFAULT_PLANS_HREF, title = "You're not on a plan yet", description = "Pick a plan to unlock the full application. You can change or cancel it whenever you like.", className, }) {
|
|
59
59
|
return (_jsxs(Card, { className: className, children: [_jsxs(Card.Header, { children: [_jsx(Card.Title, { children: title }), _jsx(Card.Description, { children: description })] }), _jsx(Card.Footer, { children: _jsxs(ConnectLinkButton, { href: plansHref, variant: "primary", children: ["See plans", _jsx(ArrowRight, { className: "size-3.5" })] }) })] }));
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
@@ -16,7 +16,7 @@ function formatDate(iso) {
|
|
|
16
16
|
*
|
|
17
17
|
* Reads the balance Connect actually debits, so the number here and the number
|
|
18
18
|
* `consumeAddon` enforces cannot disagree. Before the `addons:read` endpoint
|
|
19
|
-
* existed this component was impossible to write honestly:
|
|
19
|
+
* existed this component was impossible to write honestly: an application could
|
|
20
20
|
* spend an allowance down on every request and the only thing the API exposed
|
|
21
21
|
* was the list of add-on *ids* on the subscription.
|
|
22
22
|
*
|
|
@@ -97,7 +97,7 @@ function DestinationTile({ icon, label, url, }) {
|
|
|
97
97
|
* in place, which is why it sits alone in the footer rather than as a fourth
|
|
98
98
|
* item in the same list as three navigations.
|
|
99
99
|
*
|
|
100
|
-
* The session list is account-wide, not scoped to this
|
|
100
|
+
* The session list is account-wide, not scoped to this application - see
|
|
101
101
|
* `ConnectUserSession`'s doc comment in `types.ts`. Appearance only renders
|
|
102
102
|
* when both `theme` and `onThemeChange` are supplied.
|
|
103
103
|
*
|
|
@@ -122,13 +122,13 @@ export function ConnectUserMenu({ compact = false, onSignOut, className, theme,
|
|
|
122
122
|
if (!user)
|
|
123
123
|
return null;
|
|
124
124
|
const label = user.displayName ?? user.email ?? "Account";
|
|
125
|
-
const role = session?.
|
|
125
|
+
const role = session?.applicationRole?.role;
|
|
126
126
|
const planChip = subscriptionChip(session?.subscription ?? null);
|
|
127
127
|
return (_jsxs(Modal, { children: [_jsxs(Button, { variant: "tertiary", "aria-label": `Account: ${label}`,
|
|
128
128
|
// `pl-1` against the default right padding is optical, not sloppy: a
|
|
129
129
|
// circular avatar needs less inset than a text label to look evenly
|
|
130
130
|
// seated in the button.
|
|
131
|
-
className: `gap-2 pl-1 ${className ?? ""}`, children: [_jsx(UserAvatar, { user: user, size: "sm" }), !compact && _jsx("span", { className: "text-sm font-medium text-foreground", children: label })] }), _jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { size: "md", scroll: "inside", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Account" }) }), _jsxs(Modal.Body, { className: "flex flex-col gap-5", children: [_jsxs("div", { className: `flex items-start gap-3 rounded-xl border border-border ${PANEL_FILL} p-4`, children: [_jsx(UserAvatar, { user: user, size: "lg" }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && _jsx("p", { className: "truncate text-xs text-muted", children: user.email }), _jsx("p", { className: "truncate text-xs text-muted", children: user.
|
|
131
|
+
className: `gap-2 pl-1 ${className ?? ""}`, children: [_jsx(UserAvatar, { user: user, size: "sm" }), !compact && _jsx("span", { className: "text-sm font-medium text-foreground", children: label })] }), _jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { size: "md", scroll: "inside", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Account" }) }), _jsxs(Modal.Body, { className: "flex flex-col gap-5", children: [_jsxs("div", { className: `flex items-start gap-3 rounded-xl border border-border ${PANEL_FILL} p-4`, children: [_jsx(UserAvatar, { user: user, size: "lg" }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && _jsx("p", { className: "truncate text-xs text-muted", children: user.email }), _jsx("p", { className: "truncate text-xs text-muted", children: user.workspaceName }), (role || planChip || !user.emailVerified) && (_jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [role && (_jsx(Chip, { size: "sm", variant: "soft", color: "accent", children: humanizeRole(role) })), planChip, !user.emailVerified && (_jsx(Chip, { size: "sm", variant: "soft", color: "warning", children: "Email unverified" }))] }))] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(DestinationTile, { icon: _jsx(UserRound, { className: ICON, "aria-hidden": "true" }), label: "Profile", url: `${appUrl}/profile` }), _jsx(DestinationTile, { icon: _jsx(CreditCard, { className: ICON, "aria-hidden": "true" }), label: "Billing", url: `${appUrl}/billing` }), _jsx(DestinationTile, { icon: _jsx(ConnectMark, { className: ICON }), label: "Connect", url: appUrl })] }), theme && onThemeChange && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Appearance" }), _jsx(ToggleButtonGroup, { fullWidth: true, "aria-label": "Theme", selectionMode: "single", disallowEmptySelection: true, selectedKeys: [theme], onSelectionChange: (keys) => {
|
|
132
132
|
const [next] = [...keys];
|
|
133
133
|
if (typeof next === "string")
|
|
134
134
|
onThemeChange(next);
|
|
@@ -149,7 +149,7 @@ export function ConnectManageSubscriptionButton({ className }) {
|
|
|
149
149
|
* Invoices and payment history, on Connect.
|
|
150
150
|
*
|
|
151
151
|
* A deep link rather than a component that lists them, and that is not a
|
|
152
|
-
* placeholder: nothing in `/api/v1/
|
|
152
|
+
* placeholder: nothing in `/api/v1/application/*` exposes invoices, so an SDK-side
|
|
153
153
|
* list would have nothing to read. It is also the right answer regardless -
|
|
154
154
|
* a GST tax invoice is a legal document with a fixed format, downloadable and
|
|
155
155
|
* emailed from one place, and a partner-rendered copy of one would be a second
|
package/dist/esm/client/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Styling is HeroUI v3 semantic tokens only (`accent`, `muted`, `surface`,
|
|
10
10
|
* `separator`, ...), never a hardcoded colour - so these components inherit
|
|
11
|
-
* whatever theme the host
|
|
11
|
+
* whatever theme the host application platform already configured, and one theme
|
|
12
12
|
* change lands identically in all of them.
|
|
13
13
|
*/
|
|
14
14
|
export { ConnectProvider, useConnect, useConnectUser, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
|
package/dist/esm/client/mark.js
CHANGED
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
* The Connect mark - concentric rings on a solid accent badge.
|
|
5
5
|
*
|
|
6
6
|
* Painted from `--accent` / `--accent-foreground`, the same HeroUI tokens the
|
|
7
|
-
* host app themes, so the badge tracks whatever palette
|
|
7
|
+
* host app themes, so the badge tracks whatever palette an application platform has
|
|
8
8
|
* configured rather than carrying a hardcoded CosmoOps green into someone
|
|
9
9
|
* else's brand.
|
|
10
10
|
*/
|
|
@@ -122,7 +122,7 @@ export function ConnectProvider({ clientId = "", basePath = "/api/connect", redi
|
|
|
122
122
|
// error, so a Login button still renders rather than the UI hanging on
|
|
123
123
|
// skeletons forever.
|
|
124
124
|
setResult({
|
|
125
|
-
session: { user: null, subscription: null,
|
|
125
|
+
session: { user: null, subscription: null, applicationRole: null },
|
|
126
126
|
error: cause instanceof Error ? cause.message : "Could not reach Connect",
|
|
127
127
|
});
|
|
128
128
|
}
|
|
@@ -171,7 +171,7 @@ export function ConnectProvider({ clientId = "", basePath = "/api/connect", redi
|
|
|
171
171
|
credentials: "same-origin",
|
|
172
172
|
}).catch(() => { });
|
|
173
173
|
cache.current.clear();
|
|
174
|
-
setResult({ session: { user: null, subscription: null,
|
|
174
|
+
setResult({ session: { user: null, subscription: null, applicationRole: null }, error: null });
|
|
175
175
|
}, [basePath, fetchImpl]);
|
|
176
176
|
const value = useMemo(() => ({
|
|
177
177
|
clientId,
|
|
@@ -218,28 +218,28 @@ export function useConnectEntitlements() {
|
|
|
218
218
|
/**
|
|
219
219
|
* What is left of every consumable add-on on this workspace's subscription.
|
|
220
220
|
*
|
|
221
|
-
* The read half of `client.billing.consumeAddon` -
|
|
221
|
+
* The read half of `client.billing.consumeAddon` - an application that meters its
|
|
222
222
|
* own end users had no way to show them what remained until this existed.
|
|
223
223
|
* Empty `addons` means there is nothing to meter, which is not an error.
|
|
224
224
|
*/
|
|
225
225
|
export function useConnectAddonUsage() {
|
|
226
226
|
return useConnectResource("/addons");
|
|
227
227
|
}
|
|
228
|
-
/** This
|
|
228
|
+
/** This application's plan catalog. */
|
|
229
229
|
export function useConnectPlans() {
|
|
230
230
|
return useConnectResource("/plans");
|
|
231
231
|
}
|
|
232
|
-
/** The apps this
|
|
232
|
+
/** The apps this workspace can open. */
|
|
233
233
|
export function useConnectApps() {
|
|
234
234
|
return useConnectResource("/apps");
|
|
235
235
|
}
|
|
236
|
-
/** Enabled Composio-hosted toolkits and this
|
|
236
|
+
/** Enabled Composio-hosted toolkits and this workspace's connection status for each. */
|
|
237
237
|
export function useConnectIntegrations() {
|
|
238
238
|
return useConnectResource("/integrations");
|
|
239
239
|
}
|
|
240
240
|
/**
|
|
241
241
|
* This Connect account's own login sessions - account-wide, not scoped to
|
|
242
|
-
* this
|
|
242
|
+
* this application, see `SessionsListResponse`'s doc comment. The component
|
|
243
243
|
* that calls this is also the one that must own any revoke actions and call
|
|
244
244
|
* the returned `refetch()` afterward - see `mutateResource`'s doc comment
|
|
245
245
|
* for why nothing does that automatically.
|
|
@@ -152,15 +152,15 @@ export function buildCancelUrl({ appUrl, subscriptionId }) {
|
|
|
152
152
|
* The Connect URL that completes a subscribe or a plan change.
|
|
153
153
|
*
|
|
154
154
|
* Both land on a modal Connect renders over the relevant page (an intercepted
|
|
155
|
-
* parallel route - `app/@modal/(.)
|
|
155
|
+
* parallel route - `app/@modal/(.)applications/...` and
|
|
156
156
|
* `app/@modal/(.)billing/...`), so the customer arrives already looking at the
|
|
157
157
|
* confirmation for the plan they picked here, not at a catalogue they have to
|
|
158
158
|
* find it in again.
|
|
159
159
|
*/
|
|
160
|
-
export function buildCheckoutUrl({ appUrl,
|
|
160
|
+
export function buildCheckoutUrl({ appUrl, applicationId, planId, subscriptionId, coupon, }) {
|
|
161
161
|
const path = subscriptionId
|
|
162
162
|
? `/billing/subscriptions/${encodeURIComponent(subscriptionId)}/change-plan/${encodeURIComponent(planId)}`
|
|
163
|
-
: `/
|
|
163
|
+
: `/applications/${encodeURIComponent(applicationId)}/subscribe/${encodeURIComponent(planId)}`;
|
|
164
164
|
const url = new URL(path, appUrl);
|
|
165
165
|
if (coupon?.trim())
|
|
166
166
|
url.searchParams.set("coupon", coupon.trim());
|
package/dist/esm/errors.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Every failure the Connect
|
|
2
|
+
* Every failure the Connect application API can report, as one class.
|
|
3
3
|
*
|
|
4
4
|
* The API speaks two error dialects and this collapses them: the
|
|
5
|
-
* `/api/v1/
|
|
5
|
+
* `/api/v1/application/*` endpoints use `{ error: { code, message } }`, while the
|
|
6
6
|
* three `/sso/*` ones use OAuth2's `{ error, error_description }` (RFC 6749
|
|
7
7
|
* §5.2). Both land here with `code` populated, so integrating code branches on
|
|
8
8
|
* one field either way.
|
|
@@ -41,7 +41,7 @@ export class ConnectError extends Error {
|
|
|
41
41
|
* Whether the user's Connect sign-in is over and they need to go back
|
|
42
42
|
* through `identity.authorizeUrl()`. Collapses every session-side failure,
|
|
43
43
|
* matching how the API reports them - unknown, expired, revoked, or issued
|
|
44
|
-
* to a different
|
|
44
|
+
* to a different application all arrive as one `invalid_token`.
|
|
45
45
|
*/
|
|
46
46
|
get isSessionExpired() {
|
|
47
47
|
return this.code === "invalid_token";
|
|
@@ -55,7 +55,7 @@ export class ConnectError extends Error {
|
|
|
55
55
|
* guessing keys. These two are the exception: reaching them required
|
|
56
56
|
* presenting a key whose hash matched, so you already hold it, and what you
|
|
57
57
|
* need to know is that no retry and no scope change will fix this - the key
|
|
58
|
-
* has to be reissued from the
|
|
58
|
+
* has to be reissued from the application's Integration tab.
|
|
59
59
|
*
|
|
60
60
|
* Worth alerting on. It is the one failure that will not resolve itself and
|
|
61
61
|
* takes down every call your integration makes.
|
package/dist/esm/helpers.js
CHANGED
|
@@ -12,7 +12,7 @@ export function blockingBlockers(readiness) {
|
|
|
12
12
|
* Coerces either wire shape into `PlanFeature[]`.
|
|
13
13
|
*
|
|
14
14
|
* A bare key becomes `{ key, name: key, description: "" }` - the same
|
|
15
|
-
* fallback the server applies for a plan older than its
|
|
15
|
+
* fallback the server applies for a plan older than its application's feature
|
|
16
16
|
* catalog, so an un-upgraded Connect renders exactly what it always did
|
|
17
17
|
* rather than a column of blanks.
|
|
18
18
|
*
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@cosmoops/connect-sdk` - the official client for the CosmoOps Connect
|
|
3
|
-
* **
|
|
3
|
+
* **application-partner API** (`/api/v1/application/*`).
|
|
4
4
|
*
|
|
5
5
|
* This root entry is the isomorphic surface: wire types, the error class, and
|
|
6
6
|
* pure helpers. It pulls in neither Node-only code nor React, so it is safe to
|
package/dist/esm/next/index.js
CHANGED
|
@@ -38,7 +38,7 @@ import { createConnectHandler } from "../server/handler.js";
|
|
|
38
38
|
* | `CONNECT_SESSION_COOKIE` | no | session cookie name |
|
|
39
39
|
*
|
|
40
40
|
* Register `{your origin}/api/connect/callback` as a redirect URI in the
|
|
41
|
-
*
|
|
41
|
+
* application's Integration tab. That is the only step this cannot do for you.
|
|
42
42
|
*
|
|
43
43
|
* No `next.config.ts` changes needed either, including `transpilePackages`:
|
|
44
44
|
* `dist/` is already ES2022 with `"use client"` hoisted to the top of every
|
|
@@ -287,7 +287,7 @@ export async function getConnectSession(request, options = {}) {
|
|
|
287
287
|
* A thin wrapper, not a framework of its own - reach for `getConnectSession`
|
|
288
288
|
* plus your own check when "not signed in" isn't "redirect to sign-in"
|
|
289
289
|
* (an API route returning 401, say). Role and entitlement checks stay
|
|
290
|
-
* outside this function on purpose: `session.
|
|
290
|
+
* outside this function on purpose: `session.applicationRole.granted` and
|
|
291
291
|
* `connect.entitlements.*` are one line each on top of the session this
|
|
292
292
|
* already gives you, and where that line redirects (a plans page, a
|
|
293
293
|
* "forbidden" screen) is a decision only your app can make - see
|
|
@@ -11,7 +11,7 @@ import { AnalyticsResource } from "./resources/analytics.js";
|
|
|
11
11
|
import { OtpResource } from "./resources/otp.js";
|
|
12
12
|
import { SessionsResource } from "./resources/sessions.js";
|
|
13
13
|
export const DEFAULT_BASE_URL = "https://connect.cosmoops.com";
|
|
14
|
-
const
|
|
14
|
+
const APPLICATION_API_PREFIX = "/api/v1/application";
|
|
15
15
|
/**
|
|
16
16
|
* Refuses to build a client in a browser.
|
|
17
17
|
*
|
|
@@ -40,9 +40,9 @@ function assertNotBrowser() {
|
|
|
40
40
|
"in browser code instead; it talks to your own origin and holds no credential.");
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* The CosmoOps Connect
|
|
43
|
+
* The CosmoOps Connect application-partner API.
|
|
44
44
|
*
|
|
45
|
-
* Covers `/api/v1/
|
|
45
|
+
* Covers `/api/v1/application/*` and nothing else - the surface an application platform
|
|
46
46
|
* integrates against. Connect's internal API is not part of this package and
|
|
47
47
|
* never will be.
|
|
48
48
|
*
|
|
@@ -53,7 +53,7 @@ function assertNotBrowser() {
|
|
|
53
53
|
* });
|
|
54
54
|
*
|
|
55
55
|
* const session = await connect.identity.exchangeCode({ code, redirectUri });
|
|
56
|
-
* const { ready } = await connect.entitlements.getReadiness(session.user.
|
|
56
|
+
* const { ready } = await connect.entitlements.getReadiness(session.user.workspaceId);
|
|
57
57
|
* ```
|
|
58
58
|
*
|
|
59
59
|
* Every failure throws a `ConnectError` carrying a stable `code`. Nothing here
|
|
@@ -62,27 +62,27 @@ function assertNotBrowser() {
|
|
|
62
62
|
export class ConnectClient {
|
|
63
63
|
/** "Login with Connect" - the OAuth2 handshake and the session after it. */
|
|
64
64
|
identity;
|
|
65
|
-
/** What a signed-in member may do on your
|
|
65
|
+
/** What a signed-in member may do on your application. */
|
|
66
66
|
access;
|
|
67
|
-
/** The
|
|
67
|
+
/** The applications a workspace can open - for a launcher grid. */
|
|
68
68
|
apps;
|
|
69
|
-
/** Whether a workspace may use your
|
|
69
|
+
/** Whether a workspace may use your application, and on what plan. */
|
|
70
70
|
entitlements;
|
|
71
71
|
/** Your plan catalog, and metering usage against add-on balances. */
|
|
72
72
|
billing;
|
|
73
|
-
/** Files in
|
|
73
|
+
/** Files in a workspace's Drive. */
|
|
74
74
|
drive;
|
|
75
75
|
/** RAG chat and structured extraction. */
|
|
76
76
|
ai;
|
|
77
77
|
/** WhatsApp/SMS phone verification. */
|
|
78
78
|
otp;
|
|
79
|
-
/** Feature events into the Console's audit log, filterable to your
|
|
79
|
+
/** Feature events into the Console's audit log, filterable to your application. */
|
|
80
80
|
logs;
|
|
81
|
-
/** Emit product events and read back this
|
|
81
|
+
/** Emit product events and read back this application's aggregated numbers. */
|
|
82
82
|
analytics;
|
|
83
|
-
/** This Connect account's own login sessions - account-wide, not scoped to this
|
|
83
|
+
/** This Connect account's own login sessions - account-wide, not scoped to this application. Requires the `sessions:manage` scope. */
|
|
84
84
|
sessions;
|
|
85
|
-
/** Composio-hosted third-party connections (Google Calendar, Slack, ...)
|
|
85
|
+
/** Composio-hosted third-party connections (Google Calendar, Slack, ...) a workspace has connected. */
|
|
86
86
|
integrations;
|
|
87
87
|
constructor(options) {
|
|
88
88
|
assertNotBrowser();
|
|
@@ -91,7 +91,7 @@ export class ConnectClient {
|
|
|
91
91
|
}
|
|
92
92
|
const appBaseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
93
93
|
const transport = {
|
|
94
|
-
baseUrl: `${appBaseUrl}${
|
|
94
|
+
baseUrl: `${appBaseUrl}${APPLICATION_API_PREFIX}`,
|
|
95
95
|
apiKey: options.apiKey,
|
|
96
96
|
timeoutMs: options.timeoutMs ?? 20_000,
|
|
97
97
|
maxRetries: options.maxRetries ?? 2,
|
|
@@ -99,7 +99,7 @@ export class ConnectClient {
|
|
|
99
99
|
userAgent: `cosmoops-connect-sdk/${SDK_VERSION}`,
|
|
100
100
|
};
|
|
101
101
|
// `authorizeUrl` points at the app, not the API prefix - it is a browser
|
|
102
|
-
// redirect, not
|
|
102
|
+
// redirect, not an application call, so identity takes both.
|
|
103
103
|
this.identity = new IdentityResource(transport, options.clientId ?? null, appBaseUrl);
|
|
104
104
|
this.access = new AccessResource(transport);
|
|
105
105
|
this.apps = new AppsResource(transport);
|
|
@@ -115,4 +115,4 @@ export class ConnectClient {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
118
|
-
export const SDK_VERSION = "0.
|
|
118
|
+
export const SDK_VERSION = "0.7.0";
|