@cosmoops/connect-sdk 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +215 -6
  3. package/dist/cjs/cli/doctor.js +3 -3
  4. package/dist/cjs/cli/init.js +3 -3
  5. package/dist/cjs/client/components/access-gate.js +162 -0
  6. package/dist/cjs/client/components/account-sessions.js +21 -19
  7. package/dist/cjs/client/components/app-shell.js +157 -0
  8. package/dist/cjs/client/components/apps-grid.js +2 -1
  9. package/dist/cjs/client/components/integrations-modal.js +95 -0
  10. package/dist/cjs/client/components/motion.js +27 -0
  11. package/dist/cjs/client/components/nav.js +97 -0
  12. package/dist/cjs/client/components/plans.js +39 -37
  13. package/dist/cjs/client/components/readiness.js +3 -1
  14. package/dist/cjs/client/components/side-nav.js +58 -0
  15. package/dist/cjs/client/components/subscription.js +2 -1
  16. package/dist/cjs/client/components/surfaces.js +54 -0
  17. package/dist/cjs/client/components/usage-meter.js +3 -2
  18. package/dist/cjs/client/components/user-menu.js +30 -3
  19. package/dist/cjs/client/index.js +29 -1
  20. package/dist/cjs/client/provider.js +54 -2
  21. package/dist/cjs/client/use-active-path.js +77 -0
  22. package/dist/cjs/errors.js +1 -1
  23. package/dist/cjs/next/index.js +96 -15
  24. package/dist/cjs/server/client.js +10 -2
  25. package/dist/cjs/server/handler.js +89 -6
  26. package/dist/cjs/server/index.js +8 -1
  27. package/dist/cjs/server/resources/analytics.js +66 -0
  28. package/dist/cjs/server/resources/identity.js +1 -1
  29. package/dist/cjs/server/resources/integrations.js +66 -0
  30. package/dist/cjs/server/resources/sessions.js +1 -1
  31. package/dist/cjs/server/webhooks.js +127 -0
  32. package/dist/esm/cli/doctor.js +3 -3
  33. package/dist/esm/cli/init.js +3 -3
  34. package/dist/esm/client/components/access-gate.js +158 -0
  35. package/dist/esm/client/components/account-sessions.js +21 -19
  36. package/dist/esm/client/components/app-shell.js +154 -0
  37. package/dist/esm/client/components/apps-grid.js +2 -1
  38. package/dist/esm/client/components/integrations-modal.js +92 -0
  39. package/dist/esm/client/components/motion.js +21 -0
  40. package/dist/esm/client/components/nav.js +89 -0
  41. package/dist/esm/client/components/plans.js +39 -37
  42. package/dist/esm/client/components/readiness.js +3 -1
  43. package/dist/esm/client/components/side-nav.js +53 -0
  44. package/dist/esm/client/components/subscription.js +2 -1
  45. package/dist/esm/client/components/surfaces.js +51 -0
  46. package/dist/esm/client/components/usage-meter.js +3 -2
  47. package/dist/esm/client/components/user-menu.js +31 -4
  48. package/dist/esm/client/index.js +15 -1
  49. package/dist/esm/client/provider.js +53 -2
  50. package/dist/esm/client/use-active-path.js +74 -0
  51. package/dist/esm/errors.js +1 -1
  52. package/dist/esm/next/index.js +95 -15
  53. package/dist/esm/server/client.js +10 -2
  54. package/dist/esm/server/handler.js +89 -6
  55. package/dist/esm/server/index.js +5 -0
  56. package/dist/esm/server/resources/analytics.js +62 -0
  57. package/dist/esm/server/resources/identity.js +1 -1
  58. package/dist/esm/server/resources/integrations.js +62 -0
  59. package/dist/esm/server/resources/sessions.js +1 -1
  60. package/dist/esm/server/webhooks.js +123 -0
  61. package/dist/theme.css +28 -0
  62. package/dist/types/client/components/access-gate.d.ts +79 -0
  63. package/dist/types/client/components/app-shell.d.ts +115 -0
  64. package/dist/types/client/components/integrations-modal.d.ts +21 -0
  65. package/dist/types/client/components/motion.d.ts +22 -0
  66. package/dist/types/client/components/nav.d.ts +110 -0
  67. package/dist/types/client/components/side-nav.d.ts +31 -0
  68. package/dist/types/client/components/surfaces.d.ts +51 -0
  69. package/dist/types/client/index.d.ts +12 -1
  70. package/dist/types/client/provider.d.ts +38 -3
  71. package/dist/types/client/use-active-path.d.ts +8 -0
  72. package/dist/types/errors.d.ts +1 -1
  73. package/dist/types/next/index.d.ts +40 -0
  74. package/dist/types/server/client.d.ts +8 -2
  75. package/dist/types/server/index.d.ts +7 -0
  76. package/dist/types/server/resources/analytics.d.ts +31 -0
  77. package/dist/types/server/resources/identity.d.ts +12 -2
  78. package/dist/types/server/resources/integrations.d.ts +40 -0
  79. package/dist/types/server/webhooks.d.ts +67 -0
  80. package/dist/types/types.d.ts +182 -1
  81. package/package.json +10 -4
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The one elevation model every panel inside a Connect modal follows.
3
+ *
4
+ * This exists because "just use `bg-surface`" is wrong here, and wrong in a
5
+ * way that is invisible until you look at the tokens. `Modal.Dialog` paints
6
+ * itself `bg-overlay`, and in the shared theme (`theme.css`) `--overlay` and
7
+ * `--surface` sit at the *same lightness* in both schemes - 100% against 100%
8
+ * in light, 21% against 21% in dark. A `bg-surface` panel dropped on a modal
9
+ * therefore has no fill contrast at all; only its border separates it from
10
+ * the dialog behind it.
11
+ *
12
+ * That is how the account modal ended up looking half-finished: the identity
13
+ * card used `bg-surface-secondary` and read as a solid block, while the
14
+ * destination tiles and the session list used `bg-surface` and read as
15
+ * hairline outlines floating on nothing. Same nesting depth, two different
16
+ * physical materials, for no reason a user could infer.
17
+ *
18
+ * Three levels, and nesting one inside another always steps the fill:
19
+ *
20
+ * - **L0 - the dialog itself.** `bg-overlay`, owned by HeroUI. Not ours.
21
+ * - **L1 - a panel on the dialog.** `PANEL_FILL` + `border-border`. The
22
+ * identity card, each destination tile, the session list.
23
+ * - **L2 - a medallion inside a panel.** `bg-surface`, which is L0's value
24
+ * and therefore reads as *inset* against L1 rather than raised. Light:
25
+ * white on grey. Dark: near-black on charcoal. Both directions legible.
26
+ *
27
+ * The inversion at L2 is deliberate and is why this is written down. Reaching
28
+ * for a "lighter" fill one step down works in light mode and quietly fails in
29
+ * dark; stepping *back to the dialog's own value* is the trick that reads in
30
+ * both, because the contrast comes from the gap between L1 and L0 rather than
31
+ * from a direction of travel.
32
+ */
33
+ export declare const PANEL_FILL = "bg-surface-secondary";
34
+ /**
35
+ * The icon well shared by destination tiles and session rows: a 40px box
36
+ * holding a 20px glyph.
37
+ *
38
+ * Half the well, which is the point. Both surfaces used to run a `size-4`
39
+ * (16px) icon inside a `size-9` (36px) well - a 44% fill that reads as an
40
+ * icon someone forgot to finish, and the specific thing that made the
41
+ * Profile/Billing/Connect tiles look under-drawn. They also disagreed on
42
+ * shape (a circle in the tiles, a rounded square in the session rows) for
43
+ * what is the same component role, so the two surfaces never looked related.
44
+ *
45
+ * Pair with `ICON` on the glyph inside. Session rows override the well to
46
+ * `size-9` for density but keep the same `ICON`, so glyph size stays constant
47
+ * across the whole modal even where the well does not.
48
+ */
49
+ export declare const MEDALLION = "size-10";
50
+ /** The glyph inside a `MEDALLION`. Constant across every well in the modal - see `MEDALLION`. */
51
+ export declare const ICON = "size-5";
@@ -10,7 +10,7 @@
10
10
  * whatever theme the host service platform already configured, and one theme
