@cosmoops/connect-sdk 0.3.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) 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 +24 -22
  7. package/dist/cjs/client/components/app-shell.js +157 -0
  8. package/dist/cjs/client/components/apps-grid.js +4 -3
  9. package/dist/cjs/client/components/feature-gate.js +2 -2
  10. package/dist/cjs/client/components/integrations-modal.js +95 -0
  11. package/dist/cjs/client/components/logo.js +3 -3
  12. package/dist/cjs/client/components/motion.js +27 -0
  13. package/dist/cjs/client/components/nav.js +97 -0
  14. package/dist/cjs/client/components/plan-pricing.js +1 -1
  15. package/dist/cjs/client/components/plans.js +42 -40
  16. package/dist/cjs/client/components/readiness.js +6 -4
  17. package/dist/cjs/client/components/side-nav.js +58 -0
  18. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  19. package/dist/cjs/client/components/subscription.js +3 -2
  20. package/dist/cjs/client/components/surfaces.js +54 -0
  21. package/dist/cjs/client/components/usage-meter.js +4 -3
  22. package/dist/cjs/client/components/user-menu.js +33 -6
  23. package/dist/cjs/client/index.js +30 -2
  24. package/dist/cjs/client/mark.js +1 -1
  25. package/dist/cjs/client/provider.js +60 -8
  26. package/dist/cjs/client/use-active-path.js +77 -0
  27. package/dist/cjs/client/use-checkout.js +3 -3
  28. package/dist/cjs/errors.js +4 -4
  29. package/dist/cjs/helpers.js +1 -1
  30. package/dist/cjs/index.js +1 -1
  31. package/dist/cjs/next/index.js +96 -15
  32. package/dist/cjs/server/client.js +22 -14
  33. package/dist/cjs/server/handler.js +99 -16
  34. package/dist/cjs/server/http.js +1 -1
  35. package/dist/cjs/server/index.js +8 -1
  36. package/dist/cjs/server/resources/access.js +3 -3
  37. package/dist/cjs/server/resources/ai.js +1 -1
  38. package/dist/cjs/server/resources/analytics.js +66 -0
  39. package/dist/cjs/server/resources/apps.js +4 -4
  40. package/dist/cjs/server/resources/billing.js +5 -5
  41. package/dist/cjs/server/resources/drive.js +4 -4
  42. package/dist/cjs/server/resources/entitlements.js +7 -7
  43. package/dist/cjs/server/resources/identity.js +4 -4
  44. package/dist/cjs/server/resources/integrations.js +66 -0
  45. package/dist/cjs/server/resources/logs.js +3 -3
  46. package/dist/cjs/server/resources/sessions.js +6 -6
  47. package/dist/cjs/server/webhooks.js +127 -0
  48. package/dist/cjs/types.js +1 -1
  49. package/dist/esm/cli/doctor.js +3 -3
  50. package/dist/esm/cli/init.js +3 -3
  51. package/dist/esm/client/components/access-gate.js +158 -0
  52. package/dist/esm/client/components/account-sessions.js +24 -22
  53. package/dist/esm/client/components/app-shell.js +154 -0
  54. package/dist/esm/client/components/apps-grid.js +4 -3
  55. package/dist/esm/client/components/feature-gate.js +2 -2
  56. package/dist/esm/client/components/integrations-modal.js +92 -0
  57. package/dist/esm/client/components/logo.js +3 -3
  58. package/dist/esm/client/components/motion.js +21 -0
  59. package/dist/esm/client/components/nav.js +89 -0
  60. package/dist/esm/client/components/plan-pricing.js +1 -1
  61. package/dist/esm/client/components/plans.js +42 -40
  62. package/dist/esm/client/components/readiness.js +6 -4
  63. package/dist/esm/client/components/side-nav.js +53 -0
  64. package/dist/esm/client/components/subscribe-dialog.js +2 -2
  65. package/dist/esm/client/components/subscription.js +3 -2
  66. package/dist/esm/client/components/surfaces.js +51 -0
  67. package/dist/esm/client/components/usage-meter.js +4 -3
  68. package/dist/esm/client/components/user-menu.js +34 -7
  69. package/dist/esm/client/index.js +16 -2
  70. package/dist/esm/client/mark.js +1 -1
  71. package/dist/esm/client/provider.js +59 -8
  72. package/dist/esm/client/use-active-path.js +74 -0
  73. package/dist/esm/client/use-checkout.js +3 -3
  74. package/dist/esm/errors.js +4 -4
  75. package/dist/esm/helpers.js +1 -1
  76. package/dist/esm/index.js +1 -1
  77. package/dist/esm/next/index.js +95 -15
  78. package/dist/esm/server/client.js +22 -14
  79. package/dist/esm/server/handler.js +99 -16
  80. package/dist/esm/server/http.js +1 -1
  81. package/dist/esm/server/index.js +5 -0
  82. package/dist/esm/server/resources/access.js +3 -3
  83. package/dist/esm/server/resources/ai.js +1 -1
  84. package/dist/esm/server/resources/analytics.js +62 -0
  85. package/dist/esm/server/resources/apps.js +4 -4
  86. package/dist/esm/server/resources/billing.js +5 -5
  87. package/dist/esm/server/resources/drive.js +4 -4
  88. package/dist/esm/server/resources/entitlements.js +7 -7
  89. package/dist/esm/server/resources/identity.js +4 -4
  90. package/dist/esm/server/resources/integrations.js +62 -0
  91. package/dist/esm/server/resources/logs.js +3 -3
  92. package/dist/esm/server/resources/sessions.js +6 -6
  93. package/dist/esm/server/webhooks.js +123 -0
  94. package/dist/esm/types.js +1 -1
  95. package/dist/theme.css +28 -0
  96. package/dist/types/client/components/access-gate.d.ts +79 -0
  97. package/dist/types/client/components/account-sessions.d.ts +1 -1
  98. package/dist/types/client/components/app-shell.d.ts +115 -0
  99. package/dist/types/client/components/apps-grid.d.ts +1 -1
  100. package/dist/types/client/components/feature-gate.d.ts +1 -1
  101. package/dist/types/client/components/integrations-modal.d.ts +21 -0
  102. package/dist/types/client/components/logo.d.ts +5 -5
  103. package/dist/types/client/components/motion.d.ts +22 -0
  104. package/dist/types/client/components/nav.d.ts +110 -0
  105. package/dist/types/client/components/plan-pricing.d.ts +2 -2
  106. package/dist/types/client/components/readiness.d.ts +2 -2
  107. package/dist/types/client/components/side-nav.d.ts +31 -0
  108. package/dist/types/client/components/subscribe-dialog.d.ts +3 -3
  109. package/dist/types/client/components/surfaces.d.ts +51 -0
  110. package/dist/types/client/components/usage-meter.d.ts +1 -1
  111. package/dist/types/client/components/user-menu.d.ts +2 -2
  112. package/dist/types/client/index.d.ts +13 -2
  113. package/dist/types/client/mark.d.ts +1 -1
  114. package/dist/types/client/provider.d.ts +44 -9
  115. package/dist/types/client/use-active-path.d.ts +8 -0
  116. package/dist/types/client/use-checkout.d.ts +3 -3
  117. package/dist/types/errors.d.ts +4 -4
  118. package/dist/types/helpers.d.ts +1 -1
  119. package/dist/types/index.d.ts +1 -1
  120. package/dist/types/next/index.d.ts +40 -0
  121. package/dist/types/server/client.d.ts +17 -11
  122. package/dist/types/server/handler.d.ts +2 -2
  123. package/dist/types/server/index.d.ts +7 -0
  124. package/dist/types/server/resources/access.d.ts +3 -3
  125. package/dist/types/server/resources/ai.d.ts +2 -2
  126. package/dist/types/server/resources/analytics.d.ts +31 -0
  127. package/dist/types/server/resources/apps.d.ts +4 -4
  128. package/dist/types/server/resources/billing.d.ts +4 -4
  129. package/dist/types/server/resources/drive.d.ts +4 -4
  130. package/dist/types/server/resources/entitlements.d.ts +4 -4
  131. package/dist/types/server/resources/identity.d.ts +14 -4
  132. package/dist/types/server/resources/integrations.d.ts +40 -0
  133. package/dist/types/server/resources/logs.d.ts +3 -3
  134. package/dist/types/server/resources/sessions.d.ts +5 -5
  135. package/dist/types/server/webhooks.d.ts +67 -0
  136. package/dist/types/types.d.ts +220 -39
  137. package/package.json +10 -4
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.resolvePlanGroup = exports.groupPlansByTier = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = exports.resolveAction = exports.monthlyOf = exports.formatPrice = exports.buildCheckoutUrl = exports.useConnectCheckout = exports.ConnectPlansPage = exports.ConnectUsageMeter = exports.useConnectFeature = exports.ConnectFeatureGate = exports.ConnectReadinessGate = exports.ConnectReadinessBanner = exports.resolveSubscriptionState = exports.ConnectSeatUsage = exports.ConnectTrialBanner = exports.ConnectSubscriptionCard = exports.ConnectNoSubscriptionCard = exports.ConnectSubscriptionGate = exports.ConnectSubscribeDialog = exports.ConnectPlans = exports.ConnectAppsGrid = exports.ConnectAccountSessions = exports.ConnectSignOutButton = exports.ConnectHomeButton = exports.ConnectProfileButton = exports.ConnectInvoicesButton = exports.ConnectManageSubscriptionButton = exports.ConnectUserMenu = exports.ConnectRegisterButton = exports.ConnectLoginButton = exports.ConnectLogo = exports.ConnectMark = exports.isConnectError = exports.ConnectError = exports.normalizePlanFeatures = exports.blockingBlockers = exports.CONNECT_APP_URL = exports.useConnectResource = exports.useConnectSessions = exports.useConnectAddonUsage = exports.useConnectApps = exports.useConnectPlans = exports.useConnectEntitlements = exports.useConnectUser = exports.useConnect = exports.ConnectProvider = void 0;
4
+ exports.ConnectFeatureGate = exports.useConnectAccessGate = exports.ConnectAccessGate = exports.ConnectReadinessGate = exports.ConnectReadinessBanner = exports.resolveSubscriptionState = exports.ConnectSeatUsage = exports.ConnectTrialBanner = exports.ConnectSubscriptionCard = exports.ConnectNoSubscriptionCard = exports.ConnectSubscriptionGate = exports.ConnectSubscribeDialog = exports.ConnectPlans = exports.ConnectIntegrationsModal = exports.ConnectAppsGrid = exports.gateExemptPaths = exports.isNavItemActive = exports.resolveActivePath = exports.normalizePath = exports.flattenNav = exports.normalizeNav = exports.useActivePath = exports.ConnectSideNav = exports.ConnectAppShell = exports.ConnectAccountSessions = exports.ConnectSignOutButton = exports.ConnectHomeButton = exports.ConnectProfileButton = exports.ConnectInvoicesButton = exports.ConnectManageSubscriptionButton = exports.ConnectUserMenu = exports.ConnectRegisterButton = exports.ConnectLoginButton = exports.ConnectLogo = exports.ConnectMark = exports.isConnectError = exports.ConnectError = exports.normalizePlanFeatures = exports.blockingBlockers = exports.CONNECT_APP_URL = exports.useConnectResource = exports.useConnectIntegrations = exports.useConnectSessions = exports.useConnectAddonUsage = exports.useConnectApps = exports.useConnectPlans = exports.useConnectEntitlements = exports.useConnectUser = exports.useConnect = exports.ConnectProvider = void 0;
5
+ exports.resolvePlanGroup = exports.groupPlansByTier = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = exports.resolveAction = exports.monthlyOf = exports.formatPrice = exports.buildCheckoutUrl = exports.useConnectCheckout = exports.ConnectPlansPage = exports.ConnectUsageMeter = exports.useConnectFeature = void 0;
5
6
  /**
6
7
  * Browser half of `@cosmoops/connect-sdk` - React components and hooks.
7
8
  *
@@ -11,7 +12,7 @@ exports.resolvePlanGroup = exports.groupPlansByTier = exports.ACTION_LABEL = exp
11
12
  *
12
13
  * Styling is HeroUI v3 semantic tokens only (`accent`, `muted`, `surface`,
13
14
  * `separator`, ...), never a hardcoded colour - so these components inherit
14
- * whatever theme the host service platform already configured, and one theme
15
+ * whatever theme the host application platform already configured, and one theme
15
16
  * change lands identically in all of them.
16
17
  */
