@cosmoops/connect-sdk 0.1.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 (126) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +446 -0
  3. package/dist/cjs/cli/bin.js +16 -0
  4. package/dist/cjs/cli/init.js +179 -0
  5. package/dist/cjs/client/components/account-sessions.js +112 -0
  6. package/dist/cjs/client/components/apps-grid.js +46 -0
  7. package/dist/cjs/client/components/auth-buttons.js +42 -0
  8. package/dist/cjs/client/components/feature-gate.js +61 -0
  9. package/dist/cjs/client/components/link-button.js +24 -0
  10. package/dist/cjs/client/components/logo.js +27 -0
  11. package/dist/cjs/client/components/plan-pricing.js +166 -0
  12. package/dist/cjs/client/components/plans-page.js +39 -0
  13. package/dist/cjs/client/components/plans.js +212 -0
  14. package/dist/cjs/client/components/readiness.js +117 -0
  15. package/dist/cjs/client/components/subscribe-dialog.js +60 -0
  16. package/dist/cjs/client/components/subscription.js +189 -0
  17. package/dist/cjs/client/components/usage-meter.js +68 -0
  18. package/dist/cjs/client/components/user-menu.js +172 -0
  19. package/dist/cjs/client/index.js +99 -0
  20. package/dist/cjs/client/mark.js +16 -0
  21. package/dist/cjs/client/provider.js +259 -0
  22. package/dist/cjs/client/use-checkout.js +173 -0
  23. package/dist/cjs/errors.js +74 -0
  24. package/dist/cjs/format-relative-time.js +18 -0
  25. package/dist/cjs/helpers.js +30 -0
  26. package/dist/cjs/index.js +22 -0
  27. package/dist/cjs/next/index.js +311 -0
  28. package/dist/cjs/package.json +3 -0
  29. package/dist/cjs/server/client.js +114 -0
  30. package/dist/cjs/server/handler.js +333 -0
  31. package/dist/cjs/server/http.js +160 -0
  32. package/dist/cjs/server/index.js +18 -0
  33. package/dist/cjs/server/resources/access.js +28 -0
  34. package/dist/cjs/server/resources/ai.js +36 -0
  35. package/dist/cjs/server/resources/apps.js +30 -0
  36. package/dist/cjs/server/resources/billing.js +71 -0
  37. package/dist/cjs/server/resources/drive.js +39 -0
  38. package/dist/cjs/server/resources/entitlements.js +49 -0
  39. package/dist/cjs/server/resources/identity.js +194 -0
  40. package/dist/cjs/server/resources/logs.js +41 -0
  41. package/dist/cjs/server/resources/otp.js +29 -0
  42. package/dist/cjs/server/resources/sessions.js +82 -0
  43. package/dist/cjs/types.js +15 -0
  44. package/dist/esm/cli/bin.js +13 -0
  45. package/dist/esm/cli/init.js +174 -0
  46. package/dist/esm/client/components/account-sessions.js +109 -0
  47. package/dist/esm/client/components/apps-grid.js +43 -0
  48. package/dist/esm/client/components/auth-buttons.js +38 -0
  49. package/dist/esm/client/components/feature-gate.js +57 -0
  50. package/dist/esm/client/components/link-button.js +21 -0
  51. package/dist/esm/client/components/logo.js +24 -0
  52. package/dist/esm/client/components/plan-pricing.js +156 -0
  53. package/dist/esm/client/components/plans-page.js +36 -0
  54. package/dist/esm/client/components/plans.js +209 -0
  55. package/dist/esm/client/components/readiness.js +113 -0
  56. package/dist/esm/client/components/subscribe-dialog.js +57 -0
  57. package/dist/esm/client/components/subscription.js +181 -0
  58. package/dist/esm/client/components/usage-meter.js +65 -0
  59. package/dist/esm/client/components/user-menu.js +164 -0
  60. package/dist/esm/client/index.js +47 -0
  61. package/dist/esm/client/mark.js +13 -0
  62. package/dist/esm/client/provider.js +247 -0
  63. package/dist/esm/client/use-checkout.js +168 -0
  64. package/dist/esm/errors.js +69 -0
  65. package/dist/esm/format-relative-time.js +15 -0
  66. package/dist/esm/helpers.js +26 -0
  67. package/dist/esm/index.js +15 -0
  68. package/dist/esm/next/index.js +279 -0
  69. package/dist/esm/package.json +3 -0
  70. package/dist/esm/server/client.js +110 -0
  71. package/dist/esm/server/handler.js +330 -0
  72. package/dist/esm/server/http.js +156 -0
  73. package/dist/esm/server/index.js +10 -0
  74. package/dist/esm/server/resources/access.js +24 -0
  75. package/dist/esm/server/resources/ai.js +32 -0
  76. package/dist/esm/server/resources/apps.js +26 -0
  77. package/dist/esm/server/resources/billing.js +67 -0
  78. package/dist/esm/server/resources/drive.js +35 -0
  79. package/dist/esm/server/resources/entitlements.js +45 -0
  80. package/dist/esm/server/resources/identity.js +189 -0
  81. package/dist/esm/server/resources/logs.js +37 -0
  82. package/dist/esm/server/resources/otp.js +25 -0
  83. package/dist/esm/server/resources/sessions.js +78 -0
  84. package/dist/esm/types.js +14 -0
  85. package/dist/theme.css +84 -0
  86. package/dist/types/cli/bin.d.ts +1 -0
  87. package/dist/types/cli/init.d.ts +34 -0
  88. package/dist/types/client/components/account-sessions.d.ts +25 -0
  89. package/dist/types/client/components/apps-grid.d.ts +20 -0
  90. package/dist/types/client/components/auth-buttons.d.ts +31 -0
  91. package/dist/types/client/components/feature-gate.d.ts +57 -0
  92. package/dist/types/client/components/link-button.d.ts +20 -0
  93. package/dist/types/client/components/logo.d.ts +35 -0
  94. package/dist/types/client/components/plan-pricing.d.ts +96 -0
  95. package/dist/types/client/components/plans-page.d.ts +36 -0
  96. package/dist/types/client/components/plans.d.ts +34 -0
  97. package/dist/types/client/components/readiness.d.ts +52 -0
  98. package/dist/types/client/components/subscribe-dialog.d.ts +34 -0
  99. package/dist/types/client/components/subscription.d.ts +123 -0
  100. package/dist/types/client/components/usage-meter.d.ts +24 -0
  101. package/dist/types/client/components/user-menu.d.ts +86 -0
  102. package/dist/types/client/index.d.ts +47 -0
  103. package/dist/types/client/mark.d.ts +11 -0
  104. package/dist/types/client/provider.d.ts +209 -0
  105. package/dist/types/client/use-checkout.d.ts +93 -0
  106. package/dist/types/errors.d.ts +62 -0
  107. package/dist/types/format-relative-time.d.ts +2 -0
  108. package/dist/types/helpers.d.ts +33 -0
  109. package/dist/types/index.d.ts +17 -0
  110. package/dist/types/next/index.d.ts +136 -0
  111. package/dist/types/server/client.d.ts +83 -0
  112. package/dist/types/server/handler.d.ts +67 -0
  113. package/dist/types/server/http.d.ts +52 -0
  114. package/dist/types/server/index.d.ts +15 -0
  115. package/dist/types/server/resources/access.d.ts +17 -0
  116. package/dist/types/server/resources/ai.d.ts +24 -0
  117. package/dist/types/server/resources/apps.d.ts +12 -0
  118. package/dist/types/server/resources/billing.d.ts +41 -0
  119. package/dist/types/server/resources/drive.d.ts +26 -0
  120. package/dist/types/server/resources/entitlements.d.ts +31 -0
  121. package/dist/types/server/resources/identity.d.ts +76 -0
  122. package/dist/types/server/resources/logs.d.ts +20 -0
  123. package/dist/types/server/resources/otp.d.ts +17 -0
  124. package/dist/types/server/resources/sessions.d.ts +37 -0
  125. package/dist/types/types.d.ts +434 -0
  126. package/package.json +102 -0
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Browser half of `@cosmoops/connect-sdk` - React components and hooks.
3
+ *
4
+ * Holds no credential. Everything here talks to your own origin, where
5
+ * `createConnectHandler` from `@cosmoops/connect-sdk/server` holds the API key
6
+ * and reaches Connect.
7
+ *
8
+ * Styling is HeroUI v3 semantic tokens only (`accent`, `muted`, `surface`,
9
+ * `separator`, ...), never a hardcoded colour - so these components inherit
10
+ * whatever theme the host service platform already configured, and one theme
11
+ * change lands identically in all of them.
12
+ */
13
+ export { ConnectProvider, useConnect, useConnectUser, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectAddonUsage, useConnectSessions, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
14
+ export type { ConnectProviderProps, ConnectContextValue, ConnectSession } from "./provider.js";
15
+ export { blockingBlockers, normalizePlanFeatures } from "../helpers.js";
16
+ export type { WirePlanFeature } from "../helpers.js";
17
+ export { ConnectError, isConnectError } from "../errors.js";
18
+ export type * from "../types.js";
19
+ export { ConnectMark } from "./mark.js";
20
+ export { ConnectLogo } from "./components/logo.js";
21
+ export type { ConnectLogoProps } from "./components/logo.js";
22
+ export { ConnectLoginButton, ConnectRegisterButton } from "./components/auth-buttons.js";
23
+ export type { ConnectAuthButtonProps } from "./components/auth-buttons.js";
24
+ export { ConnectUserMenu, ConnectManageSubscriptionButton, ConnectInvoicesButton, ConnectProfileButton, ConnectHomeButton, ConnectSignOutButton, } from "./components/user-menu.js";
25
+ export { ConnectAccountSessions } from "./components/account-sessions.js";
26
+ export type { ConnectAccountSessionsProps } from "./components/account-sessions.js";
27
+ export type { ConnectUserMenuProps } from "./components/user-menu.js";
28
+ export { ConnectAppsGrid } from "./components/apps-grid.js";
29
+ export type { ConnectAppsGridProps } from "./components/apps-grid.js";
30
+ export { ConnectPlans } from "./components/plans.js";
31
+ export type { ConnectPlansProps } from "./components/plans.js";
32
+ export { ConnectSubscribeDialog } from "./components/subscribe-dialog.js";
33
+ export type { ConnectSubscribeDialogProps } from "./components/subscribe-dialog.js";
34
+ export { ConnectSubscriptionGate, ConnectNoSubscriptionCard, ConnectSubscriptionCard, ConnectTrialBanner, ConnectSeatUsage, resolveSubscriptionState, } from "./components/subscription.js";
35
+ export type { ConnectSubscriptionGateProps, ConnectNoSubscriptionCardProps, ConnectSubscriptionCardProps, ConnectTrialBannerProps, ConnectSubscriptionState, } from "./components/subscription.js";
36
+ export { ConnectReadinessBanner, ConnectReadinessGate } from "./components/readiness.js";
37
+ export type { ConnectReadinessBannerProps, ConnectReadinessGateProps } from "./components/readiness.js";
38
+ export { ConnectFeatureGate, useConnectFeature } from "./components/feature-gate.js";
39
+ export type { ConnectFeatureGateProps } from "./components/feature-gate.js";
40
+ export { ConnectUsageMeter } from "./components/usage-meter.js";
41
+ export type { ConnectUsageMeterProps } from "./components/usage-meter.js";
42
+ export { ConnectPlansPage } from "./components/plans-page.js";
43
+ export type { ConnectPlansPageProps } from "./components/plans-page.js";
44
+ export { useConnectCheckout, buildCheckoutUrl } from "./use-checkout.js";
45
+ export type { ConnectCheckoutStatus, ConnectCheckoutState, ConnectEntitlements, StartCheckoutOptions, CheckoutUrlOptions, } from "./use-checkout.js";
46
+ export { formatPrice, monthlyOf, resolveAction, annualSaving, discountLabel, ACTION_LABEL, groupPlansByTier, resolvePlanGroup, } from "./components/plan-pricing.js";
47
+ export type { PlanAction, PlanTier, ComparablePlan } from "./components/plan-pricing.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The Connect mark - concentric rings on a solid accent badge.
3
+ *
4
+ * Painted from `--accent` / `--accent-foreground`, the same HeroUI tokens the
5
+ * host app themes, so the badge tracks whatever palette a service platform has
6
+ * configured rather than carrying a hardcoded CosmoOps green into someone
7
+ * else's brand.
8
+ */
9
+ export declare function ConnectMark({ className }: Readonly<{
10
+ className?: string;
11
+ }>): import("react").JSX.Element;
@@ -0,0 +1,209 @@
1
+ import type { ReactNode } from "react";
2
+ import type { AddonUsageResponse, ConnectUser, OrganizationApp, PlansResponse, ReadinessResponse, ServiceRole, SessionsListResponse, SessionSubscription, SubscriptionResponse } from "../types.js";
3
+ export declare const CONNECT_APP_URL = "https://connect.cosmoops.com";
4
+ export interface ConnectSession {
5
+ user: ConnectUser | null;
6
+ subscription: SessionSubscription | null;
7
+ serviceRole: ServiceRole | null;
8
+ }
9
+ export interface ConnectContextValue {
10
+ /** Public OAuth client id. Safe in a browser - it goes in the authorize URL. */
11
+ clientId: string;
12
+ /** Connect's own origin, for deep links out to Profile, Billing and the catalog. */
13
+ appUrl: string;
14
+ session: ConnectSession | null;
15
+ /** `true` until the first `/session` read settles. Render skeletons, not a Login button. */
16
+ isLoading: boolean;
17
+ error: string | null;
18
+ signIn(options?: {
19
+ prompt?: "create";
20
+ }): void;
21
+ signOut(): Promise<void>;
22
+ refresh(): Promise<void>;
23
+ /** GET through the bridge, relative to `basePath`. Shared cache, deduped in flight. */
24
+ fetchResource<T>(path: string): Promise<T>;
25
+ /**
26
+ * POST through the bridge, relative to `basePath`. Unlike `signOut`
27
+ * (which deliberately swallows a failed logout so the local UI doesn't
28
+ * hang on a Connect outage), this throws on a non-2xx response - a caller
29
+ * mutating state needs to know when it didn't take.
30
+ *
31
+ * Evicts only `path`'s own cache entry, not the whole shared cache - a
32
+ * blunt full clear would break `fetchResource`'s in-flight dedup for
33
+ * every *other* resource mounted on the same page. A component that reads
34
+ * `path` through `useConnectResource` still needs to call that hook's own
35
+ * `refetch()` afterward; clearing the cache entry alone does not re-render
36
+ * anything by itself.
37
+ */
38
+ mutateResource<T>(path: string, body?: unknown): Promise<T>;
39
+ /**
40
+ * Drops one path's cached read so the next `fetchResource` for it goes to
41
+ * the bridge again.
42
+ *
43
+ * `useConnectResource`'s own `refetch()` is not enough on its own: it bumps
44
+ * a version counter to re-run that hook's effect, and the effect calls
45
+ * `fetchResource`, which hands back the *same* cached promise. That is
46
+ * correct after a `mutateResource` (which evicts the entry itself), but a
47
+ * caller polling for a change made somewhere else entirely - a checkout
48
+ * completed in a Connect tab, say - has no mutation to piggyback on. So
49
+ * invalidate, then refetch.
50
+ *
51
+ * Scoped to one path for the same reason `mutateResource` is: clearing the
52
+ * whole cache would break in-flight dedup for every other resource on the
53
+ * page.
54
+ */
55
+ invalidateResource(path: string): void;
56
+ }
57
+ export interface ConnectProviderProps {
58
+ /**
59
+ * This service's public `client_id`, from its Setup tab in Connect.
60
+ *
61
+ * Optional in Next.js, where it falls back to `NEXT_PUBLIC_CONNECT_CLIENT_ID`
62
+ * - inlined at build time, so `<ConnectProvider>` needs no props at all.
63
+ * Pass it explicitly anywhere that variable does not exist.
64
+ */
65
+ clientId?: string;
66
+ /** Where `createConnectHandler` is mounted in your app. Must match its `basePath`. */
67
+ basePath?: string;
68
+ /**
69
+ * Where Connect sends the user back after sign-in. Your own callback route,
70
+ * not a Connect URL - it is your backend that exchanges the code.
71
+ */
72
+ redirectUri?: string;
73
+ /** Override for non-production Connect instances. */
74
+ appUrl?: string;
75
+ /**
76
+ * Replace the `fetch` used to reach your bridge. The browser-side twin of
77
+ * `ConnectClientOptions.fetch` - it exists so Storybook and tests can drive
78
+ * the real provider against canned responses instead of monkey-patching
79
+ * `window.fetch`, which is a render-time global mutation and races with
80
+ * child effects.
81
+ */
82
+ fetch?: (input: string, init?: RequestInit) => Promise<Response>;
83
+ children: ReactNode;
84
+ }
85
+ /**
86
+ * Wraps the part of your app that shows Connect UI.
87
+ *
88
+ * Holds one session read, shared by every component below it, so a page with a
89
+ * user menu, an apps grid and a plan table makes one request rather than three.
90
+ * There is no API key here and there cannot be: everything goes to your own
91
+ * origin, where `createConnectHandler` holds the credential.
92
+ *
93
+ * ```tsx
94
+ * // Next.js - clientId comes from NEXT_PUBLIC_CONNECT_CLIENT_ID
95
+ * <ConnectProvider>
96
+ * <App />
97
+ * </ConnectProvider>
98
+ * ```
99
+ */
100
+ export declare function ConnectProvider({ clientId, basePath, redirectUri, appUrl, fetch: fetchImpl, children, }: ConnectProviderProps): import("react").JSX.Element;
101
+ export declare function useConnect(): ConnectContextValue;
102
+ /** The signed-in user, or `null`. Sugar over `useConnect().session`. */
103
+ export declare function useConnectUser(): ConnectUser | null;
104
+ /** Readiness gates and the live subscription, fetched on first use. */
105
+ export declare function useConnectEntitlements(): {
106
+ data: {
107
+ readiness: ReadinessResponse;
108
+ subscription: SubscriptionResponse;
109
+ } | null;
110
+ isLoading: boolean;
111
+ error: string | null;
112
+ /**
113
+ * Forces a fresh read - the provider's cache clears the mutated path's
114
+ * entry on a successful `mutateResource` call, but nothing re-triggers
115
+ * this hook's own effect on its own (none of its dependencies change from
116
+ * a cache write nobody here observes). Call this right after a
117
+ * `mutateResource` you made against the same `path` completes.
118
+ */
119
+ refetch(): void;
120
+ };
121
+ /**
122
+ * What is left of every consumable add-on on this workspace's subscription.
123
+ *
124
+ * The read half of `client.billing.consumeAddon` - a service that meters its
125
+ * own end users had no way to show them what remained until this existed.
126
+ * Empty `addons` means there is nothing to meter, which is not an error.
127
+ */
128
+ export declare function useConnectAddonUsage(): {
129
+ data: AddonUsageResponse | null;
130
+ isLoading: boolean;
131
+ error: string | null;
132
+ /**
133
+ * Forces a fresh read - the provider's cache clears the mutated path's
134
+ * entry on a successful `mutateResource` call, but nothing re-triggers
135
+ * this hook's own effect on its own (none of its dependencies change from
136
+ * a cache write nobody here observes). Call this right after a
137
+ * `mutateResource` you made against the same `path` completes.
138
+ */
139
+ refetch(): void;
140
+ };
141
+ /** This service's plan catalog. */
142
+ export declare function useConnectPlans(): {
143
+ data: PlansResponse | null;
144
+ isLoading: boolean;
145
+ error: string | null;
146
+ /**
147
+ * Forces a fresh read - the provider's cache clears the mutated path's
148
+ * entry on a successful `mutateResource` call, but nothing re-triggers
149
+ * this hook's own effect on its own (none of its dependencies change from
150
+ * a cache write nobody here observes). Call this right after a
151
+ * `mutateResource` you made against the same `path` completes.
152
+ */
153
+ refetch(): void;
154
+ };
155
+ /** The apps this organization can open. */
156
+ export declare function useConnectApps(): {
157
+ data: {
158
+ apps: OrganizationApp[];
159
+ } | null;
160
+ isLoading: boolean;
161
+ error: string | null;
162
+ /**
163
+ * Forces a fresh read - the provider's cache clears the mutated path's
164
+ * entry on a successful `mutateResource` call, but nothing re-triggers
165
+ * this hook's own effect on its own (none of its dependencies change from
166
+ * a cache write nobody here observes). Call this right after a
167
+ * `mutateResource` you made against the same `path` completes.
168
+ */
169
+ refetch(): void;
170
+ };
171
+ /**
172
+ * This Connect account's own login sessions - account-wide, not scoped to
173
+ * this service, see `SessionsListResponse`'s doc comment. The component
174
+ * that calls this is also the one that must own any revoke actions and call
175
+ * the returned `refetch()` afterward - see `mutateResource`'s doc comment
176
+ * for why nothing does that automatically.
177
+ */
178
+ export declare function useConnectSessions(): {
179
+ data: SessionsListResponse | null;
180
+ isLoading: boolean;
181
+ error: string | null;
182
+ /**
183
+ * Forces a fresh read - the provider's cache clears the mutated path's
184
+ * entry on a successful `mutateResource` call, but nothing re-triggers
185
+ * this hook's own effect on its own (none of its dependencies change from
186
+ * a cache write nobody here observes). Call this right after a
187
+ * `mutateResource` you made against the same `path` completes.
188
+ */
189
+ refetch(): void;
190
+ };
191
+ /**
192
+ * One bridge read, shared through the provider's cache.
193
+ *
194
+ * Only fetches once a user is known: every resource route needs a session, so
195
+ * firing before one exists just buys a guaranteed 401.
196
+ */
197
+ export declare function useConnectResource<T>(path: string): {
198
+ data: T | null;
199
+ isLoading: boolean;
200
+ error: string | null;
201
+ /**
202
+ * Forces a fresh read - the provider's cache clears the mutated path's
203
+ * entry on a successful `mutateResource` call, but nothing re-triggers
204
+ * this hook's own effect on its own (none of its dependencies change from
205
+ * a cache write nobody here observes). Call this right after a
206
+ * `mutateResource` you made against the same `path` completes.
207
+ */
208
+ refetch(): void;
209
+ };
@@ -0,0 +1,93 @@
1
+ import type { ReadinessResponse, SubscriptionResponse } from "../types.js";
2
+ /**
3
+ * Where a hand-off to Connect has got to.
4
+ *
5
+ * - `idle` - nothing started, or the last one was acknowledged.
6
+ * - `waiting` - Connect is open and this hook is polling `/entitlements`.
7
+ * - `completed` - the entitlement actually changed. The only success signal
8
+ * worth trusting; see this module's doc comment for why the Connect window
9
+ * closing is not one.
10
+ * - `abandoned` - the window closed (or the deadline passed) with the
11
+ * entitlement unchanged.
12
+ * - `blocked` - the browser refused to open the window at all.
13
+ */
14
+ export type ConnectCheckoutStatus = "idle" | "waiting" | "completed" | "abandoned" | "blocked";
15
+ export interface ConnectCheckoutState {
16
+ status: ConnectCheckoutStatus;
17
+ /** The Connect URL of the run that produced this state - render it as a fallback link when `status` is `"blocked"`. */
18
+ url: string | null;
19
+ }
20
+ export interface StartCheckoutOptions {
21
+ /** Absolute Connect URL to open. Build it with `buildCheckoutUrl`. */
22
+ url: string;
23
+ /**
24
+ * `true` once `/entitlements` reflects the change being waited on.
25
+ *
26
+ * Compared against the entitlements read *before* the window opened, so a
27
+ * predicate is genuinely needed - "the subscription exists" is the right
28
+ * test for a first subscribe and the wrong one for a plan change, where a
29
+ * subscription existed all along.
30
+ */
31
+ isSettled(entitlements: ConnectEntitlements): boolean;
32
+ }
33
+ export interface ConnectEntitlements {
34
+ readiness: ReadinessResponse;
35
+ subscription: SubscriptionResponse;
36
+ }
37
+ /**
38
+ * Drives a checkout that completes on Connect, not here.
39
+ *
40
+ * The partner's page cannot complete a subscription itself - see
41
+ * `ConnectPlans`' doc comment for why that is deliberate - so the flow is:
42
+ * open Connect, watch `/entitlements` until the entitlement changes, then
43
+ * refresh the session so every Connect component on the page repaints.
44
+ *
45
+ * **The result is read from Connect's own state, never from the window.**
46
+ * A closed window proves nothing: the customer may have paid and closed it,
47
+ * or opened it, thought better of it, and closed it. Polling is what tells
48
+ * the two apart, which is why the window closing only starts a short grace
49
+ * period rather than ending the run.
50
+ */
51
+ export declare function useConnectCheckout(): ConnectCheckoutState & {
52
+ /** Opens `url` and starts watching. A second call supersedes any run in flight. */
53
+ start(options: StartCheckoutOptions): void;
54
+ /** Stops watching and returns to `idle` - for a "Done"/dismiss control. */
55
+ reset(): void;
56
+ };
57
+ export interface CheckoutUrlOptions {
58
+ /** Connect's origin, from `useConnect().appUrl`. */
59
+ appUrl: string;
60
+ serviceId: string;
61
+ planId: string;
62
+ /**
63
+ * The live subscription's id, when there is one. Present means "change the
64
+ * plan on this subscription"; absent means "subscribe for the first time" -
65
+ * and the two are separate screens in Connect, because only one of them can
66
+ * price proration against a cycle already paid for.
67
+ */
68
+ subscriptionId?: string | null;
69
+ /** Carried into Connect's checkout, where it is validated against the real cart. */
70
+ coupon?: string | null;
71
+ }
72
+ /**
73
+ * The Connect URL that cancels a subscription.
74
+ *
75
+ * Its own builder rather than a mode on `buildCheckoutUrl`, because it takes
76
+ * no plan: cancelling is about the subscription, not about what to move it to.
77
+ * Lands on the same kind of intercepted modal the other two do
78
+ * (`app/@modal/(.)billing/.../cancel`).
79
+ */
80
+ export declare function buildCancelUrl({ appUrl, subscriptionId }: {
81
+ appUrl: string;
82
+ subscriptionId: string;
83
+ }): string;
84
+ /**
85
+ * The Connect URL that completes a subscribe or a plan change.
86
+ *
87
+ * Both land on a modal Connect renders over the relevant page (an intercepted
88
+ * parallel route - `app/@modal/(.)services/...` and
89
+ * `app/@modal/(.)billing/...`), so the customer arrives already looking at the
90
+ * confirmation for the plan they picked here, not at a catalogue they have to
91
+ * find it in again.
92
+ */
93
+ export declare function buildCheckoutUrl({ appUrl, serviceId, planId, subscriptionId, coupon, }: CheckoutUrlOptions): string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Every failure the Connect service API can report, as one class.
3
+ *
4
+ * The API speaks two error dialects and this collapses them: the
5
+ * `/api/v1/service/*` endpoints use `{ error: { code, message } }`, while the
6
+ * three `/sso/*` ones use OAuth2's `{ error, error_description }` (RFC 6749
7
+ * §5.2). Both land here with `code` populated, so integrating code branches on
8
+ * one field either way.
9
+ */
10
+ export declare class ConnectError extends Error {
11
+ readonly name = "ConnectError";
12
+ /** HTTP status. `0` when the request never got a response (network failure, timeout). */
13
+ readonly status: number;
14
+ /** `rate_limited`, `forbidden`, `invalid_token`, ... - branch on this, never on `message`. */
15
+ readonly code: string;
16
+ /** From `Retry-After` on a 429, in milliseconds. `null` otherwise. */
17
+ readonly retryAfterMs: number | null;
18
+ /** The endpoint that failed, e.g. `POST /sso/token` - present on every error. */
19
+ readonly endpoint: string;
20
+ /** Parsed response body, when there was one. */
21
+ readonly body: unknown;
22
+ constructor(init: {
23
+ message: string;
24
+ status: number;
25
+ code: string;
26
+ endpoint: string;
27
+ retryAfterMs?: number | null;
28
+ body?: unknown;
29
+ cause?: unknown;
30
+ });
31
+ /**
32
+ * Whether trying the exact same request again could plausibly succeed -
33
+ * throttling, a transient server fault, or no response at all. The SDK
34
+ * already retries these internally (see `ConnectClientOptions.maxRetries`);
35
+ * this is for callers deciding whether to queue their own retry after the
36
+ * SDK has given up.
37
+ */
38
+ get isRetryable(): boolean;
39
+ /**
40
+ * Whether the user's Connect sign-in is over and they need to go back
41
+ * through `identity.authorizeUrl()`. Collapses every session-side failure,
42
+ * matching how the API reports them - unknown, expired, revoked, or issued
43
+ * to a different service all arrive as one `invalid_token`.
44
+ */
45
+ get isSessionExpired(): boolean;
46
+ /**
47
+ * Whether *your own API key* is dead - revoked or past its expiry - rather
48
+ * than the request being wrong.
49
+ *
50
+ * Connect answers `403` for every credential failure and keeps the code
51
+ * uniform (`forbidden`) whenever telling them apart would help someone
52
+ * guessing keys. These two are the exception: reaching them required
53
+ * presenting a key whose hash matched, so you already hold it, and what you
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.
56
+ *
57
+ * Worth alerting on. It is the one failure that will not resolve itself and
58
+ * takes down every call your integration makes.
59
+ */
60
+ get isKeyRejected(): boolean;
61
+ }
62
+ export declare function isConnectError(error: unknown): error is ConnectError;
@@ -0,0 +1,2 @@
1
+ /** "3m ago" / "2h ago" / "5d ago", falling back to a plain date past 30 days. */
2
+ export declare function formatRelativeTime(date: Date): string;
@@ -0,0 +1,33 @@
1
+ import type { PlanFeature, ReadinessBlocker, ReadinessResponse } from "./types.js";
2
+ /**
3
+ * The blockers that actually deny access, in the order to show them.
4
+ *
5
+ * Advisory ones (no saved payment method mid-subscription, say) are worth
6
+ * surfacing but are not a reason to lock anyone out today - filtering them here
7
+ * keeps that judgement in one place rather than in every call site.
8
+ */
9
+ export declare function blockingBlockers(readiness: ReadinessResponse): ReadinessBlocker[];
10
+ /**
11
+ * What `features` can look like coming off the wire.
12
+ *
13
+ * Connect used to send bare keys and now sends `{ key, name, description }`.
14
+ * A published SDK talks to whatever version the partner's Connect instance is
15
+ * running and has no say in when that moves - so both shapes are real, and a
16
+ * client that assumes the newer one renders an empty feature list against
17
+ * every instance that has not upgraded yet.
18
+ */
19
+ export type WirePlanFeature = string | PlanFeature;
20
+ /**
21
+ * Coerces either wire shape into `PlanFeature[]`.
22
+ *
23
+ * A bare key becomes `{ key, name: key, description: "" }` - the same
24
+ * fallback the server applies for a plan older than its service's feature
25
+ * catalog, so an un-upgraded Connect renders exactly what it always did
26
+ * rather than a column of blanks.
27
+ *
28
+ * Applied inside `BillingResource.listPlans`, so anything reading a plan
29
+ * through the SDK or its bridge already has the normalised shape and no
30
+ * component needs to know this ambiguity exists. Exported for a partner who
31
+ * hand-rolled a bridge instead of using `createConnectHandler`.
32
+ */
33
+ export declare function normalizePlanFeatures(features: readonly WirePlanFeature[] | undefined): PlanFeature[];
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@cosmoops/connect-sdk` - the official client for the CosmoOps Connect
3
+ * **service-partner API** (`/api/v1/service/*`).
4
+ *
5
+ * This root entry is the isomorphic surface: wire types, the error class, and
6
+ * pure helpers. It pulls in neither Node-only code nor React, so it is safe to
7
+ * import from anywhere.
8
+ *
9
+ * - `@cosmoops/connect-sdk/server` - `ConnectClient` and `createConnectHandler`.
10
+ * Holds the API key. Never import this from browser code.
11
+ * - `@cosmoops/connect-sdk/client` - React components and hooks. Talks to your
12
+ * own origin only.
13
+ */
14
+ export { ConnectError, isConnectError } from "./errors.js";
15
+ export { blockingBlockers, normalizePlanFeatures } from "./helpers.js";
16
+ export type { WirePlanFeature } from "./helpers.js";
17
+ export type * from "./types.js";
@@ -0,0 +1,136 @@
1
+ import { ConnectClient, type ConnectClientOptions } from "../server/client.js";
2
+ import { type ConnectSessionStore } from "../server/handler.js";
3
+ import type { UserResponse } from "../types.js";
4
+ /**
5
+ * The shared server client, built from the environment on first use.
6
+ *
7
+ * Lazy on purpose: reading `CONNECT_API_KEY` at module load would throw during
8
+ * `next build`, where route modules are imported but never invoked and secrets
9
+ * are frequently absent.
10
+ */
11
+ export declare function connectClient(overrides?: Partial<ConnectClientOptions>): ConnectClient;
12
+ /**
13
+ * An httpOnly cookie holding the opaque `sessionToken`.
14
+ *
15
+ * Not signed or encrypted, deliberately. The token is opaque and useless
16
+ * without the `client_secret` that never leaves the server, so a tampered
17
+ * value buys an attacker a 401 rather than a forged identity - and a signing
18
+ * secret would be one more thing to rotate for protection already provided by
19
+ * the token's own design. `SameSite=Lax` because `Strict` would withhold the
20
+ * cookie on the redirect back from Connect.
21
+ */
22
+ export declare function cookieSessionStore(cookieName?: string): ConnectSessionStore;
23
+ export interface CreateConnectHandlersOptions {
24
+ client?: ConnectClient;
25
+ session?: ConnectSessionStore;
26
+ basePath?: string;
27
+ /** Where `/callback` lands when the request carried no `next`. Defaults to `/`. */
28
+ defaultRedirect?: string;
29
+ }
30
+ /**
31
+ * The catch-all route handlers, ready to re-export.
32
+ *
33
+ * Only needed when you are changing something - a different mount point, a
34
+ * session store that isn't a cookie, a post-login destination that isn't `/`.
35
+ * The zero-configuration case is the `GET` / `POST` re-export below, which is
36
+ * this function with no arguments.
37
+ *
38
+ * ```ts
39
+ * // app/api/connect/[...connect]/route.ts
40
+ * import { createConnectHandlers } from "@cosmoops/connect-sdk/next";
41
+ * export const { GET, POST } = createConnectHandlers({ basePath: "/api/auth" });
42
+ * ```
43
+ *
44
+ * `GET` and `POST` are the same function - the bridge routes on method and
45
+ * path itself, so Next only needs to know both verbs reach it.
46
+ */
47
+ export declare function createConnectHandlers(options?: CreateConnectHandlersOptions): {
48
+ GET: (request: Request) => Promise<Response>;
49
+ POST: (request: Request) => Promise<Response>;
50
+ };
51
+ /**
52
+ * The bridge itself, for the default mount at `/api/connect`.
53
+ *
54
+ * Built on the first request rather than at module load, for the same reason
55
+ * `connectClient` is lazy: `next build` imports every route module, and an
56
+ * environment-reading constructor at the top level turns a missing secret in
57
+ * CI into a failed build of routes that were never going to run there.
58
+ */
59
+ declare function handle(request: Request): Promise<Response>;
60
+ /**
61
+ * The one-line backend.
62
+ *
63
+ * ```ts
64
+ * // app/api/connect/[...connect]/route.ts
65
+ * export { GET, POST } from "@cosmoops/connect-sdk/next";
66
+ * ```
67
+ *
68
+ * That gives you `/signin`, `/callback`, the session cookie, and the
69
+ * `/session` `/entitlements` `/plans` `/apps` `/addons` `/sessions` `/logout`
70
+ * reads the browser half calls. Reach for `createConnectHandlers` instead only
71
+ * when you need to configure one of them.
72
+ */
73
+ export { handle as GET, handle as POST };
74
+ /**
75
+ * The signed-in user for a Server Component, Route Handler or Server Action,
76
+ * or `null`.
77
+ *
78
+ * ```tsx
79
+ * // app/dashboard/page.tsx
80
+ * const session = await getConnectSession();
81
+ * if (!session) redirect("/api/connect/signin?next=/dashboard");
82
+ * ```
83
+ *
84
+ * Pass a `Request` to use it somewhere `next/headers` does not exist - a
85
+ * framework-agnostic handler, a test, a custom server. Both forms take the
86
+ * same path afterwards; the argument is only how the cookie is found.
87
+ *
88
+ * Reads `cookies()` transitively, so under `cacheComponents` this belongs
89
+ * inside a `<Suspense>` boundary (or a `'use cache: private'` function) like
90
+ * any other request-scoped read - a page that awaits it above every boundary
91
+ * gives up its static shell.
92
+ *
93
+ * Returns `null` for every failure - an expired session and a missing one both
94
+ * mean "not signed in", and a page guard should not need a try/catch.
95
+ */
96
+ export declare function getConnectSession(request?: Request, options?: {
97
+ client?: ConnectClient;
98
+ session?: ConnectSessionStore;
99
+ }): Promise<UserResponse | null>;
100
+ /**
101
+ * `next.config` wrapper.
102
+ *
103
+ * Asserts, at config load, that the public client id is present - the one
104
+ * variable whose absence fails silently in the browser instead of loudly on
105
+ * the server, because `NEXT_PUBLIC_*` is inlined at build time and an unset
106
+ * one simply becomes `undefined`. That is its whole job.
107
+ *
108
+ * In particular it does **not** replace the bridge route, and cannot: Next has
109
+ * no plugin API that contributes a route from a `node_modules` package - the
110
+ * App Router discovers routes by walking your own `app/` directory and nothing
111
+ * else, and a `next.config` rewrite still needs a real route to point at. So
112
+ * `app/api/connect/[...connect]/route.ts` has to exist whether or not you use
113
+ * this wrapper. It is one line, and `npx @cosmoops/connect-sdk init` writes it:
114
+ *
115
+ * ```ts
116
+ * export { GET, POST } from "@cosmoops/connect-sdk/next";
117
+ * ```
118
+ *
119
+ * The two are independent - the route works with a plain config, and this
120
+ * wrapper is useful with no route at all. Using both is just the default setup.
121
+ *
122
+ * It does **not** add this package to `transpilePackages`, and you should not
123
+ * either. `dist/` is already ES2022 with `"use client"` hoisted to the top of
124
+ * every browser file, so Turbopack and webpack both consume it as-is;
125
+ * transpiling it again only costs build time. An earlier version of this
126
+ * wrapper added the entry, which is why it may still be in your config.
127
+ *
128
+ * ```ts
129
+ * import { withConnect } from "@cosmoops/connect-sdk/next";
130
+ * export default withConnect({ });
131
+ * ```
132
+ */
133
+ export declare function withConnect<T extends object>(config?: T): T;
134
+ export { createConnectHandler } from "../server/handler.js";
135
+ export type { ConnectSessionStore } from "../server/handler.js";
136
+ export { ConnectClient } from "../server/client.js";