@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.
- package/CHANGELOG.md +125 -0
- package/README.md +215 -6
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +162 -0
- package/dist/cjs/client/components/account-sessions.js +21 -19
- package/dist/cjs/client/components/app-shell.js +157 -0
- package/dist/cjs/client/components/apps-grid.js +2 -1
- package/dist/cjs/client/components/integrations-modal.js +95 -0
- package/dist/cjs/client/components/motion.js +27 -0
- package/dist/cjs/client/components/nav.js +97 -0
- package/dist/cjs/client/components/plans.js +39 -37
- package/dist/cjs/client/components/readiness.js +3 -1
- package/dist/cjs/client/components/side-nav.js +58 -0
- package/dist/cjs/client/components/subscription.js +2 -1
- package/dist/cjs/client/components/surfaces.js +54 -0
- package/dist/cjs/client/components/usage-meter.js +3 -2
- package/dist/cjs/client/components/user-menu.js +30 -3
- package/dist/cjs/client/index.js +29 -1
- package/dist/cjs/client/provider.js +54 -2
- package/dist/cjs/client/use-active-path.js +77 -0
- package/dist/cjs/errors.js +1 -1
- package/dist/cjs/next/index.js +96 -15
- package/dist/cjs/server/client.js +10 -2
- package/dist/cjs/server/handler.js +89 -6
- package/dist/cjs/server/index.js +8 -1
- package/dist/cjs/server/resources/analytics.js +66 -0
- package/dist/cjs/server/resources/identity.js +1 -1
- package/dist/cjs/server/resources/integrations.js +66 -0
- package/dist/cjs/server/resources/sessions.js +1 -1
- package/dist/cjs/server/webhooks.js +127 -0
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +158 -0
- package/dist/esm/client/components/account-sessions.js +21 -19
- package/dist/esm/client/components/app-shell.js +154 -0
- package/dist/esm/client/components/apps-grid.js +2 -1
- package/dist/esm/client/components/integrations-modal.js +92 -0
- package/dist/esm/client/components/motion.js +21 -0
- package/dist/esm/client/components/nav.js +89 -0
- package/dist/esm/client/components/plans.js +39 -37
- package/dist/esm/client/components/readiness.js +3 -1
- package/dist/esm/client/components/side-nav.js +53 -0
- package/dist/esm/client/components/subscription.js +2 -1
- package/dist/esm/client/components/surfaces.js +51 -0
- package/dist/esm/client/components/usage-meter.js +3 -2
- package/dist/esm/client/components/user-menu.js +31 -4
- package/dist/esm/client/index.js +15 -1
- package/dist/esm/client/provider.js +53 -2
- package/dist/esm/client/use-active-path.js +74 -0
- package/dist/esm/errors.js +1 -1
- package/dist/esm/next/index.js +95 -15
- package/dist/esm/server/client.js +10 -2
- package/dist/esm/server/handler.js +89 -6
- package/dist/esm/server/index.js +5 -0
- package/dist/esm/server/resources/analytics.js +62 -0
- package/dist/esm/server/resources/identity.js +1 -1
- package/dist/esm/server/resources/integrations.js +62 -0
- package/dist/esm/server/resources/sessions.js +1 -1
- package/dist/esm/server/webhooks.js +123 -0
- package/dist/theme.css +28 -0
- package/dist/types/client/components/access-gate.d.ts +79 -0
- package/dist/types/client/components/app-shell.d.ts +115 -0
- package/dist/types/client/components/integrations-modal.d.ts +21 -0
- package/dist/types/client/components/motion.d.ts +22 -0
- package/dist/types/client/components/nav.d.ts +110 -0
- package/dist/types/client/components/side-nav.d.ts +31 -0
- package/dist/types/client/components/surfaces.d.ts +51 -0
- package/dist/types/client/index.d.ts +12 -1
- package/dist/types/client/provider.d.ts +38 -3
- package/dist/types/client/use-active-path.d.ts +8 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/next/index.d.ts +40 -0
- package/dist/types/server/client.d.ts +8 -2
- package/dist/types/server/index.d.ts +7 -0
- package/dist/types/server/resources/analytics.d.ts +31 -0
- package/dist/types/server/resources/identity.d.ts +12 -2
- package/dist/types/server/resources/integrations.d.ts +40 -0
- package/dist/types/server/webhooks.d.ts +67 -0
- package/dist/types/types.d.ts +182 -1
- 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
|
|
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;
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
-
/**
|
|
8
|
-
|
|
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>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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
|
+
}
|