11
11
  * change lands identically in all of them.
12
12
  */
13
- export { ConnectProvider, useConnect, useConnectUser, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectAddonUsage, useConnectSessions, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
13
+ export { ConnectProvider, useConnect, useConnectUser, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
14
14
  export type { ConnectProviderProps, ConnectContextValue, ConnectSession } from "./provider.js";
15
15
  export { blockingBlockers, normalizePlanFeatures } from "../helpers.js";
16
16
  export type { WirePlanFeature } from "../helpers.js";
@@ -25,8 +25,17 @@ export { ConnectUserMenu, ConnectManageSubscriptionButton, ConnectInvoicesButton
25
25
  export { ConnectAccountSessions } from "./components/account-sessions.js";
26
26
  export type { ConnectAccountSessionsProps } from "./components/account-sessions.js";
27
27
  export type { ConnectUserMenuProps } from "./components/user-menu.js";
28
+ export { ConnectAppShell } from "./components/app-shell.js";
29
+ export type { ConnectAppShellProps } from "./components/app-shell.js";
30
+ export { ConnectSideNav } from "./components/side-nav.js";
31
+ export type { ConnectSideNavProps } from "./components/side-nav.js";
32
+ export { useActivePath } from "./use-active-path.js";
33
+ export { normalizeNav, flattenNav, normalizePath, resolveActivePath, isNavItemActive, gateExemptPaths, } from "./components/nav.js";
34
+ export type { ConnectNav, ConnectNavItem, ConnectNavSection, ConnectNavLinkProps, ConnectLinkRenderer, } from "./components/nav.js";
28
35
  export { ConnectAppsGrid } from "./components/apps-grid.js";
29
36
  export type { ConnectAppsGridProps } from "./components/apps-grid.js";
37
+ export { ConnectIntegrationsModal } from "./components/integrations-modal.js";
38
+ export type { ConnectIntegrationsModalProps } from "./components/integrations-modal.js";
30
39
  export { ConnectPlans } from "./components/plans.js";
31
40
  export type { ConnectPlansProps } from "./components/plans.js";
32
41
  export { ConnectSubscribeDialog } from "./components/subscribe-dialog.js";
@@ -35,6 +44,8 @@ export { ConnectSubscriptionGate, ConnectNoSubscriptionCard, ConnectSubscription
35
44
  export type { ConnectSubscriptionGateProps, ConnectNoSubscriptionCardProps, ConnectSubscriptionCardProps, ConnectTrialBannerProps, ConnectSubscriptionState, } from "./components/subscription.js";
36
45
  export { ConnectReadinessBanner, ConnectReadinessGate } from "./components/readiness.js";
37
46
  export type { ConnectReadinessBannerProps, ConnectReadinessGateProps } from "./components/readiness.js";
47
+ export { ConnectAccessGate, useConnectAccessGate } from "./components/access-gate.js";
48
+ export type { ConnectAccessGateProps, ConnectAccessGateState, ConnectAccessGateResult, } from "./components/access-gate.js";
38
49
  export { ConnectFeatureGate, useConnectFeature } from "./components/feature-gate.js";
39
50
  export type { ConnectFeatureGateProps } from "./components/feature-gate.js";
40
51
  export { ConnectUsageMeter } from "./components/usage-meter.js";
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import type { AddonUsageResponse, ConnectUser, OrganizationApp, PlansResponse, ReadinessResponse, ServiceRole, SessionsListResponse, SessionSubscription, SubscriptionResponse } from "../types.js";
2
+ import type { AddonUsageResponse, ConnectUser, IntegrationsListResponse, OrganizationApp, PlansResponse, ReadinessResponse, ServiceRole, SessionsListResponse, SessionSubscription, SubscriptionResponse } from "../types.js";
3
3
  export declare const CONNECT_APP_URL = "https://connect.cosmoops.com";
4
4
  export interface ConnectSession {
5
5
  user: ConnectUser | null;
@@ -61,7 +61,7 @@ export interface ConnectContextValue {
61
61
  }
62
62
  export interface ConnectProviderProps {
63
63
  /**
64
- * This service's public `client_id`, from its Setup tab in Connect.
64
+ * This service's public `client_id`, from its Integration tab in Connect.
65
65
  *
66
66
  * Not needed for the default sign-in flow: `signIn()` with no `redirectUri`
67
67
  * bounces the browser to your own `/signin` route, which already knows
@@ -87,6 +87,27 @@ export interface ConnectProviderProps {
87
87
  * child effects.
88
88
  */
89
89
  fetch?: (input: string, init?: RequestInit) => Promise<Response>;
90
+ /**
91
+ * Sign in a returning visitor automatically, with no click.
92
+ *
93
+ * When the first `/session` read comes back signed out, the browser is sent
94
+ * on one round trip to Connect with OIDC `prompt=none`. If they still have
95
+ * a Connect session and have authorised this app before, they come straight
96
+ * back signed in and never see a screen. If not, they come straight back
97
+ * signed out, with no error and nothing rendered differently - your normal
98
+ * signed-out UI just appears a beat later.
99
+ *
100
+ * **Off by default, and it is a real trade, not just caution.** It costs a
101
+ * full-page redirect on the first load of every signed-out visit - a brief
102
+ * flash on a marketing page, invisible on an app shell - so it belongs on
103
+ * apps whose visitors are overwhelmingly already Connect users. It is
104
+ * attempted at most once per tab (a `sessionStorage` marker), so a visitor
105
+ * who genuinely has no session does not bounce on every navigation.
106
+ *
107
+ * Requires `createConnectHandler` mounted at `basePath`; the redirect goes
108
+ * to your own `/signin`, which mints the `state` server-side.
109
+ */
110
+ silentSignIn?: boolean;
90
111
  children: ReactNode;
91
112
  }
92
113
  /**
@@ -103,7 +124,7 @@ export interface ConnectProviderProps {
103
124
  * </ConnectProvider>
104
125
  * ```
105
126
  */
106
- export declare function ConnectProvider({ clientId, basePath, redirectUri, appUrl, fetch: fetchImpl, children, }: ConnectProviderProps): import("react").JSX.Element;
127
+ export declare function ConnectProvider({ clientId, basePath, redirectUri, appUrl, fetch: fetchImpl, silentSignIn, children, }: ConnectProviderProps): import("react").JSX.Element;
107
128
  export declare function useConnect(): ConnectContextValue;
108
129
  /** The signed-in user, or `null`. Sugar over `useConnect().session`. */
109
130
  export declare function useConnectUser(): ConnectUser | null;
@@ -174,6 +195,20 @@ export declare function useConnectApps(): {
174
195
  */
175
196
  refetch(): void;
176
197
  };
198
+ /** Enabled Composio-hosted toolkits and this organization's connection status for each. */
199
+ export declare function useConnectIntegrations(): {
200
+ data: IntegrationsListResponse | null;
201
+ isLoading: boolean;
202
+ error: string | null;
203
+ /**
204
+ * Forces a fresh read - the provider's cache clears the mutated path's
205
+ * entry on a successful `mutateResource` call, but nothing re-triggers
206
+ * this hook's own effect on its own (none of its dependencies change from
207
+ * a cache write nobody here observes). Call this right after a
208
+ * `mutateResource` you made against the same `path` completes.
209
+ */
210
+ refetch(): void;
211
+ };
177
212
  /**
178
213
  * This Connect account's own login sessions - account-wide, not scoped to
179
214
  * this service, see `SessionsListResponse`'s doc comment. The component
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The current path, from `activePath` when given and from the browser
3
+ * otherwise.
4
+ *
5
+ * Exported because a host building its own shell out of `ConnectSideNav`
6
+ * needs the same fallback the shell uses.
7
+ */
8
+ export declare function useActivePath(activePath?: string): string;
@@ -52,7 +52,7 @@ export declare class ConnectError extends Error {
52
52
  * guessing keys. These two are the exception: reaching them required
53
53
  * presenting a key whose hash matched, so you already hold it, and what you
54
54
  * need to know is that no retry and no scope change will fix this - the key
55
- * has to be reissued from the service's Setup tab.
55
+ * has to be reissued from the service's Integration tab.
56
56
  *
57
57
  * Worth alerting on. It is the one failure that will not resolve itself and
58
58
  * takes down every call your integration makes.
@@ -81,6 +81,9 @@ export { handle as GET, handle as POST };
81
81
  * if (!session) redirect("/api/connect/signin?next=/dashboard");
82
82
  * ```
83
83
  *
84
+ * `requireConnectSession()` below is that exact check in one call, for the
85
+ * common case of "redirect if signed out."
86
+ *
84
87
  * Pass a `Request` to use it somewhere `next/headers` does not exist - a
85
88
  * framework-agnostic handler, a test, a custom server. Both forms take the
86
89
  * same path afterwards; the argument is only how the cookie is found.
@@ -92,11 +95,48 @@ export { handle as GET, handle as POST };
92
95
  *
93
96
  * Returns `null` for every failure - an expired session and a missing one both
94
97
  * mean "not signed in", and a page guard should not need a try/catch.
98
+ *
99
+ * **Memoized per request for the plain, zero-argument call** - the shape
100
+ * every page, layout and Server Action actually uses - via React's `cache()`.
101
+ * Passing an explicit `request`, `client` or `session` bypasses the cache
102
+ * and always makes a live call: those are for tests and non-ambient
103
+ * callers, where a fresh read every time is the point, not an oversight.
95
104
  */
96
105
  export declare function getConnectSession(request?: Request, options?: {
97
106
  client?: ConnectClient;
98
107
  session?: ConnectSessionStore;
99
108
  }): Promise<UserResponse | null>;
109
+ /**
110
+ * `getConnectSession()`, minus the boilerplate of checking it and redirecting
111
+ * yourself:
112
+ *
113
+ * ```tsx
114
+ * // app/dashboard/page.tsx
115
+ * const session = await requireConnectSession();
116
+ * ```
117
+ *
118
+ * Redirects to `/signin` under the same `basePath` as `getConnectSession`'s
119
+ * default (`/api/connect`) when signed out - pass `signInPath` if you mounted
120
+ * the bridge somewhere else, and `next` to send the visitor back to where
121
+ * they were once they're signed in. Only sensible in a Server Component,
122
+ * Route Handler or Server Action, the same three places `redirect()` from
123
+ * `next/navigation` itself works.
124
+ *
125
+ * A thin wrapper, not a framework of its own - reach for `getConnectSession`
126
+ * plus your own check when "not signed in" isn't "redirect to sign-in"
127
+ * (an API route returning 401, say). Role and entitlement checks stay
128
+ * outside this function on purpose: `session.serviceRole.granted` and
129
+ * `connect.entitlements.*` are one line each on top of the session this
130
+ * already gives you, and where that line redirects (a plans page, a
131
+ * "forbidden" screen) is a decision only your app can make - see
132
+ * "Authentication & authorization" for the pattern.
133
+ */
134
+ export declare function requireConnectSession(request?: Request, options?: {
135
+ client?: ConnectClient;
136
+ session?: ConnectSessionStore;
137
+ signInPath?: string;
138
+ next?: string;
139
+ }): Promise<UserResponse>;
100
140
  export { createConnectHandler } from "../server/handler.js";
101
141
  export type { ConnectSessionStore } from "../server/handler.js";
102
142
  export { ConnectClient } from "../server/client.js";
@@ -6,13 +6,15 @@ import { BillingResource } from "./resources/billing.js";
6
6
  import { DriveResource } from "./resources/drive.js";
7
7
  import { EntitlementsResource } from "./resources/entitlements.js";
8
8
  import { IdentityResource } from "./resources/identity.js";
9
+ import { IntegrationsResource } from "./resources/integrations.js";
9
10
  import { LogsResource } from "./resources/logs.js";
11
+ import { AnalyticsResource } from "./resources/analytics.js";
10
12
  import { OtpResource } from "./resources/otp.js";
11
13
  import { SessionsResource } from "./resources/sessions.js";
12
14
  export declare const DEFAULT_BASE_URL = "https://connect.cosmoops.com";
13
15
  export interface ConnectClientOptions {
14
16
  /**
15
- * The API key plaintext from this service's Setup tab, shown once at
17
+ * The API key plaintext from this service's Integration tab, shown once at
16
18
  * creation.
17
19
  *
18
20
  * One credential does three jobs: it is the `Authorization: Bearer` value
@@ -75,9 +77,13 @@ export declare class ConnectClient {
75
77
  readonly otp: OtpResource;
76
78
  /** Feature events into the Console's audit log, filterable to your service. */
77
79
  readonly logs: LogsResource;
80
+ /** Emit product events and read back this service's aggregated numbers. */
81
+ readonly analytics: AnalyticsResource;
78
82
  /** This Connect account's own login sessions - account-wide, not scoped to this service. Requires the `sessions:manage` scope. */
79
83
  readonly sessions: SessionsResource;
84
+ /** Composio-hosted third-party connections (Google Calendar, Slack, ...) an organization has connected. */
85
+ readonly integrations: IntegrationsResource;
80
86
  constructor(options: ConnectClientOptions);
81
87
  }
82
88
  /** Kept in step with `package.json` by `sdk.test.ts`. */
83
- export declare const SDK_VERSION = "0.3.0";
89
+ export declare const SDK_VERSION = "0.6.0";
@@ -20,4 +20,11 @@ export type { BillingResource } from "./resources/billing.js";
20
20
  export type { AiResource } from "./resources/ai.js";
21
21
  export type { OtpResource } from "./resources/otp.js";
22
22
  export type { LogsResource } from "./resources/logs.js";
23
+ export type { AnalyticsResource } from "./resources/analytics.js";
23
24
  export type { SessionsResource } from "./resources/sessions.js";
25
+ export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./resources/integrations.js";
26
+ /**
27
+ * Webhook verification and a ready-made POST route. Server-only for the
28
+ * obvious reason: the signing secret must never reach a browser bundle.
29
+ */
30
+ export { createConnectWebhookHandler, verifyConnectWebhook, type ConnectWebhookHandlers, type ConnectWebhookHandlerOptions, } from "./webhooks.js";
@@ -0,0 +1,31 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { AnalyticsQuery, AnalyticsResponse, EmitEventInput, EmitEventsResponse } from "../../types.js";
3
+ /**
4
+ * Your service's own analytics: emit product events, and read back the daily
5
+ * numbers Connect aggregates from them.
6
+ *
7
+ * There is no way to name a service in either call. The scope is always the one
8
+ * your API key belongs to - that is the endpoint's security model, not an
9
+ * omission.
10
+ */
11
+ export declare class AnalyticsResource {
12
+ private readonly transport;
13
+ constructor(transport: TransportOptions);
14
+ /**
15
+ * Daily series for the requested metrics, up to 90 days.
16
+ *
17
+ * **Throws on failure**, unlike `emit` below. A read whose result you are
18
+ * about to render has to be able to say "I could not load"; silently
19
+ * returning an empty series would draw a chart of zeroes.
20
+ */
21
+ series(query: AnalyticsQuery): Promise<AnalyticsResponse>;
22
+ /**
23
+ * Never throws - the same judgement `LogsResource.write` makes, for the same
24
+ * reason: telemetry that takes down the code path it was observing is worse
25
+ * than a lost event. A failure is reported through `delivered`.
26
+ *
27
+ * Up to 50 events per call. Pass `sessionToken` on an event to attribute it
28
+ * to the signed-in user; without one it still lands, just unattributed.
29
+ */
30
+ emit(events: EmitEventInput[]): Promise<EmitEventsResponse>;
31
+ }
@@ -4,8 +4,18 @@ export interface AuthorizeUrlInput {
4
4
  redirectUri: string;
5
5
  /** Fresh and random per request; check it matches on the way back. `createState()` makes one. */
6
6
  state: string;
7
- /** `"create"` opens registration instead of sign-in, so one entry point serves both buttons. */
8
- prompt?: "create";
7
+ /**
8
+ * `"create"` opens registration instead of sign-in, so one entry point
9
+ * serves both buttons.
10
+ *
11
+ * `"none"` is OIDC silent authentication: Connect answers from the
12
+ * visitor's existing session without showing them anything, returning a
13
+ * code if they are signed in and have already authorised this client, and
14
+ * `error=login_required`/`consent_required`/`interaction_required` if not.
15
+ * Use it to sign in a returning visitor with no click - never to *start* a
16
+ * sign-in, since by definition it will never show them a login screen.
17
+ */
18
+ prompt?: "create" | "none";
9
19
  /** Required only if this service enforces PKCE. */
10
20
  codeChallenge?: string;
11
21
  }
@@ -0,0 +1,40 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationsListResponse } from "../../types.js";
3
+ export interface ExecuteIntegrationActionInput {
4
+ toolkitSlug: string;
5
+ actionSlug: string;
6
+ /** Passed through to Composio verbatim - its shape is defined by the toolkit's own action schema, which Connect does not model. */
7
+ params?: Record<string, unknown>;
8
+ }
9
+ /**
10
+ * Composio-hosted third-party connections (Google Calendar, Slack,
11
+ * Twitter/X, ...) an organization has connected. Connect proxies every call
12
+ * here - your service never sees or holds the underlying OAuth token, which
13
+ * stays with Composio.
14
+ */
15
+ export declare class IntegrationsResource {
16
+ private readonly transport;
17
+ constructor(transport: TransportOptions);
18
+ /** Enabled toolkits and this organization's connection status for each. */
19
+ list(organizationId: string): Promise<IntegrationsListResponse["toolkits"]>;
20
+ /**
21
+ * Starts a Composio-hosted connection for one toolkit. The returned
22
+ * `redirectUrl` is where the browser completes the provider's OAuth
23
+ * screen, then Composio bounces it back to `returnUrl`.
24
+ *
25
+ * **The connection is not active when this returns.** Treat the toolkit
26
+ * as `pending` until a later `list()` call reports `active` - only
27
+ * Connect's own webhook from Composio confirms that, never the redirect
28
+ * itself. Reconnecting a disconnected/failed toolkit calls this same
29
+ * method - there is no separate reconnect.
30
+ *
31
+ * Meant to be called on behalf of a signed-in org admin (this is what the
32
+ * bridge's fixed `POST /integrations/connect` route does), not as a bare
33
+ * background action with no admin present to complete the OAuth screen.
34
+ */
35
+ initiateConnect(organizationId: string, toolkitSlug: string, returnUrl: string): Promise<InitiateIntegrationConnectionResponse>;
36
+ /** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
37
+ disconnect(organizationId: string, toolkitSlug: string): Promise<DisconnectIntegrationResponse>;
38
+ /** Calls a Composio action against this organization's connected account for the named toolkit. */
39
+ execute(organizationId: string, input: ExecuteIntegrationActionInput): Promise<ExecuteIntegrationActionResponse>;
40
+ }
@@ -0,0 +1,67 @@
1
+ import type { ConnectWebhookEnvelope, ConnectWebhookEventMap, ConnectWebhookEventType } from "../types.js";
2
+ /**
3
+ * Verifies a `Connect-Signature` header against the raw request body.
4
+ *
5
+ * Exported on its own for anyone wiring this into a framework
6
+ * `createConnectWebhookHandler` does not cover. The **raw** body matters:
7
+ * verify before parsing, and never re-serialise - `JSON.stringify(await
8
+ * req.json())` produces a different string whenever key order or number
9
+ * formatting differs, and the signature will not match.
10
+ *
11
+ * Returns a boolean and never explains which check failed. Reporting "bad
12
+ * timestamp" separately from "bad signature" tells whoever is probing which
13
+ * half of a forgery to keep working on.
14
+ */
15
+ export declare function verifyConnectWebhook(rawBody: string, signatureHeader: string | null | undefined, secret: string, now?: Date): boolean;
16
+ /** One handler per event type. Every one is optional; unhandled types are acknowledged and ignored. */
17
+ export type ConnectWebhookHandlers = {
18
+ [K in ConnectWebhookEventType]?: (data: ConnectWebhookEventMap[K], envelope: ConnectWebhookEnvelope<K>) => void | Promise<void>;
19
+ };
20
+ export interface ConnectWebhookHandlerOptions {
21
+ /** The endpoint's signing secret, from Console. Keep it in an environment variable. */
22
+ secret: string;
23
+ on: ConnectWebhookHandlers;
24
+ /**
25
+ * Called when a handler throws. Return nothing to have the delivery
26
+ * retried (Connect responds to a 5xx by retrying for about 33 hours);
27
+ * the default logs and rethrows, which produces exactly that.
28
+ */
29
+ onError?: (error: unknown, envelope: ConnectWebhookEnvelope) => void | Promise<void>;
30
+ }
31
+ /**
32
+ * A ready-made `POST` route for Connect's webhooks.
33
+ *
34
+ * ```ts
35
+ * export const POST = createConnectWebhookHandler({
36
+ * secret: process.env.CONNECT_WEBHOOK_SECRET!,
37
+ * on: {
38
+ * "organization.deletion_scheduled": ({ organizationId, purgeAfter }) =>
39
+ * freeze(organizationId, purgeAfter),
40
+ * "organization.deletion_cancelled": ({ organizationId }) => unfreeze(organizationId),
41
+ * "organization.deleted": ({ organizationId }) => purge(organizationId),
42
+ * },
43
+ * })
44
+ * ```
45
+ *
46
+ * Three behaviours worth knowing, because each of them is a decision rather
47
+ * than an accident:
48
+ *
49
+ * **An unverified request gets a 401 and nothing else happens.** No parsing,
50
+ * no logging of the body, no handler. The endpoint is a public URL, so
51
+ * anything reachable before the signature check is reachable by anyone.
52
+ *
53
+ * **An unhandled event type is a 200.** Connect adds event types over time,
54
+ * and answering 4xx to one you have not written code for would dead-letter
55
+ * it immediately and, after enough of them, disable your endpoint. Silence
56
+ * is the correct answer to "we do not care about this one".
57
+ *
58
+ * **A throwing handler produces a 500, on purpose.** That is what makes
59
+ * Connect retry. Swallowing the error would acknowledge an event you failed
60
+ * to process, and the retry ladder is the only thing standing between a
61
+ * transient failure and a permanently missed deletion.
62
+ *
63
+ * Delivery is at-least-once and unordered. Dedupe on `envelope.id`, and
64
+ * reconcile against the readiness endpoint rather than trusting arrival
65
+ * order - the pull channel is authoritative, this one is fast.
66
+ */
67
+ export declare function createConnectWebhookHandler(options: ConnectWebhookHandlerOptions): (request: Request) => Promise<Response>;
@@ -16,6 +16,15 @@ export interface ConnectUser {
16
16
  email: string | null;
17
17
  emailVerified: boolean;
18
18
  displayName: string | null;
19
+ /**
20
+ * The member's profile picture, or `null` when they have not set one.
21
+ *
22
+ * Deliberately nullable rather than a generated placeholder: only you know
23
+ * what your fallback should look like. `<ConnectUserMenu>` renders initials
24
+ * via `<Avatar.Fallback>`, which also covers the URL loading slowly or
25
+ * failing outright - so pair the two rather than branching on `null`.
26
+ */
27
+ photoUrl: string | null;
19
28
  organizationId: string;
20
29
  organizationName: string;
21
30
  }
@@ -95,7 +104,7 @@ export interface RolesResponse {
95
104
  permissionCatalog: ServicePermission[];
96
105
  roles: ServiceRoleDefinition[];
97
106
  }
98
- export type ReadinessBlockerCode = "subscription_missing" | "subscription_inactive" | "audience_mismatch" | "verification_required" | "verification_pending" | "verification_rejected" | "verification_expired" | "payment_method_missing" | "seat_limit_reached" | "organization_not_found" | "onboarding_approval_pending" | "onboarding_approval_rejected";
107
+ export type ReadinessBlockerCode = "subscription_missing" | "subscription_inactive" | "audience_mismatch" | "verification_required" | "verification_pending" | "verification_rejected" | "verification_expired" | "payment_method_missing" | "seat_limit_reached" | "organization_not_found" | "onboarding_approval_pending" | "onboarding_approval_rejected" | "organization_suspended" | "payment_retry_pending";
99
108
  export interface ReadinessBlocker {
100
109
  /**
101
110
  * Branch on this. Typed as a union for the codes that exist today, widened
@@ -108,6 +117,8 @@ export interface ReadinessBlocker {
108
117
  message: string;
109
118
  /** Where in Connect the customer resolves this. `null` when nothing they do helps (e.g. a review in progress). */
110
119
  resolveUrl: string | null;
120
+ /** When this blocker resolves itself automatically - currently only set on `payment_retry_pending`, the deadline dunning suspends the subscription. `undefined`/`null` for every other code. */
121
+ graceEndsAt?: string | null;
111
122
  }
112
123
  export interface ReadinessResponse {
113
124
  organizationId: string;
@@ -432,3 +443,173 @@ export interface RevokeOtherSessionsResponse {
432
443
  }
433
444
  /** Host-controlled - the SDK has no theme provider of its own. See `ConnectUserMenuProps.theme`. */
434
445
  export type ThemeMode = "light" | "dark" | "system";
446
+ /**
447
+ * `not_connected` never travels over the wire as a persisted state - it's
448
+ * what `ListOrganizationIntegrationsUseCase` reports for a toolkit this
449
+ * organization has no `IntegrationConnection` record for at all.
450
+ */
451
+ export type IntegrationConnectionStatus = "not_connected" | "pending" | "active" | "failed" | "disconnected" | "expired";
452
+ /**
453
+ * A Composio-hosted third-party toolkit (Google Calendar, Slack, Twitter/X,
454
+ * ...) this organization can see - only ever one a platform admin enabled in
455
+ * Connect's own Console, joined with this org's own connection status.
456
+ * Connections are org-shared: one covers every member, the way a Slack app
457
+ * install is shared rather than per-user.
458
+ */
459
+ export interface IntegrationToolkit {
460
+ slug: string;
461
+ name: string;
462
+ description: string;
463
+ category: string | null;
464
+ logoUrl: string | null;
465
+ status: IntegrationConnectionStatus;
466
+ }
467
+ export interface IntegrationsListResponse {
468
+ toolkits: IntegrationToolkit[];
469
+ }
470
+ /** The connection isn't active yet when this returns - see `IntegrationsResource.initiateConnect`'s doc comment. */
471
+ export interface InitiateIntegrationConnectionResponse {
472
+ /** Send the browser here to complete the provider's OAuth screen. */
473
+ redirectUrl: string;
474
+ connectionId: string;
475
+ }
476
+ export interface DisconnectIntegrationResponse {
477
+ success: true;
478
+ }
479
+ export interface ExecuteIntegrationActionResponse {
480
+ data: unknown;
481
+ successful: boolean;
482
+ }
483
+ /**
484
+ * The metrics Connect can report for your service.
485
+ *
486
+ * Hand-written here rather than imported from Connect's own registry, like
487
+ * every other wire type in this file, so the published package carries none of
488
+ * Connect's internals. `conformance.test.ts` type-checks these against the
489
+ * server DTOs, so drift fails the build in Connect's repo rather than in a
490
+ * partner's integration.
491
+ */
492
+ export type ConnectMetric = "usage.events" | "usage.active_users" | "api.requests" | "api.errors" | "billing.mrr_paise" | "billing.active_subscriptions" | "metering.addon_consumed";
493
+ export interface AnalyticsPoint {
494
+ /** `YYYY-MM-DD`, UTC. */
495
+ day: string;
496
+ /**
497
+ * **`null` is not zero.** It means the metric was not measurable that day -
498
+ * `usage.active_users` is a distinct count that only exists after Connect's
499
+ * nightly rollup, so today's is always `null`, as is any future day. Render
500
+ * it as a gap or a dash, never as a zero.
501
+ */
502
+ value: number | null;
503
+ }
504
+ export interface AnalyticsSeries {
505
+ metric: ConnectMetric;
506
+ points: AnalyticsPoint[];
507
+ /** Sum across measurable days only, or `null` if no day in the range was measurable. */
508
+ total: number | null;
509
+ /** True when at least one day was not measurable - caveat the total when this is set. */
510
+ partial: boolean;
511
+ }
512
+ export interface AnalyticsResponse {
513
+ from: string;
514
+ to: string;
515
+ series: AnalyticsSeries[];
516
+ }
517
+ export interface AnalyticsQuery {
518
+ metrics: ConnectMetric[];
519
+ /** Inclusive `YYYY-MM-DD`, UTC. The range must be shorter than 90 days. */
520
+ from: string;
521
+ to: string;
522
+ }
523
+ /** One event you push into Connect's analytics pipeline. */
524
+ export interface EmitEventInput {
525
+ /**
526
+ * Dot-namespaced and lowercase, e.g. `checkout.completed`. Prefixes Connect
527
+ * emits itself - `api.`, `billing.`, `addon.`, `subscription.`, `connect.` -
528
+ * are rejected, so your events can never inflate a metered number.
529
+ */
530
+ name: string;
531
+ /** Defaults to now. Clamped server-side to within 48 hours. */
532
+ occurredAt?: string | Date;
533
+ /** Attributes the event to the signed-in user and their organization. */
534
+ sessionToken?: string;
535
+ /** A whole number in the smallest unit of `unit` - paise, bytes, milliseconds. */
536
+ value?: number;
537
+ unit?: string;
538
+ props?: Record<string, unknown>;
539
+ }
540
+ export interface EmitEventsResponse {
541
+ /** False when the call failed outright - see `AnalyticsResource.emit`. */
542
+ delivered: boolean;
543
+ accepted: number;
544
+ attributed: number;
545
+ }
546
+ /**
547
+ * Every event Connect will POST to your webhook endpoint.
548
+ *
549
+ * Additive only - Connect adds event types, never renames one - so a
550
+ * `switch` that does not handle a future type is safe, and
551
+ * `createConnectWebhookHandler` simply ignores anything you have not
552
+ * registered a handler for.
553
+ */
554
+ export type ConnectWebhookEventType = "organization.deletion_scheduled" | "organization.deletion_cancelled" | "organization.deleted" | "subscription.cancelled";
555
+ /**
556
+ * A workspace's deletion has been confirmed and the grace window is running.
557
+ *
558
+ * Access is *already* denied - readiness reports
559
+ * `organization_pending_deletion` from this moment - so treat this as "stop
560
+ * writing", not "start planning". `purgeAfter` is what makes this event worth
561
+ * having: the customer's data still exists until then, so an export or a
562
+ * wind-down can run on the same clock rather than after everything is gone.
563
+ */
564
+ export interface OrganizationDeletionScheduledEvent {
565
+ organizationId: string;
566
+ /** ISO-8601. When Connect destroys the workspace. */
567
+ purgeAfter: string;
568
+ requestedBy: "owner" | "platform_admin";
569
+ graceDays: number;
570
+ }
571
+ /**
572
+ * The deletion was withdrawn and the workspace is live again.
573
+ *
574
+ * **Handle this one.** It is the event integrations forget, and its absence
575
+ * is the worst failure of the set: a service that froze a workspace on
576
+ * `organization.deletion_scheduled` and never unfreezes leaves a paying
577
+ * customer locked out of something that has no idea anything changed.
578
+ */
579
+ export interface OrganizationDeletionCancelledEvent {
580
+ organizationId: string;
581
+ }
582
+ /** The grace window closed and Connect purged the workspace. Purge your own copy. */
583
+ export interface OrganizationDeletedEvent {
584
+ organizationId: string;
585
+ /** ISO-8601. */
586
+ purgedAt: string;
587
+ }
588
+ /** A subscription ended. Fires on its own, not only as part of a deletion. */
589
+ export interface SubscriptionCancelledEvent {
590
+ organizationId: string;
591
+ subscriptionId: string;
592
+ serviceId: string;
593
+ }
594
+ /** Maps each event type to the shape of its `data`. */
595
+ export interface ConnectWebhookEventMap {
596
+ "organization.deletion_scheduled": OrganizationDeletionScheduledEvent;
597
+ "organization.deletion_cancelled": OrganizationDeletionCancelledEvent;
598
+ "organization.deleted": OrganizationDeletedEvent;
599
+ "subscription.cancelled": SubscriptionCancelledEvent;
600
+ }
601
+ /** The envelope, as it arrives on the wire. */
602
+ export interface ConnectWebhookEnvelope<T extends ConnectWebhookEventType = ConnectWebhookEventType> {
603
+ /**
604
+ * Unique per delivery. **Dedupe on this.** Delivery is at-least-once and
605
+ * unordered: a partner that assumes exactly-once, or assumes order, will
606
+ * eventually be wrong.
607
+ */
608
+ id: string;
609
+ type: T;
610
+ /** ISO-8601. */
611
+ createdAt: string;
612
+ /** Bumped only for a breaking payload change - never for a new event type. */
613
+ apiVersion: string;
614
+ data: ConnectWebhookEventMap[T];
615
+ }