@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.
- 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 +24 -22
- package/dist/cjs/client/components/app-shell.js +157 -0
- package/dist/cjs/client/components/apps-grid.js +4 -3
- package/dist/cjs/client/components/feature-gate.js +2 -2
- package/dist/cjs/client/components/integrations-modal.js +95 -0
- package/dist/cjs/client/components/logo.js +3 -3
- package/dist/cjs/client/components/motion.js +27 -0
- package/dist/cjs/client/components/nav.js +97 -0
- package/dist/cjs/client/components/plan-pricing.js +1 -1
- package/dist/cjs/client/components/plans.js +42 -40
- package/dist/cjs/client/components/readiness.js +6 -4
- package/dist/cjs/client/components/side-nav.js +58 -0
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +3 -2
- package/dist/cjs/client/components/surfaces.js +54 -0
- package/dist/cjs/client/components/usage-meter.js +4 -3
- package/dist/cjs/client/components/user-menu.js +33 -6
- package/dist/cjs/client/index.js +30 -2
- package/dist/cjs/client/mark.js +1 -1
- package/dist/cjs/client/provider.js +60 -8
- package/dist/cjs/client/use-active-path.js +77 -0
- package/dist/cjs/client/use-checkout.js +3 -3
- package/dist/cjs/errors.js +4 -4
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/next/index.js +96 -15
- package/dist/cjs/server/client.js +22 -14
- package/dist/cjs/server/handler.js +99 -16
- package/dist/cjs/server/http.js +1 -1
- package/dist/cjs/server/index.js +8 -1
- package/dist/cjs/server/resources/access.js +3 -3
- package/dist/cjs/server/resources/ai.js +1 -1
- package/dist/cjs/server/resources/analytics.js +66 -0
- package/dist/cjs/server/resources/apps.js +4 -4
- package/dist/cjs/server/resources/billing.js +5 -5
- package/dist/cjs/server/resources/drive.js +4 -4
- package/dist/cjs/server/resources/entitlements.js +7 -7
- package/dist/cjs/server/resources/identity.js +4 -4
- package/dist/cjs/server/resources/integrations.js +66 -0
- package/dist/cjs/server/resources/logs.js +3 -3
- package/dist/cjs/server/resources/sessions.js +6 -6
- package/dist/cjs/server/webhooks.js +127 -0
- package/dist/cjs/types.js +1 -1
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +158 -0
- package/dist/esm/client/components/account-sessions.js +24 -22
- package/dist/esm/client/components/app-shell.js +154 -0
- package/dist/esm/client/components/apps-grid.js +4 -3
- package/dist/esm/client/components/feature-gate.js +2 -2
- package/dist/esm/client/components/integrations-modal.js +92 -0
- package/dist/esm/client/components/logo.js +3 -3
- package/dist/esm/client/components/motion.js +21 -0
- package/dist/esm/client/components/nav.js +89 -0
- package/dist/esm/client/components/plan-pricing.js +1 -1
- package/dist/esm/client/components/plans.js +42 -40
- package/dist/esm/client/components/readiness.js +6 -4
- package/dist/esm/client/components/side-nav.js +53 -0
- package/dist/esm/client/components/subscribe-dialog.js +2 -2
- package/dist/esm/client/components/subscription.js +3 -2
- package/dist/esm/client/components/surfaces.js +51 -0
- package/dist/esm/client/components/usage-meter.js +4 -3
- package/dist/esm/client/components/user-menu.js +34 -7
- package/dist/esm/client/index.js +16 -2
- package/dist/esm/client/mark.js +1 -1
- package/dist/esm/client/provider.js +59 -8
- package/dist/esm/client/use-active-path.js +74 -0
- package/dist/esm/client/use-checkout.js +3 -3
- package/dist/esm/errors.js +4 -4
- package/dist/esm/helpers.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/next/index.js +95 -15
- package/dist/esm/server/client.js +22 -14
- package/dist/esm/server/handler.js +99 -16
- package/dist/esm/server/http.js +1 -1
- package/dist/esm/server/index.js +5 -0
- package/dist/esm/server/resources/access.js +3 -3
- package/dist/esm/server/resources/ai.js +1 -1
- package/dist/esm/server/resources/analytics.js +62 -0
- package/dist/esm/server/resources/apps.js +4 -4
- package/dist/esm/server/resources/billing.js +5 -5
- package/dist/esm/server/resources/drive.js +4 -4
- package/dist/esm/server/resources/entitlements.js +7 -7
- package/dist/esm/server/resources/identity.js +4 -4
- package/dist/esm/server/resources/integrations.js +62 -0
- package/dist/esm/server/resources/logs.js +3 -3
- package/dist/esm/server/resources/sessions.js +6 -6
- package/dist/esm/server/webhooks.js +123 -0
- package/dist/esm/types.js +1 -1
- package/dist/theme.css +28 -0
- package/dist/types/client/components/access-gate.d.ts +79 -0
- package/dist/types/client/components/account-sessions.d.ts +1 -1
- package/dist/types/client/components/app-shell.d.ts +115 -0
- package/dist/types/client/components/apps-grid.d.ts +1 -1
- package/dist/types/client/components/feature-gate.d.ts +1 -1
- package/dist/types/client/components/integrations-modal.d.ts +21 -0
- package/dist/types/client/components/logo.d.ts +5 -5
- 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/plan-pricing.d.ts +2 -2
- package/dist/types/client/components/readiness.d.ts +2 -2
- package/dist/types/client/components/side-nav.d.ts +31 -0
- package/dist/types/client/components/subscribe-dialog.d.ts +3 -3
- package/dist/types/client/components/surfaces.d.ts +51 -0
- package/dist/types/client/components/usage-meter.d.ts +1 -1
- package/dist/types/client/components/user-menu.d.ts +2 -2
- package/dist/types/client/index.d.ts +13 -2
- package/dist/types/client/mark.d.ts +1 -1
- package/dist/types/client/provider.d.ts +44 -9
- package/dist/types/client/use-active-path.d.ts +8 -0
- package/dist/types/client/use-checkout.d.ts +3 -3
- package/dist/types/errors.d.ts +4 -4
- package/dist/types/helpers.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/next/index.d.ts +40 -0
- package/dist/types/server/client.d.ts +17 -11
- package/dist/types/server/handler.d.ts +2 -2
- package/dist/types/server/index.d.ts +7 -0
- package/dist/types/server/resources/access.d.ts +3 -3
- package/dist/types/server/resources/ai.d.ts +2 -2
- package/dist/types/server/resources/analytics.d.ts +31 -0
- package/dist/types/server/resources/apps.d.ts +4 -4
- package/dist/types/server/resources/billing.d.ts +4 -4
- package/dist/types/server/resources/drive.d.ts +4 -4
- package/dist/types/server/resources/entitlements.d.ts +4 -4
- package/dist/types/server/resources/identity.d.ts +14 -4
- package/dist/types/server/resources/integrations.d.ts +40 -0
- package/dist/types/server/resources/logs.d.ts +3 -3
- package/dist/types/server/resources/sessions.d.ts +5 -5
- package/dist/types/server/webhooks.d.ts +67 -0
- package/dist/types/types.d.ts +220 -39
- package/package.json +10 -4
|
@@ -94,9 +94,9 @@ function redirect(location, cookies = []) {
|
|
|
94
94
|
* export { handler as GET, handler as POST };
|
|
95
95
|
* ```
|
|
96
96
|
*
|
|
97
|
-
* Every route resolves the
|
|
97
|
+
* Every route resolves the workspace from the caller's *own* session, never
|
|
98
98
|
* from a query parameter. A browser must not be able to ask about an
|
|
99
|
-
*
|
|
99
|
+
* workspace it is not signed into, and a client-supplied `workspaceId`
|
|
100
100
|
* is exactly that hole.
|
|
101
101
|
*/
|
|
102
102
|
function createConnectHandler(options) {
|
|
@@ -104,6 +104,7 @@ function createConnectHandler(options) {
|
|
|
104
104
|
const { client, session } = options;
|
|
105
105
|
const defaultRedirect = options.defaultRedirect ?? "/";
|
|
106
106
|
const stateCookieName = options.stateCookieName ?? "connect_oauth_state";
|
|
107
|
+
const silentCookieName = `${stateCookieName}_silent`;
|
|
107
108
|
const callbackPath = `${basePath}/callback`;
|
|
108
109
|
/**
|
|
109
110
|
* Short-lived, httpOnly, and `SameSite=Lax` - `Strict` would not be sent on
|
|
@@ -124,6 +125,32 @@ function createConnectHandler(options) {
|
|
|
124
125
|
.filter(Boolean)
|
|
125
126
|
.join("; ");
|
|
126
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Marks the in-flight round trip as a silent (`prompt=none`) one, so the
|
|
130
|
+
* callback can tell a *probe that found nobody* from a *sign-in that
|
|
131
|
+
* failed*. The two arrive as the identical `error=login_required`, and they
|
|
132
|
+
* need opposite handling - see the callback.
|
|
133
|
+
*
|
|
134
|
+
* A cookie of its own rather than a flag folded into the state cookie:
|
|
135
|
+
* that one is parsed by splitting on `:` with the destination URL rejoined
|
|
136
|
+
* from the remainder, so smuggling a third field through it is exactly the
|
|
137
|
+
* kind of encoding trick that breaks the first time a `next` URL contains
|
|
138
|
+
* something unexpected. Same attributes and lifetime as the state cookie
|
|
139
|
+
* it travels beside.
|
|
140
|
+
*/
|
|
141
|
+
function silentCookie(value, secure) {
|
|
142
|
+
const cleared = value === "";
|
|
143
|
+
return [
|
|
144
|
+
`${silentCookieName}=${encodeURIComponent(value)}`,
|
|
145
|
+
`Path=${basePath}`,
|
|
146
|
+
"HttpOnly",
|
|
147
|
+
"SameSite=Lax",
|
|
148
|
+
secure ? "Secure" : "",
|
|
149
|
+
cleared ? "Max-Age=0" : "Max-Age=900",
|
|
150
|
+
]
|
|
151
|
+
.filter(Boolean)
|
|
152
|
+
.join("; ");
|
|
153
|
+
}
|
|
127
154
|
async function requireToken(request) {
|
|
128
155
|
const token = await session.read(request);
|
|
129
156
|
if (!token) {
|
|
@@ -136,7 +163,7 @@ function createConnectHandler(options) {
|
|
|
136
163
|
}
|
|
137
164
|
return token;
|
|
138
165
|
}
|
|
139
|
-
/** Signed-in identity plus the
|
|
166
|
+
/** Signed-in identity plus the workspace it belongs to - the first read every other route needs. */
|
|
140
167
|
async function whoami(request) {
|
|
141
168
|
return client.identity.getUser(await requireToken(request));
|
|
142
169
|
}
|
|
@@ -146,30 +173,61 @@ function createConnectHandler(options) {
|
|
|
146
173
|
// Signed out is a normal state for a page rendering a Login button, not
|
|
147
174
|
// an error - `user: null` rather than a 401 the client must catch.
|
|
148
175
|
if (!token)
|
|
149
|
-
return json({ user: null, subscription: null,
|
|
176
|
+
return json({ user: null, subscription: null, applicationRole: null });
|
|
150
177
|
return json(await client.identity.getUser(token));
|
|
151
178
|
},
|
|
152
179
|
"GET /entitlements": async (request) => {
|
|
153
180
|
const { user } = await whoami(request);
|
|
154
181
|
const [readiness, subscription] = await Promise.all([
|
|
155
|
-
client.entitlements.getReadiness(user.
|
|
156
|
-
client.entitlements.getSubscription(user.
|
|
182
|
+
client.entitlements.getReadiness(user.workspaceId),
|
|
183
|
+
client.entitlements.getSubscription(user.workspaceId),
|
|
157
184
|
]);
|
|
158
185
|
return json({ readiness, subscription });
|
|
159
186
|
},
|
|
160
187
|
"GET /plans": async () => json(await client.billing.listPlans()),
|
|
161
|
-
//
|
|
188
|
+
// Workspace comes from the caller's own session, same as every other
|
|
162
189
|
// route here - a browser must not be able to ask what another workspace
|
|
163
190
|
// has left.
|
|
164
191
|
"GET /addons": async (request) => {
|
|
165
192
|
const { user } = await whoami(request);
|
|
166
|
-
return json(await client.billing.getAddonUsage(user.
|
|
193
|
+
return json(await client.billing.getAddonUsage(user.workspaceId));
|
|
167
194
|
},
|
|
168
195
|
"GET /apps": async (request) => {
|
|
169
196
|
const { user } = await whoami(request);
|
|
170
|
-
return json({ apps: await client.apps.list(user.
|
|
197
|
+
return json({ apps: await client.apps.list(user.workspaceId) });
|
|
171
198
|
},
|
|
172
|
-
|
|
199
|
+
"GET /integrations": async (request) => {
|
|
200
|
+
const { user } = await whoami(request);
|
|
201
|
+
return json({ toolkits: await client.integrations.list(user.workspaceId) });
|
|
202
|
+
},
|
|
203
|
+
"POST /integrations/connect": async (request) => {
|
|
204
|
+
const { user } = await whoami(request);
|
|
205
|
+
const body = (await request.json().catch(() => null));
|
|
206
|
+
if (!body?.toolkitSlug || !body?.returnUrl) {
|
|
207
|
+
throw new errors_js_1.ConnectError({
|
|
208
|
+
status: 400,
|
|
209
|
+
code: "validation_error",
|
|
210
|
+
message: "toolkitSlug and returnUrl are required",
|
|
211
|
+
endpoint: "POST /integrations/connect",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return json(await client.integrations.initiateConnect(user.workspaceId, body.toolkitSlug, body.returnUrl));
|
|
215
|
+
},
|
|
216
|
+
"POST /integrations/disconnect": async (request) => {
|
|
217
|
+
const { user } = await whoami(request);
|
|
218
|
+
const body = (await request.json().catch(() => null));
|
|
219
|
+
if (!body?.toolkitSlug) {
|
|
220
|
+
throw new errors_js_1.ConnectError({
|
|
221
|
+
status: 400,
|
|
222
|
+
code: "validation_error",
|
|
223
|
+
message: "toolkitSlug is required",
|
|
224
|
+
endpoint: "POST /integrations/disconnect",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
await client.integrations.disconnect(user.workspaceId, body.toolkitSlug);
|
|
228
|
+
return json({ ok: true });
|
|
229
|
+
},
|
|
230
|
+
// Account-wide, not scoped to this application - see `SessionsResource`'s
|
|
173
231
|
// doc comment. `requireToken` alone identifies the caller; the target
|
|
174
232
|
// `sessionId` on the revoke routes travels in the JSON body, not the
|
|
175
233
|
// URL, so the exact-match routes table above never needs a dynamic
|
|
@@ -208,14 +266,19 @@ function createConnectHandler(options) {
|
|
|
208
266
|
const secure = url.protocol === "https:";
|
|
209
267
|
const state = (0, identity_js_1.createState)();
|
|
210
268
|
const next = safeRedirect(url.searchParams.get("next"), defaultRedirect);
|
|
269
|
+
const requestedPrompt = url.searchParams.get("prompt");
|
|
270
|
+
const prompt = requestedPrompt === "create" || requestedPrompt === "none" ? requestedPrompt : undefined;
|
|
211
271
|
const authorizeUrl = client.identity.authorizeUrl({
|
|
212
272
|
redirectUri: `${url.origin}${callbackPath}`,
|
|
213
273
|
state,
|
|
214
|
-
...(
|
|
274
|
+
...(prompt ? { prompt } : {}),
|
|
215
275
|
});
|
|
216
276
|
// State and destination travel together in one cookie so the callback
|
|
217
277
|
// cannot end up with one and not the other.
|
|
218
|
-
|
|
278
|
+
const cookies = [stateCookie(`${state}:${next}`, secure)];
|
|
279
|
+
if (prompt === "none")
|
|
280
|
+
cookies.push(silentCookie("1", secure));
|
|
281
|
+
return redirect(authorizeUrl, cookies);
|
|
219
282
|
},
|
|
220
283
|
/**
|
|
221
284
|
* The other end of the round trip. Verifies `state`, exchanges the code,
|
|
@@ -232,10 +295,29 @@ function createConnectHandler(options) {
|
|
|
232
295
|
if (!cookieState || !sameState(cookieState, returnedState)) {
|
|
233
296
|
// Either a forged callback or a genuinely stale tab. Neither should
|
|
234
297
|
// mint a session, and neither is worth an error page.
|
|
235
|
-
return redirect(`${next}${next.includes("?") ? "&" : "?"}connect_error=state_mismatch`, [
|
|
298
|
+
return redirect(`${next}${next.includes("?") ? "&" : "?"}connect_error=state_mismatch`, [
|
|
299
|
+
clearState,
|
|
300
|
+
silentCookie("", secure),
|
|
301
|
+
]);
|
|
236
302
|
}
|
|
303
|
+
const wasSilent = readCookie(request, silentCookieName) === "1";
|
|
304
|
+
const clearSilent = silentCookie("", secure);
|
|
237
305
|
const error = url.searchParams.get("error");
|
|
238
306
|
if (error) {
|
|
307
|
+
// A silent (`prompt=none`) probe that came back empty-handed. All
|
|
308
|
+
// three of its outcomes mean the same thing to the app - "carry on
|
|
309
|
+
// signed out" - so it returns to `next` quietly: no retry, and no
|
|
310
|
+
// `connect_error`, because nothing went wrong.
|
|
311
|
+
//
|
|
312
|
+
// Getting this wrong is not subtle. The retry below fires on exactly
|
|
313
|
+
// the `login_required` a silent probe returns, so without this branch
|
|
314
|
+
// every silent attempt for a signed-out visitor would escalate itself
|
|
315
|
+
// into a full interactive sign-in redirect - turning "quietly check
|
|
316
|
+
// if they're already here" into "force everyone to log in", which is
|
|
317
|
+
// the precise opposite of the feature.
|
|
318
|
+
if (wasSilent) {
|
|
319
|
+
return redirect(next, [clearState, clearSilent]);
|
|
320
|
+
}
|
|
239
321
|
// `login_required` is normal traffic, not a fault: a new account gets
|
|
240
322
|
// pulled out to their inbox to verify, and the ~15 minute request
|
|
241
323
|
// expires before they return. Connect has already signed them in, so
|
|
@@ -245,15 +327,16 @@ function createConnectHandler(options) {
|
|
|
245
327
|
const retry = new URL(`${url.origin}${basePath}/signin`);
|
|
246
328
|
retry.searchParams.set("next", next);
|
|
247
329
|
retry.searchParams.set("connect_retry", "1");
|
|
248
|
-
return redirect(retry.toString(), [clearState]);
|
|
330
|
+
return redirect(retry.toString(), [clearState, clearSilent]);
|
|
249
331
|
}
|
|
250
332
|
return redirect(`${next}${next.includes("?") ? "&" : "?"}connect_error=${encodeURIComponent(error)}`, [
|
|
251
333
|
clearState,
|
|
334
|
+
clearSilent,
|
|
252
335
|
]);
|
|
253
336
|
}
|
|
254
337
|
const code = url.searchParams.get("code");
|
|
255
338
|
if (!code) {
|
|
256
|
-
return redirect(`${next}${next.includes("?") ? "&" : "?"}connect_error=missing_code`, [clearState]);
|
|
339
|
+
return redirect(`${next}${next.includes("?") ? "&" : "?"}connect_error=missing_code`, [clearState, clearSilent]);
|
|
257
340
|
}
|
|
258
341
|
const result = await client.identity.exchangeCode({
|
|
259
342
|
code,
|
|
@@ -262,7 +345,7 @@ function createConnectHandler(options) {
|
|
|
262
345
|
// Verified before the session is stored, not after - an unverifiable
|
|
263
346
|
// token must never become a signed-in user.
|
|
264
347
|
await client.identity.verifyIdToken(result.idToken);
|
|
265
|
-
const response = redirect(next, [clearState]);
|
|
348
|
+
const response = redirect(next, [clearState, clearSilent]);
|
|
266
349
|
await session.write(request, response, result.sessionToken);
|
|
267
350
|
return response;
|
|
268
351
|
},
|
package/dist/cjs/server/http.js
CHANGED
|
@@ -44,7 +44,7 @@ function parseRetryAfterMs(response) {
|
|
|
44
44
|
return Number.isFinite(seconds) ? Math.max(0, seconds) * 1000 : null;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Both error dialects, one shape. `/api/v1/
|
|
47
|
+
* Both error dialects, one shape. `/api/v1/application/*` answers
|
|
48
48
|
* `{ error: { code, message } }`; the `/sso/*` endpoints answer OAuth2's
|
|
49
49
|
* `{ error, error_description }`.
|
|
50
50
|
*/
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createState = exports.createConnectHandler = exports.SDK_VERSION = exports.DEFAULT_BASE_URL = exports.ConnectClient = void 0;
|
|
3
|
+
exports.verifyConnectWebhook = exports.createConnectWebhookHandler = exports.createState = exports.createConnectHandler = exports.SDK_VERSION = exports.DEFAULT_BASE_URL = exports.ConnectClient = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Server-only half of `@cosmoops/connect-sdk`.
|
|
6
6
|
*
|
|
@@ -16,3 +16,10 @@ var handler_js_1 = require("./handler.js");
|
|
|
16
16
|
Object.defineProperty(exports, "createConnectHandler", { enumerable: true, get: function () { return handler_js_1.createConnectHandler; } });
|
|
17
17
|
var identity_js_1 = require("./resources/identity.js");
|
|
18
18
|
Object.defineProperty(exports, "createState", { enumerable: true, get: function () { return identity_js_1.createState; } });
|
|
19
|
+
/**
|
|
20
|
+
* Webhook verification and a ready-made POST route. Server-only for the
|
|
21
|
+
* obvious reason: the signing secret must never reach a browser bundle.
|
|
22
|
+
*/
|
|
23
|
+
var webhooks_js_1 = require("./webhooks.js");
|
|
24
|
+
Object.defineProperty(exports, "createConnectWebhookHandler", { enumerable: true, get: function () { return webhooks_js_1.createConnectWebhookHandler; } });
|
|
25
|
+
Object.defineProperty(exports, "verifyConnectWebhook", { enumerable: true, get: function () { return webhooks_js_1.verifyConnectWebhook; } });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AccessResource = void 0;
|
|
4
4
|
const http_js_1 = require("../http.js");
|
|
5
|
-
/** What a signed-in member may do on your
|
|
5
|
+
/** What a signed-in member may do on your application, and the role catalog behind it. */
|
|
6
6
|
class AccessResource {
|
|
7
7
|
transport;
|
|
8
8
|
constructor(transport) {
|
|
@@ -17,10 +17,10 @@ class AccessResource {
|
|
|
17
17
|
return (0, http_js_1.request)(this.transport, {
|
|
18
18
|
method: "GET",
|
|
19
19
|
path: `/rbac/users/${(0, http_js_1.pathSegment)(userId, "userId")}/roles`,
|
|
20
|
-
query: {
|
|
20
|
+
query: { workspaceId: input.workspaceId },
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
/** Your
|
|
23
|
+
/** Your application's own permission catalog and role definitions - the definitions `getUserRoles` resolves against. */
|
|
24
24
|
listRoles() {
|
|
25
25
|
return (0, http_js_1.request)(this.transport, { method: "GET", path: "/roles" });
|
|
26
26
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AiResource = void 0;
|
|
4
4
|
const http_js_1 = require("../http.js");
|
|
5
|
-
/** RAG chat and structured extraction against
|
|
5
|
+
/** RAG chat and structured extraction against a workspace's data. */
|
|
6
6
|
class AiResource {
|
|
7
7
|
transport;
|
|
8
8
|
constructor(transport) {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AnalyticsResource = void 0;
|
|
4
|
+
const http_js_1 = require("../http.js");
|
|
5
|
+
/**
|
|
6
|
+
* Your application's own analytics: emit product events, and read back the daily
|
|
7
|
+
* numbers Connect aggregates from them.
|
|
8
|
+
*
|
|
9
|
+
* There is no way to name an application in either call. The scope is always the one
|
|
10
|
+
* your API key belongs to - that is the endpoint's security model, not an
|
|
11
|
+
* omission.
|
|
12
|
+
*/
|
|
13
|
+
class AnalyticsResource {
|
|
14
|
+
transport;
|
|
15
|
+
constructor(transport) {
|
|
16
|
+
this.transport = transport;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Daily series for the requested metrics, up to 90 days.
|
|
20
|
+
*
|
|
21
|
+
* **Throws on failure**, unlike `emit` below. A read whose result you are
|
|
22
|
+
* about to render has to be able to say "I could not load"; silently
|
|
23
|
+
* returning an empty series would draw a chart of zeroes.
|
|
24
|
+
*/
|
|
25
|
+
async series(query) {
|
|
26
|
+
const params = new URLSearchParams({
|
|
27
|
+
metrics: query.metrics.join(","),
|
|
28
|
+
from: query.from,
|
|
29
|
+
to: query.to,
|
|
30
|
+
});
|
|
31
|
+
return (0, http_js_1.request)(this.transport, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
path: `/analytics?${params.toString()}`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Never throws - the same judgement `LogsResource.write` makes, for the same
|
|
38
|
+
* reason: telemetry that takes down the code path it was observing is worse
|
|
39
|
+
* than a lost event. A failure is reported through `delivered`.
|
|
40
|
+
*
|
|
41
|
+
* Up to 50 events per call. Pass `sessionToken` on an event to attribute it
|
|
42
|
+
* to the signed-in user; without one it still lands, just unattributed.
|
|
43
|
+
*/
|
|
44
|
+
async emit(events) {
|
|
45
|
+
const normalized = events.map((event) => ({
|
|
46
|
+
...event,
|
|
47
|
+
occurredAt: event.occurredAt instanceof Date ? event.occurredAt.toISOString() : event.occurredAt,
|
|
48
|
+
}));
|
|
49
|
+
try {
|
|
50
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
path: "/events",
|
|
53
|
+
json: { events: normalized },
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
delivered: true,
|
|
57
|
+
accepted: result?.accepted ?? events.length,
|
|
58
|
+
attributed: result?.attributed ?? 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return { delivered: false, accepted: 0, attributed: 0 };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.AnalyticsResource = AnalyticsResource;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AppsResource = void 0;
|
|
4
4
|
const http_js_1 = require("../http.js");
|
|
5
|
-
/** The
|
|
5
|
+
/** The applications a workspace can open - the data behind a launcher grid. */
|
|
6
6
|
class AppsResource {
|
|
7
7
|
transport;
|
|
8
8
|
constructor(transport) {
|
|
@@ -10,13 +10,13 @@ class AppsResource {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Launchable apps only, so a grid tile is never a dead end. The calling
|
|
13
|
-
*
|
|
13
|
+
* application is included and flagged `isCurrent` rather than filtered out.
|
|
14
14
|
*/
|
|
15
|
-
async list(
|
|
15
|
+
async list(workspaceId) {
|
|
16
16
|
const result = await (0, http_js_1.request)(this.transport, {
|
|
17
17
|
method: "GET",
|
|
18
18
|
path: "/apps",
|
|
19
|
-
query: {
|
|
19
|
+
query: { workspaceId },
|
|
20
20
|
});
|
|
21
21
|
// `?? []` for the same reason `billing.listPlans` has it: this is a
|
|
22
22
|
// collection every caller immediately maps or measures, and a body without
|
|
@@ -10,7 +10,7 @@ class BillingResource {
|
|
|
10
10
|
this.transport = transport;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Your
|
|
13
|
+
* Your application's active plans - render live pricing instead of hardcoding it.
|
|
14
14
|
*
|
|
15
15
|
* `features` is normalised on the way out: Connect used to send bare keys
|
|
16
16
|
* and now sends `{ key, name, description }`, and this SDK is versioned
|
|
@@ -28,7 +28,7 @@ class BillingResource {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* What is left of every consumable add-on on
|
|
31
|
+
* What is left of every consumable add-on on a workspace's
|
|
32
32
|
* subscription - the read half of `consumeAddon`.
|
|
33
33
|
*
|
|
34
34
|
* Needs a key scoped to `addons:read`, which is separate from
|
|
@@ -38,10 +38,10 @@ class BillingResource {
|
|
|
38
38
|
* Returns an empty `addons` array rather than throwing when there is
|
|
39
39
|
* nothing to meter, so a caller can render it unconditionally.
|
|
40
40
|
*/
|
|
41
|
-
async getAddonUsage(
|
|
41
|
+
async getAddonUsage(workspaceId) {
|
|
42
42
|
const response = await (0, http_js_1.request)(this.transport, {
|
|
43
43
|
method: "GET",
|
|
44
|
-
path: `/addons/
|
|
44
|
+
path: `/addons/workspaces/${(0, http_js_1.pathSegment)(workspaceId, "workspaceId")}`,
|
|
45
45
|
});
|
|
46
46
|
// Normalised, not passed through: the promise above resolves for any 2xx,
|
|
47
47
|
// and `ConnectUsageMeter` reads `addons` straight off it. A body without
|
|
@@ -52,7 +52,7 @@ class BillingResource {
|
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Debit a consumable add-on's per-cycle balance on behalf of an
|
|
55
|
-
*
|
|
55
|
+
* workspace's users. Call it at whatever cadence your usage happens.
|
|
56
56
|
*
|
|
57
57
|
* An `Idempotency-Key` is always sent, generated per call when you do not
|
|
58
58
|
* supply one: a balance cannot be un-spent, so a retried consume without a
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DriveResource = void 0;
|
|
4
4
|
const http_js_1 = require("../http.js");
|
|
5
|
-
/** Files in
|
|
5
|
+
/** Files in a workspace's Drive - upload, and signed URLs to read back. */
|
|
6
6
|
class DriveResource {
|
|
7
7
|
transport;
|
|
8
8
|
constructor(transport) {
|
|
@@ -12,7 +12,7 @@ class DriveResource {
|
|
|
12
12
|
const form = new FormData();
|
|
13
13
|
const fileName = input.fileName ?? input.file.name ?? "upload";
|
|
14
14
|
form.set("file", input.file, fileName);
|
|
15
|
-
form.set("
|
|
15
|
+
form.set("workspaceId", input.workspaceId);
|
|
16
16
|
if (input.parentId)
|
|
17
17
|
form.set("parentId", input.parentId);
|
|
18
18
|
return (0, http_js_1.request)(this.transport, { method: "POST", path: "/drive/files", multipart: form });
|
|
@@ -22,7 +22,7 @@ class DriveResource {
|
|
|
22
22
|
const result = await (0, http_js_1.request)(this.transport, {
|
|
23
23
|
method: "GET",
|
|
24
24
|
path: `/drive/files/${(0, http_js_1.pathSegment)(fileId, "fileId")}/url`,
|
|
25
|
-
query: {
|
|
25
|
+
query: { workspaceId: input.workspaceId },
|
|
26
26
|
});
|
|
27
27
|
return result.url;
|
|
28
28
|
}
|
|
@@ -31,7 +31,7 @@ class DriveResource {
|
|
|
31
31
|
const result = await (0, http_js_1.request)(this.transport, {
|
|
32
32
|
method: "GET",
|
|
33
33
|
path: `/drive/files/${(0, http_js_1.pathSegment)(fileId, "fileId")}/preview-url`,
|
|
34
|
-
query: {
|
|
34
|
+
query: { workspaceId: input.workspaceId, size: input.size },
|
|
35
35
|
});
|
|
36
36
|
return result.url;
|
|
37
37
|
}
|
|
@@ -12,7 +12,7 @@ class EntitlementsResource {
|
|
|
12
12
|
this.transport = transport;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Every gate Connect owns between
|
|
15
|
+
* Every gate Connect owns between a workspace and your application -
|
|
16
16
|
* subscription, KYC/KYB, a chargeable payment method, seat headroom -
|
|
17
17
|
* resolved in one call.
|
|
18
18
|
*
|
|
@@ -20,17 +20,17 @@ class EntitlementsResource {
|
|
|
20
20
|
* Connect screen that fixes it, so you can route them somewhere useful
|
|
21
21
|
* instead of refusing them with nowhere to go.
|
|
22
22
|
*/
|
|
23
|
-
getReadiness(
|
|
23
|
+
getReadiness(workspaceId) {
|
|
24
24
|
return (0, http_js_1.request)(this.transport, {
|
|
25
25
|
method: "GET",
|
|
26
|
-
path: `/onboarding/
|
|
26
|
+
path: `/onboarding/workspaces/${(0, http_js_1.pathSegment)(workspaceId, "workspaceId")}`,
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
/** The live plan and subscription, including `plan.features`. */
|
|
30
|
-
getSubscription(
|
|
30
|
+
getSubscription(workspaceId) {
|
|
31
31
|
return (0, http_js_1.request)(this.transport, {
|
|
32
32
|
method: "GET",
|
|
33
|
-
path: `/subscriptions/
|
|
33
|
+
path: `/subscriptions/workspaces/${(0, http_js_1.pathSegment)(workspaceId, "workspaceId")}`,
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
@@ -41,8 +41,8 @@ class EntitlementsResource {
|
|
|
41
41
|
* all: "not entitled" is the same answer either way, and a feature gate
|
|
42
42
|
* should not need a try/catch around it.
|
|
43
43
|
*/
|
|
44
|
-
async hasFeature(
|
|
45
|
-
const result = await this.getSubscription(
|
|
44
|
+
async hasFeature(workspaceId, feature) {
|
|
45
|
+
const result = await this.getSubscription(workspaceId);
|
|
46
46
|
return result.hasActiveSubscription && (result.plan?.features.includes(feature) ?? false);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -70,7 +70,7 @@ class IdentityResource {
|
|
|
70
70
|
sessionTokenExpiresIn: wire.session_token_expires_in,
|
|
71
71
|
user: wire.user,
|
|
72
72
|
subscription: wire.subscription,
|
|
73
|
-
|
|
73
|
+
applicationRole: wire.applicationRole,
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
@@ -140,7 +140,7 @@ class IdentityResource {
|
|
|
140
140
|
return {
|
|
141
141
|
user: wire.user,
|
|
142
142
|
subscription: wire.subscription,
|
|
143
|
-
|
|
143
|
+
applicationRole: wire.applicationRole,
|
|
144
144
|
sessionExpiresAt: wire.session_expires_at,
|
|
145
145
|
};
|
|
146
146
|
}
|
|
@@ -149,7 +149,7 @@ class IdentityResource {
|
|
|
149
149
|
* rest of its ~15-day life. Discard your own copy of `sessionToken` too;
|
|
150
150
|
* this does not do that for you.
|
|
151
151
|
*
|
|
152
|
-
* `scope` defaults to `"device"`: this sign-in, every other
|
|
152
|
+
* `scope` defaults to `"device"`: this sign-in, every other application signed
|
|
153
153
|
* into from the same browser, and Connect's own session on that one device.
|
|
154
154
|
* `"all"` ends every session on every device - an explicit "sign out
|
|
155
155
|
* everywhere" action only.
|
|
@@ -181,7 +181,7 @@ class IdentityResource {
|
|
|
181
181
|
throw new errors_js_1.ConnectError({
|
|
182
182
|
status: 0,
|
|
183
183
|
code: "missing_client_id",
|
|
184
|
-
message: "identity.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this
|
|
184
|
+
message: "identity.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this application's Integration tab, under Connect client.",
|
|
185
185
|
endpoint: "identity",
|
|
186
186
|
});
|
|
187
187
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IntegrationsResource = void 0;
|
|
4
|
+
const http_js_1 = require("../http.js");
|
|
5
|
+
/**
|
|
6
|
+
* Composio-hosted third-party connections (Google Calendar, Slack,
|
|
7
|
+
* Twitter/X, ...) a workspace has connected. Connect proxies every call
|
|
8
|
+
* here - your application never sees or holds the underlying OAuth token, which
|
|
9
|
+
* stays with Composio.
|
|
10
|
+
*/
|
|
11
|
+
class IntegrationsResource {
|
|
12
|
+
transport;
|
|
13
|
+
constructor(transport) {
|
|
14
|
+
this.transport = transport;
|
|
15
|
+
}
|
|
16
|
+
/** Enabled toolkits and this workspace's connection status for each. */
|
|
17
|
+
async list(workspaceId) {
|
|
18
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
19
|
+
method: "GET",
|
|
20
|
+
path: "/integrations",
|
|
21
|
+
query: { workspaceId },
|
|
22
|
+
});
|
|
23
|
+
// `?? []` for the same reason `AppsResource.list` has it - a collection
|
|
24
|
+
// every caller immediately maps or measures.
|
|
25
|
+
return result.toolkits ?? [];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Starts a Composio-hosted connection for one toolkit. The returned
|
|
29
|
+
* `redirectUrl` is where the browser completes the provider's OAuth
|
|
30
|
+
* screen, then Composio bounces it back to `returnUrl`.
|
|
31
|
+
*
|
|
32
|
+
* **The connection is not active when this returns.** Treat the toolkit
|
|
33
|
+
* as `pending` until a later `list()` call reports `active` - only
|
|
34
|
+
* Connect's own webhook from Composio confirms that, never the redirect
|
|
35
|
+
* itself. Reconnecting a disconnected/failed toolkit calls this same
|
|
36
|
+
* method - there is no separate reconnect.
|
|
37
|
+
*
|
|
38
|
+
* Meant to be called on behalf of a signed-in workspace admin (this is what the
|
|
39
|
+
* bridge's fixed `POST /integrations/connect` route does), not as a bare
|
|
40
|
+
* background action with no admin present to complete the OAuth screen.
|
|
41
|
+
*/
|
|
42
|
+
async initiateConnect(workspaceId, toolkitSlug, returnUrl) {
|
|
43
|
+
return (0, http_js_1.request)(this.transport, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
path: "/integrations/connect",
|
|
46
|
+
json: { workspaceId, toolkitSlug, returnUrl },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
|
|
50
|
+
async disconnect(workspaceId, toolkitSlug) {
|
|
51
|
+
return (0, http_js_1.request)(this.transport, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
path: "/integrations/disconnect",
|
|
54
|
+
json: { workspaceId, toolkitSlug },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Calls a Composio action against this workspace's connected account for the named toolkit. */
|
|
58
|
+
async execute(workspaceId, input) {
|
|
59
|
+
return (0, http_js_1.request)(this.transport, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
path: "/integrations/actions/execute",
|
|
62
|
+
json: { workspaceId, ...input },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.IntegrationsResource = IntegrationsResource;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LogsResource = void 0;
|
|
4
4
|
const http_js_1 = require("../http.js");
|
|
5
|
-
/** Push feature events into the Console's audit log, filterable to your
|
|
5
|
+
/** Push feature events into the Console's audit log, filterable to your application. */
|
|
6
6
|
class LogsResource {
|
|
7
7
|
transport;
|
|
8
8
|
constructor(transport) {
|
|
@@ -14,9 +14,9 @@ class LogsResource {
|
|
|
14
14
|
* reported through the return value.
|
|
15
15
|
*
|
|
16
16
|
* Pass `sessionToken` to attribute the event to the end user it belongs to.
|
|
17
|
-
* Both scopes on the stored entry come from something verified - the
|
|
17
|
+
* Both scopes on the stored entry come from something verified - the application
|
|
18
18
|
* from your API key, the user from that token, which Connect checks was
|
|
19
|
-
* minted for your
|
|
19
|
+
* minted for your application before attributing anything. `attributed` in the
|
|
20
20
|
* result says whether it resolved; a `false` there still means the line was
|
|
21
21
|
* stored, just without a user on it.
|
|
22
22
|
*/
|
|
@@ -8,11 +8,11 @@ const http_js_1 = require("../http.js");
|
|
|
8
8
|
* timestamps - the same list Connect's own account settings page shows the
|
|
9
9
|
* user themselves.
|
|
10
10
|
*
|
|
11
|
-
* **Account-wide, not scoped to this
|
|
12
|
-
* browser's login to Connect, and it fans out to every partner
|
|
13
|
-
* `sessionToken` - Connect has no per-
|
|
11
|
+
* **Account-wide, not scoped to this application.** A Connect session is one
|
|
12
|
+
* browser's login to Connect, and it fans out to every partner application's own
|
|
13
|
+
* `sessionToken` - Connect has no per-application session concept to filter by.
|
|
14
14
|
* `list()` returns every session on the account, including ones that
|
|
15
|
-
* originated from a completely different, unrelated
|
|
15
|
+
* originated from a completely different, unrelated application. Requires the
|
|
16
16
|
* `sessions:manage` scope on your API key - unlike `identity`'s base `sso`
|
|
17
17
|
* capability, this is never granted to a zero-scope legacy key by default.
|
|
18
18
|
*
|
|
@@ -38,7 +38,7 @@ class SessionsResource {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Ends one specific session by id - not necessarily the one `sessionToken`
|
|
41
|
-
* itself was minted from. Cascades to every partner-
|
|
41
|
+
* itself was minted from. Cascades to every partner-application sign-in that
|
|
42
42
|
* originated from that same browser, same as Connect's own "sign out this
|
|
43
43
|
* device".
|
|
44
44
|
*/
|
|
@@ -72,7 +72,7 @@ class SessionsResource {
|
|
|
72
72
|
throw new errors_js_1.ConnectError({
|
|
73
73
|
status: 0,
|
|
74
74
|
code: "missing_client_id",
|
|
75
|
-
message: "sessions.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this
|
|
75
|
+
message: "sessions.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this application's Integration tab, under Connect client.",
|
|
76
76
|
endpoint: "sessions",
|
|
77
77
|
});
|
|
78
78
|
}
|