17
18
  var provider_js_1 = require("./provider.js");
@@ -23,6 +24,7 @@ Object.defineProperty(exports, "useConnectPlans", { enumerable: true, get: funct
23
24
  Object.defineProperty(exports, "useConnectApps", { enumerable: true, get: function () { return provider_js_1.useConnectApps; } });
24
25
  Object.defineProperty(exports, "useConnectAddonUsage", { enumerable: true, get: function () { return provider_js_1.useConnectAddonUsage; } });
25
26
  Object.defineProperty(exports, "useConnectSessions", { enumerable: true, get: function () { return provider_js_1.useConnectSessions; } });
27
+ Object.defineProperty(exports, "useConnectIntegrations", { enumerable: true, get: function () { return provider_js_1.useConnectIntegrations; } });
26
28
  Object.defineProperty(exports, "useConnectResource", { enumerable: true, get: function () { return provider_js_1.useConnectResource; } });
27
29
  Object.defineProperty(exports, "CONNECT_APP_URL", { enumerable: true, get: function () { return provider_js_1.CONNECT_APP_URL; } });
28
30
  // Re-exported from the root entry rather than kept there alone: it is a pure
@@ -52,8 +54,31 @@ Object.defineProperty(exports, "ConnectSignOutButton", { enumerable: true, get:
52
54
  // `ConnectUserMenu` was the only thing that could render it.
53
55
  var account_sessions_js_1 = require("./components/account-sessions.js");
54
56
  Object.defineProperty(exports, "ConnectAccountSessions", { enumerable: true, get: function () { return account_sessions_js_1.ConnectAccountSessions; } });
57
+ // The frame the rest of these components live inside. Exported next to the
58
+ // pieces it composes rather than in a section of its own - an integration
59
+ // reaching for a sidebar wants to find the launcher and the account menu in
60
+ // the same breath, since the shell already mounts both.
61
+ var app_shell_js_1 = require("./components/app-shell.js");
62
+ Object.defineProperty(exports, "ConnectAppShell", { enumerable: true, get: function () { return app_shell_js_1.ConnectAppShell; } });
63
+ var side_nav_js_1 = require("./components/side-nav.js");
64
+ Object.defineProperty(exports, "ConnectSideNav", { enumerable: true, get: function () { return side_nav_js_1.ConnectSideNav; } });
65
+ var use_active_path_js_1 = require("./use-active-path.js");
66
+ Object.defineProperty(exports, "useActivePath", { enumerable: true, get: function () { return use_active_path_js_1.useActivePath; } });
67
+ // The nav model is plain data and pure functions - a host generating its nav
68
+ // from its own route table needs the types, and `resolveActivePath` is the
69
+ // same longest-prefix rule the sidebar highlights with, so a breadcrumb or a
70
+ // page title derived from it cannot disagree with the rail.
71
+ var nav_js_1 = require("./components/nav.js");
72
+ Object.defineProperty(exports, "normalizeNav", { enumerable: true, get: function () { return nav_js_1.normalizeNav; } });
73
+ Object.defineProperty(exports, "flattenNav", { enumerable: true, get: function () { return nav_js_1.flattenNav; } });
74
+ Object.defineProperty(exports, "normalizePath", { enumerable: true, get: function () { return nav_js_1.normalizePath; } });
75
+ Object.defineProperty(exports, "resolveActivePath", { enumerable: true, get: function () { return nav_js_1.resolveActivePath; } });
76
+ Object.defineProperty(exports, "isNavItemActive", { enumerable: true, get: function () { return nav_js_1.isNavItemActive; } });
77
+ Object.defineProperty(exports, "gateExemptPaths", { enumerable: true, get: function () { return nav_js_1.gateExemptPaths; } });
55
78
  var apps_grid_js_1 = require("./components/apps-grid.js");
56
79
  Object.defineProperty(exports, "ConnectAppsGrid", { enumerable: true, get: function () { return apps_grid_js_1.ConnectAppsGrid; } });
80
+ var integrations_modal_js_1 = require("./components/integrations-modal.js");
81
+ Object.defineProperty(exports, "ConnectIntegrationsModal", { enumerable: true, get: function () { return integrations_modal_js_1.ConnectIntegrationsModal; } });
57
82
  var plans_js_1 = require("./components/plans.js");
58
83
  Object.defineProperty(exports, "ConnectPlans", { enumerable: true, get: function () { return plans_js_1.ConnectPlans; } });
59
84
  var subscribe_dialog_js_1 = require("./components/subscribe-dialog.js");
@@ -73,6 +98,9 @@ Object.defineProperty(exports, "resolveSubscriptionState", { enumerable: true, g
73
98
  var readiness_js_1 = require("./components/readiness.js");
74
99
  Object.defineProperty(exports, "ConnectReadinessBanner", { enumerable: true, get: function () { return readiness_js_1.ConnectReadinessBanner; } });
75
100
  Object.defineProperty(exports, "ConnectReadinessGate", { enumerable: true, get: function () { return readiness_js_1.ConnectReadinessGate; } });
101
+ var access_gate_js_1 = require("./components/access-gate.js");
102
+ Object.defineProperty(exports, "ConnectAccessGate", { enumerable: true, get: function () { return access_gate_js_1.ConnectAccessGate; } });
103
+ Object.defineProperty(exports, "useConnectAccessGate", { enumerable: true, get: function () { return access_gate_js_1.useConnectAccessGate; } });
76
104
  var feature_gate_js_1 = require("./components/feature-gate.js");
77
105
  Object.defineProperty(exports, "ConnectFeatureGate", { enumerable: true, get: function () { return feature_gate_js_1.ConnectFeatureGate; } });
78
106
  Object.defineProperty(exports, "useConnectFeature", { enumerable: true, get: function () { return feature_gate_js_1.useConnectFeature; } });
@@ -7,7 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  * The Connect mark - concentric rings on a solid accent badge.
8
8
  *
9
9
  * Painted from `--accent` / `--accent-foreground`, the same HeroUI tokens the
10
- * host app themes, so the badge tracks whatever palette a service platform has
10
+ * host app themes, so the badge tracks whatever palette an application platform has
11
11
  * configured rather than carrying a hardcoded CosmoOps green into someone
12
12
  * else's brand.
13
13
  */
@@ -9,12 +9,15 @@ exports.useConnectEntitlements = useConnectEntitlements;
9
9
  exports.useConnectAddonUsage = useConnectAddonUsage;
10
10
  exports.useConnectPlans = useConnectPlans;
11
11
  exports.useConnectApps = useConnectApps;
12
+ exports.useConnectIntegrations = useConnectIntegrations;
12
13
  exports.useConnectSessions = useConnectSessions;
13
14
  exports.useConnectResource = useConnectResource;
14
15
  const jsx_runtime_1 = require("react/jsx-runtime");
15
16
  const react_1 = require("react");
16
17
  exports.CONNECT_APP_URL = "https://connect.cosmoops.com";
17
18
  const ConnectContext = (0, react_1.createContext)(null);
19
+ /** Marks that this tab has already spent its one silent attempt - see `ConnectProviderProps.silentSignIn`. */
20
+ const SILENT_ATTEMPT_KEY = "connect_silent_attempted";
18
21
  /**
19
22
  * Wraps the part of your app that shows Connect UI.
20
23
  *
@@ -29,14 +32,44 @@ const ConnectContext = (0, react_1.createContext)(null);
29
32
  * </ConnectProvider>
30
33
  * ```
31
34
  */
32
- function ConnectProvider({ clientId = "", basePath = "/api/connect", redirectUri, appUrl = exports.CONNECT_APP_URL, fetch: fetchImpl, children, }) {
35
+ function ConnectProvider({ clientId = "", basePath = "/api/connect", redirectUri, appUrl = exports.CONNECT_APP_URL, fetch: fetchImpl, silentSignIn = false, children, }) {
33
36
  // One state cell, not three. `isLoading` is *derived* from "no result yet"
34
37
  // rather than set - a `setIsLoading(true)` at the top of an effect is a
35
38
  // synchronous setState inside that effect, which cascades an extra render
36
39
  // and is what `react-hooks/set-state-in-effect` flags. Everything below
37
40
  // writes state only from an async callback.
38
41
  const [result, setResult] = (0, react_1.useState)(null);
39
- const isLoading = result === null;
42
+ /**
43
+ * Whether this tab has already spent its one silent attempt, sampled once
44
+ * at mount.
45
+ *
46
+ * A lazy `useState` initializer rather than a read inside the render body:
47
+ * `sessionStorage` is external mutable state, and reading it during render
48
+ * is impure - with the React Compiler on, that is exactly the kind of thing
49
+ * that can be hoisted or memoized into the wrong answer. An initializer
50
+ * runs once, which is all this needs; the value only ever changes as this
51
+ * component navigates the page away.
52
+ *
53
+ * Safe on the server, where it reads `false`: `result` is `null` during SSR
54
+ * and first client render alike, so `isLoading` below is `true` on both
55
+ * regardless of this value, and hydration cannot diverge on it.
56
+ */
57
+ const [silentAttemptSpent] = (0, react_1.useState)(() => typeof window !== "undefined" && window.sessionStorage.getItem(SILENT_ATTEMPT_KEY) === "1");
58
+ /**
59
+ * A silent attempt is settled-signed-out *and* about to redirect. Reported
60
+ * as still-loading, which is the honest answer: the question "is this
61
+ * visitor signed in" has not been finally answered yet, and there is a
62
+ * navigation already on its way.
63
+ *
64
+ * Without this the provider publishes `signed out` for the one frame before
65
+ * the effect below fires, so every consumer paints its signed-out state -
66
+ * a "Sign in" button, the plan-picker gate - and then the page vanishes
67
+ * underneath it. Flashing a sales pitch at someone who is about to be
68
+ * silently signed in is the same bug `ConnectSubscriptionGate` exists to
69
+ * prevent, one layer down.
70
+ */
71
+ const silentPending = silentSignIn && !silentAttemptSpent && result !== null && !result.session.user && !result.error;
72
+ const isLoading = result === null || silentPending;
40
73
  const session = result?.session ?? null;
41
74
  const error = result?.error ?? null;
42
75
  // One entry per path. Held in a ref, not state, because a cache write must
@@ -102,7 +135,7 @@ function ConnectProvider({ clientId = "", basePath = "/api/connect", redirectUri
102
135
  // error, so a Login button still renders rather than the UI hanging on
103
136
  // skeletons forever.
104
137
  setResult({
105
- session: { user: null, subscription: null, serviceRole: null },
138
+ session: { user: null, subscription: null, applicationRole: null },
106
139
  error: cause instanceof Error ? cause.message : "Could not reach Connect",
107
140
  });
108
141
  }
@@ -116,6 +149,21 @@ function ConnectProvider({ clientId = "", basePath = "/api/connect", redirectUri
116
149
  // eslint-disable-next-line react-hooks/set-state-in-effect
117
150
  void refresh();
118
151
  }, [refresh]);
152
+ (0, react_1.useEffect)(() => {
153
+ // `silentPending` already encodes settled-and-signed-out, no prior
154
+ // attempt, and no bridge error - a failed bridge read says nothing about
155
+ // whether Connect has a session, and bouncing the browser on a transient
156
+ // network failure would turn a recoverable error into a navigation nobody
157
+ // asked for.
158
+ if (!silentPending || typeof window === "undefined")
159
+ return;
160
+ // Written before navigating, not after: a navigation that begins before
161
+ // the marker lands leaves the tab unmarked and free to probe again on the
162
+ // way back, which is the loop this bounds.
163
+ window.sessionStorage.setItem(SILENT_ATTEMPT_KEY, "1");
164
+ const next = `${window.location.pathname}${window.location.search}`;
165
+ window.location.href = `${basePath}/signin?prompt=none&next=${encodeURIComponent(next)}`;
166
+ }, [silentPending, basePath]);
119
167
  const signIn = (0, react_1.useCallback)((options = {}) => {
120
168
  const url = new URL("/sso/authorize", appUrl);
121
169
  url.searchParams.set("client_id", clientId);
@@ -136,7 +184,7 @@ function ConnectProvider({ clientId = "", basePath = "/api/connect", redirectUri
136
184
  credentials: "same-origin",
137
185
  }).catch(() => { });
138
186
  cache.current.clear();
139
- setResult({ session: { user: null, subscription: null, serviceRole: null }, error: null });
187
+ setResult({ session: { user: null, subscription: null, applicationRole: null }, error: null });
140
188
  }, [basePath, fetchImpl]);
141
189
  const value = (0, react_1.useMemo)(() => ({
142
190
  clientId,
@@ -183,24 +231,28 @@ function useConnectEntitlements() {
183
231
  /**
184
232
  * What is left of every consumable add-on on this workspace's subscription.
185
233
  *
186
- * The read half of `client.billing.consumeAddon` - a service that meters its
234
+ * The read half of `client.billing.consumeAddon` - an application that meters its
187
235
  * own end users had no way to show them what remained until this existed.
188
236
  * Empty `addons` means there is nothing to meter, which is not an error.
189
237
  */
190
238
  function useConnectAddonUsage() {
191
239
  return useConnectResource("/addons");
192
240
  }
193
- /** This service's plan catalog. */
241
+ /** This application's plan catalog. */
194
242
  function useConnectPlans() {
195
243
  return useConnectResource("/plans");
196
244
  }
197
- /** The apps this organization can open. */
245
+ /** The apps this workspace can open. */
198
246
  function useConnectApps() {
199
247
  return useConnectResource("/apps");
200
248
  }
249
+ /** Enabled Composio-hosted toolkits and this workspace's connection status for each. */
250
+ function useConnectIntegrations() {
251
+ return useConnectResource("/integrations");
252
+ }
201
253
  /**
202
254
  * This Connect account's own login sessions - account-wide, not scoped to
203
- * this service, see `SessionsListResponse`'s doc comment. The component
255
+ * this application, see `SessionsListResponse`'s doc comment. The component
204
256
  * that calls this is also the one that must own any revoke actions and call
205
257
  * the returned `refetch()` afterward - see `mutateResource`'s doc comment
206
258
  * for why nothing does that automatically.
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.useActivePath = useActivePath;
5
+ const react_1 = require("react");
6
+ const LOCATION_CHANGE = "connect:locationchange";
7
+ let patched = false;
8
+ /**
9
+ * Make `history.pushState`/`replaceState` announce themselves.
10
+ *
11
+ * Neither fires an event - `popstate` covers Back and Forward only - so a
12
+ * client-side router that pushes a new URL moves the address bar and tells
13
+ * nothing outside itself. Without this, a sidebar reading `location.pathname`
14
+ * highlights whatever route the page first loaded on and then never moves
15
+ * again.
16
+ *
17
+ * Both wrappers call through to the original first and return its result, so
18
+ * the patch is transparent to whatever router installed the navigation; it is
19
+ * applied at most once per document, and only when a `ConnectSideNav` is
20
+ * actually mounted without an `activePath`. Pass `activePath` (Next:
21
+ * `usePathname()`) and none of this runs at all - which is the recommended
22
+ * path for any app that has a router to ask.
23
+ */
24
+ function patchHistory() {
25
+ if (patched || typeof window === "undefined" || typeof window.history === "undefined")
26
+ return;
27
+ patched = true;
28
+ for (const method of ["pushState", "replaceState"]) {
29
+ const original = window.history[method];
30
+ window.history[method] = function patchedHistoryMethod(...args) {
31
+ const result = original.apply(this, args);
32
+ window.dispatchEvent(new Event(LOCATION_CHANGE));
33
+ return result;
34
+ };
35
+ }
36
+ }
37
+ function subscribe(onStoreChange) {
38
+ patchHistory();
39
+ window.addEventListener("popstate", onStoreChange);
40
+ window.addEventListener(LOCATION_CHANGE, onStoreChange);
41
+ return () => {
42
+ window.removeEventListener("popstate", onStoreChange);
43
+ window.removeEventListener(LOCATION_CHANGE, onStoreChange);
44
+ };
45
+ }
46
+ function getSnapshot() {
47
+ return window.location.pathname;
48
+ }
49
+ /**
50
+ * The server never has a location, and guessing one is worse than having
51
+ * none: a guess that misses renders a *different* item highlighted on the
52
+ * server than on the client, which is a hydration mismatch. `""` matches no
53
+ * nav item, so the first paint has no highlight and the client's first commit
54
+ * adds it.
55
+ *
56
+ * Doubles as the snapshot when `activePath` *was* supplied - the value is
57
+ * discarded either way, and pairing it with `noopSubscribe` is what keeps a
58
+ * controlled shell from ever touching `history` or `window.location`.
59
+ */
60
+ function emptySnapshot() {
61
+ return "";
62
+ }
63
+ function noopSubscribe() {
64
+ return () => { };
65
+ }
66
+ /**
67
+ * The current path, from `activePath` when given and from the browser
68
+ * otherwise.
69
+ *
70
+ * Exported because a host building its own shell out of `ConnectSideNav`
71
+ * needs the same fallback the shell uses.
72
+ */
73
+ function useActivePath(activePath) {
74
+ const controlled = activePath !== undefined;
75
+ const live = (0, react_1.useSyncExternalStore)(controlled ? noopSubscribe : subscribe, controlled ? emptySnapshot : getSnapshot, emptySnapshot);
76
+ return activePath ?? live;
77
+ }
@@ -157,15 +157,15 @@ function buildCancelUrl({ appUrl, subscriptionId }) {
157
157
  * The Connect URL that completes a subscribe or a plan change.
158
158
  *
159
159
  * Both land on a modal Connect renders over the relevant page (an intercepted
160
- * parallel route - `app/@modal/(.)services/...` and
160
+ * parallel route - `app/@modal/(.)applications/...` and
161
161
  * `app/@modal/(.)billing/...`), so the customer arrives already looking at the
162
162
  * confirmation for the plan they picked here, not at a catalogue they have to
163
163
  * find it in again.
164
164
  */
165
- function buildCheckoutUrl({ appUrl, serviceId, planId, subscriptionId, coupon, }) {
165
+ function buildCheckoutUrl({ appUrl, applicationId, planId, subscriptionId, coupon, }) {
166
166
  const path = subscriptionId
167
167
  ? `/billing/subscriptions/${encodeURIComponent(subscriptionId)}/change-plan/${encodeURIComponent(planId)}`
168
- : `/services/${encodeURIComponent(serviceId)}/subscribe/${encodeURIComponent(planId)}`;
168
+ : `/applications/${encodeURIComponent(applicationId)}/subscribe/${encodeURIComponent(planId)}`;
169
169
  const url = new URL(path, appUrl);
170
170
  if (coupon?.trim())
171
171
  url.searchParams.set("coupon", coupon.trim());
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectError = void 0;
4
4
  exports.isConnectError = isConnectError;
5
5
  /**
6
- * Every failure the Connect service API can report, as one class.
6
+ * Every failure the Connect application API can report, as one class.
7
7
  *
8
8
  * The API speaks two error dialects and this collapses them: the
9
- * `/api/v1/service/*` endpoints use `{ error: { code, message } }`, while the
9
+ * `/api/v1/application/*` endpoints use `{ error: { code, message } }`, while the
10
10
  * three `/sso/*` ones use OAuth2's `{ error, error_description }` (RFC 6749
11
11
  * §5.2). Both land here with `code` populated, so integrating code branches on
12
12
  * one field either way.
@@ -45,7 +45,7 @@ class ConnectError extends Error {
45
45
  * Whether the user's Connect sign-in is over and they need to go back
46
46
  * through `identity.authorizeUrl()`. Collapses every session-side failure,
47
47
  * matching how the API reports them - unknown, expired, revoked, or issued
48
- * to a different service all arrive as one `invalid_token`.
48
+ * to a different application all arrive as one `invalid_token`.
49
49
  */
50
50
  get isSessionExpired() {
51
51
  return this.code === "invalid_token";
@@ -59,7 +59,7 @@ class ConnectError extends Error {
59
59
  * guessing keys. These two are the exception: reaching them required
60
60
  * presenting a key whose hash matched, so you already hold it, and what you
61
61
  * need to know is that no retry and no scope change will fix this - the key
62
- * has to be reissued from the service's Setup tab.
62
+ * has to be reissued from the application's Integration tab.
63
63
  *
64
64
  * Worth alerting on. It is the one failure that will not resolve itself and
65
65
  * takes down every call your integration makes.
@@ -16,7 +16,7 @@ function blockingBlockers(readiness) {
16
16
  * Coerces either wire shape into `PlanFeature[]`.
17
17
  *
18
18
  * A bare key becomes `{ key, name: key, description: "" }` - the same
19
- * fallback the server applies for a plan older than its service's feature
19
+ * fallback the server applies for a plan older than its application's feature
20
20
  * catalog, so an un-upgraded Connect renders exactly what it always did
21
21
  * rather than a column of blanks.
22
22
  *
package/dist/cjs/index.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizePlanFeatures = exports.blockingBlockers = exports.isConnectError = exports.ConnectError = void 0;
4
4
  /**
5
5
  * `@cosmoops/connect-sdk` - the official client for the CosmoOps Connect
6
- * **service-partner API** (`/api/v1/service/*`).
6
+ * **application-partner API** (`/api/v1/application/*`).
7
7
  *
8
8
  * This root entry is the isomorphic surface: wire types, the error class, and
9
9
  * pure helpers. It pulls in neither Node-only code nor React, so it is safe to
@@ -41,16 +41,22 @@ exports.createConnectHandlers = createConnectHandlers;
41
41
  exports.GET = handle;
42
42
  exports.POST = handle;
43
43
  exports.getConnectSession = getConnectSession;
44
+ exports.requireConnectSession = requireConnectSession;
45
+ const react_1 = require("react");
44
46
  const client_js_1 = require("../server/client.js");
45
47
  const handler_js_1 = require("../server/handler.js");
46
48
  /**
47
49
  * Plug-and-play wiring for Next.js.
48
50
  *
49
51
  * Everything below is server-side. Nothing here is *statically* imported from
50
- * `next`, so this module loads in App Router route handlers, the Node runtime
51
- * and the edge runtime alike - and needs no peer dependency. The one place
52
- * that does reach for `next/headers` does it behind a dynamic import, inside
53
- * the function that needs it, and says so when it isn't there.
52
+ * `next` itself, so this module loads in App Router route handlers, the Node
53
+ * runtime and the edge runtime alike without a `next` peer dependency. The
54
+ * one place that does reach for `next/headers` (or, in `requireConnectSession`,
55
+ * `next/navigation`) does it behind a dynamic import, inside the function that
56
+ * needs it, and says so when it isn't there. `react` is a real, static
57
+ * dependency though - `getConnectSession`'s automatic per-request memoization
58
+ * (below) is built on its `cache()`, which every App Router project already
59
+ * has.
54
60
  *
55
61
  * The whole integration is two files:
56
62
  *
@@ -76,7 +82,7 @@ const handler_js_1 = require("../server/handler.js");
76
82
  * | `CONNECT_SESSION_COOKIE` | no | session cookie name |
77
83
  *
78
84
  * Register `{your origin}/api/connect/callback` as a redirect URI in the
79
- * service's Setup tab. That is the only step this cannot do for you.
85
+ * application's Integration tab. That is the only step this cannot do for you.
80
86
  *
81
87
  * No `next.config.ts` changes needed either, including `transpilePackages`:
82
88
  * `dist/` is already ES2022 with `"use client"` hoisted to the top of every
@@ -227,6 +233,35 @@ async function requestFromHeaders() {
227
233
  const proto = incoming.get("x-forwarded-proto") ?? (host.startsWith("localhost") ? "http" : "https");
228
234
  return new Request(`${proto}://${host}/`, { headers: incoming });
229
235
  }
236
+ async function readSession(request, options) {
237
+ const store = options.session ?? cookieSessionStore();
238
+ const token = await store.read(request);
239
+ if (!token)
240
+ return null;
241
+ try {
242
+ return await (options.client ?? connectClient()).identity.getUser(token);
243
+ }
244
+ catch {
245
+ return null;
246
+ }
247
+ }
248
+ /**
249
+ * The ambient case - no explicit `request`, default client, default cookie
250
+ * store - wrapped in React's `cache()` so every call in the same request
251
+ * shares one round trip to Connect. A layout reading the session for a nav
252
+ * avatar and the page beneath it reading it again for a role check hit this
253
+ * exact function; without the wrapper that's two live reads of the same
254
+ * answer.
255
+ *
256
+ * Deliberately its own zero-argument function rather than `cache()` wrapping
257
+ * `getConnectSession` directly - `cache()` keys on argument identity, and
258
+ * `getConnectSession`'s default `options = {}` is a fresh object on every
259
+ * call, so wrapping it as-is would never hit the cache for the very call
260
+ * pattern this exists to speed up.
261
+ */
262
+ const cachedAmbientSession = (0, react_1.cache)(async () => {
263
+ return readSession(await requestFromHeaders(), {});
264
+ });
230
265
  /**
231
266
  * The signed-in user for a Server Component, Route Handler or Server Action,
232
267
  * or `null`.
@@ -237,6 +272,9 @@ async function requestFromHeaders() {
237
272
  * if (!session) redirect("/api/connect/signin?next=/dashboard");
238
273
  * ```
239
274
  *
275
+ * `requireConnectSession()` below is that exact check in one call, for the
276
+ * common case of "redirect if signed out."
277
+ *
240
278
  * Pass a `Request` to use it somewhere `next/headers` does not exist - a
241
279
  * framework-agnostic handler, a test, a custom server. Both forms take the
242
280
  * same path afterwards; the argument is only how the cookie is found.
@@ -248,18 +286,61 @@ async function requestFromHeaders() {
248
286
  *
249
287
  * Returns `null` for every failure - an expired session and a missing one both
250
288
  * mean "not signed in", and a page guard should not need a try/catch.
289
+ *
290
+ * **Memoized per request for the plain, zero-argument call** - the shape
291
+ * every page, layout and Server Action actually uses - via React's `cache()`.
292
+ * Passing an explicit `request`, `client` or `session` bypasses the cache
293
+ * and always makes a live call: those are for tests and non-ambient
294
+ * callers, where a fresh read every time is the point, not an oversight.
251
295
  */
252
296
  async function getConnectSession(request, options = {}) {
253
- const store = options.session ?? cookieSessionStore();
254
- const token = await store.read(request ?? (await requestFromHeaders()));
255
- if (!token)
256
- return null;
257
- try {
258
- return await (options.client ?? connectClient()).identity.getUser(token);
259
- }
260
- catch {
261
- return null;
262
- }
297
+ if (!request && !options.client && !options.session)
298
+ return cachedAmbientSession();
299
+ return readSession(request ?? (await requestFromHeaders()), options);
300
+ }
301
+ /**
302
+ * `getConnectSession()`, minus the boilerplate of checking it and redirecting
303
+ * yourself:
304
+ *
305
+ * ```tsx
306
+ * // app/dashboard/page.tsx
307
+ * const session = await requireConnectSession();
308
+ * ```
309
+ *
310
+ * Redirects to `/signin` under the same `basePath` as `getConnectSession`'s
311
+ * default (`/api/connect`) when signed out - pass `signInPath` if you mounted
312
+ * the bridge somewhere else, and `next` to send the visitor back to where
313
+ * they were once they're signed in. Only sensible in a Server Component,
314
+ * Route Handler or Server Action, the same three places `redirect()` from
315
+ * `next/navigation` itself works.
316
+ *
317
+ * A thin wrapper, not a framework of its own - reach for `getConnectSession`
318
+ * plus your own check when "not signed in" isn't "redirect to sign-in"
319
+ * (an API route returning 401, say). Role and entitlement checks stay
320
+ * outside this function on purpose: `session.applicationRole.granted` and
321
+ * `connect.entitlements.*` are one line each on top of the session this
322
+ * already gives you, and where that line redirects (a plans page, a
323
+ * "forbidden" screen) is a decision only your app can make - see
324
+ * "Authentication & authorization" for the pattern.
325
+ */
326
+ async function requireConnectSession(request, options = {}) {
327
+ const session = await getConnectSession(request, options);
328
+ if (session)
329
+ return session;
330
+ const signIn = options.signInPath ?? "/api/connect/signin";
331
+ return redirectTo(options.next ? `${signIn}?next=${encodeURIComponent(options.next)}` : signIn);
332
+ }
333
+ /**
334
+ * `next/navigation`'s `redirect()`, imported the same dynamic way as
335
+ * `next/headers` above and for the same reason: no static dependency on
336
+ * `next`'s own types at this package's build time. Typed by hand rather than
337
+ * imported, since that's the only way to erase them - `redirect()` itself is
338
+ * typed `never`, which is what lets `requireConnectSession` return here
339
+ * without its own `return` after the call.
340
+ */
341
+ async function redirectTo(url) {
342
+ const { redirect } = (await Promise.resolve().then(() => __importStar(require("next/navigation"))));
343
+ return redirect(url);
263
344
  }
264
345
  var handler_js_2 = require("../server/handler.js");
265
346
  Object.defineProperty(exports, "createConnectHandler", { enumerable: true, get: function () { return handler_js_2.createConnectHandler; } });
@@ -8,11 +8,13 @@ const billing_js_1 = require("./resources/billing.js");
8
8
  const drive_js_1 = require("./resources/drive.js");
9
9
  const entitlements_js_1 = require("./resources/entitlements.js");
10
10
  const identity_js_1 = require("./resources/identity.js");
11
+ const integrations_js_1 = require("./resources/integrations.js");
11
12
  const logs_js_1 = require("./resources/logs.js");
13
+ const analytics_js_1 = require("./resources/analytics.js");
12
14
  const otp_js_1 = require("./resources/otp.js");
13
15
  const sessions_js_1 = require("./resources/sessions.js");
14
16
  exports.DEFAULT_BASE_URL = "https://connect.cosmoops.com";
15
- const SERVICE_API_PREFIX = "/api/v1/service";
17
+ const APPLICATION_API_PREFIX = "/api/v1/application";
16
18
  /**
17
19
  * Refuses to build a client in a browser.
18
20
  *
@@ -41,9 +43,9 @@ function assertNotBrowser() {
41
43
  "in browser code instead; it talks to your own origin and holds no credential.");
42
44
  }
43
45
  /**
44
- * The CosmoOps Connect service-partner API.
46
+ * The CosmoOps Connect application-partner API.
45
47
  *
46
- * Covers `/api/v1/service/*` and nothing else - the surface a service platform
48
+ * Covers `/api/v1/application/*` and nothing else - the surface an application platform
47
49
  * integrates against. Connect's internal API is not part of this package and
48
50
  * never will be.
49
51
  *
@@ -54,7 +56,7 @@ function assertNotBrowser() {
54
56
  * });
55
57
  *
56
58
  * const session = await connect.identity.exchangeCode({ code, redirectUri });
57
- * const { ready } = await connect.entitlements.getReadiness(session.user.organizationId);
59
+ * const { ready } = await connect.entitlements.getReadiness(session.user.workspaceId);
58
60
  * ```
59
61
  *
60
62
  * Every failure throws a `ConnectError` carrying a stable `code`. Nothing here
@@ -63,32 +65,36 @@ function assertNotBrowser() {
63
65
  class ConnectClient {
64
66
  /** "Login with Connect" - the OAuth2 handshake and the session after it. */
65
67
  identity;
66
- /** What a signed-in member may do on your service. */
68
+ /** What a signed-in member may do on your application. */
67
69
  access;
68
- /** The services an organization can open - for a launcher grid. */
70
+ /** The applications a workspace can open - for a launcher grid. */
69
71
  apps;
70
- /** Whether a workspace may use your service, and on what plan. */
72
+ /** Whether a workspace may use your application, and on what plan. */
71
73
  entitlements;
72
74
  /** Your plan catalog, and metering usage against add-on balances. */
73
75
  billing;
74
- /** Files in an organization's Drive. */
76
+ /** Files in a workspace's Drive. */
75
77
  drive;
76
78
  /** RAG chat and structured extraction. */
77
79
  ai;
78
80
  /** WhatsApp/SMS phone verification. */
79
81
  otp;
80
- /** Feature events into the Console's audit log, filterable to your service. */
82
+ /** Feature events into the Console's audit log, filterable to your application. */
81
83
  logs;
82
- /** This Connect account's own login sessions - account-wide, not scoped to this service. Requires the `sessions:manage` scope. */
84
+ /** Emit product events and read back this application's aggregated numbers. */
85
+ analytics;
86
+ /** This Connect account's own login sessions - account-wide, not scoped to this application. Requires the `sessions:manage` scope. */
83
87
  sessions;
88
+ /** Composio-hosted third-party connections (Google Calendar, Slack, ...) a workspace has connected. */
89
+ integrations;
84
90
  constructor(options) {
85
91
  assertNotBrowser();
86
92
  if (!options.apiKey) {
87
- throw new TypeError("ConnectClient requires `apiKey` - the plaintext shown once in the Setup tab.");
93
+ throw new TypeError("ConnectClient requires `apiKey` - the plaintext shown once in the Integration tab.");
88
94
  }
89
95
  const appBaseUrl = (options.baseUrl ?? exports.DEFAULT_BASE_URL).replace(/\/+$/, "");
90
96
  const transport = {
91
- baseUrl: `${appBaseUrl}${SERVICE_API_PREFIX}`,
97
+ baseUrl: `${appBaseUrl}${APPLICATION_API_PREFIX}`,
92
98
  apiKey: options.apiKey,
93
99
  timeoutMs: options.timeoutMs ?? 20_000,
94
100
  maxRetries: options.maxRetries ?? 2,
@@ -96,7 +102,7 @@ class ConnectClient {
96
102
  userAgent: `cosmoops-connect-sdk/${exports.SDK_VERSION}`,
97
103
  };
98
104
  // `authorizeUrl` points at the app, not the API prefix - it is a browser
99
- // redirect, not a service call, so identity takes both.
105
+ // redirect, not an application call, so identity takes both.
100
106
  this.identity = new identity_js_1.IdentityResource(transport, options.clientId ?? null, appBaseUrl);
101
107
  this.access = new access_js_1.AccessResource(transport);
102
108
  this.apps = new apps_js_1.AppsResource(transport);
@@ -106,9 +112,11 @@ class ConnectClient {
106
112
  this.ai = new ai_js_1.AiResource(transport);
107
113
  this.otp = new otp_js_1.OtpResource(transport);
108
114
  this.logs = new logs_js_1.LogsResource(transport);
115
+ this.analytics = new analytics_js_1.AnalyticsResource(transport);
109
116
  this.sessions = new sessions_js_1.SessionsResource(transport, options.clientId ?? null);
117
+ this.integrations = new integrations_js_1.IntegrationsResource(transport);
110
118
  }
111
119
  }
112
120
  exports.ConnectClient = ConnectClient;
113
121
  /** Kept in step with `package.json` by `sdk.test.ts`. */
114
- exports.SDK_VERSION = "0.3.0";
122
+ exports.SDK_VERSION = "0.7.0";