@cosmoops/connect-sdk 0.6.0 → 0.9.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 +165 -0
- package/README.md +273 -91
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +42 -7
- package/dist/cjs/client/components/account-sessions.js +3 -3
- package/dist/cjs/client/components/ai-format.js +135 -0
- package/dist/cjs/client/components/ai-widgets.js +232 -0
- package/dist/cjs/client/components/app-shell.js +9 -8
- package/dist/cjs/client/components/apps-grid.js +3 -3
- package/dist/cjs/client/components/ask-ai.js +334 -0
- package/dist/cjs/client/components/feature-gate.js +2 -2
- package/dist/cjs/client/components/integrations-modal.js +1 -1
- package/dist/cjs/client/components/logo.js +3 -3
- package/dist/cjs/client/components/nav.js +1 -1
- package/dist/cjs/client/components/plan-pricing.js +1 -1
- package/dist/cjs/client/components/plans.js +6 -6
- package/dist/cjs/client/components/readiness.js +3 -3
- package/dist/cjs/client/components/side-nav.js +1 -1
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/usage-meter.js +1 -1
- package/dist/cjs/client/components/user-menu.js +31 -8
- package/dist/cjs/client/components/workspace-type-gate.js +48 -0
- package/dist/cjs/client/index.js +32 -4
- package/dist/cjs/client/mark.js +1 -1
- package/dist/cjs/client/provider.js +50 -7
- 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 +2 -2
- package/dist/cjs/server/client.js +15 -15
- package/dist/cjs/server/handler.js +56 -13
- package/dist/cjs/server/http.js +47 -25
- 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 +2 -2
- 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 +48 -7
- package/dist/cjs/server/resources/identity.js +6 -4
- package/dist/cjs/server/resources/integrations.js +13 -13
- package/dist/cjs/server/resources/logs.js +3 -3
- package/dist/cjs/server/resources/sessions.js +6 -6
- package/dist/cjs/server/webhooks.js +4 -4
- 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 +43 -8
- package/dist/esm/client/components/account-sessions.js +3 -3
- package/dist/esm/client/components/ai-format.js +129 -0
- package/dist/esm/client/components/ai-widgets.js +223 -0
- package/dist/esm/client/components/app-shell.js +8 -7
- package/dist/esm/client/components/apps-grid.js +3 -3
- package/dist/esm/client/components/ask-ai.js +331 -0
- package/dist/esm/client/components/feature-gate.js +2 -2
- package/dist/esm/client/components/integrations-modal.js +1 -1
- package/dist/esm/client/components/logo.js +3 -3
- package/dist/esm/client/components/nav.js +1 -1
- package/dist/esm/client/components/plan-pricing.js +1 -1
- package/dist/esm/client/components/plans.js +6 -6
- package/dist/esm/client/components/readiness.js +3 -3
- package/dist/esm/client/components/side-nav.js +1 -1
- package/dist/esm/client/components/subscribe-dialog.js +2 -2
- package/dist/esm/client/components/subscription.js +1 -1
- package/dist/esm/client/components/usage-meter.js +1 -1
- package/dist/esm/client/components/user-menu.js +31 -8
- package/dist/esm/client/components/workspace-type-gate.js +45 -0
- package/dist/esm/client/index.js +14 -3
- package/dist/esm/client/mark.js +1 -1
- package/dist/esm/client/provider.js +48 -7
- 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 +2 -2
- package/dist/esm/server/client.js +15 -15
- package/dist/esm/server/handler.js +56 -13
- package/dist/esm/server/http.js +47 -25
- 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 +2 -2
- 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 +48 -7
- package/dist/esm/server/resources/identity.js +6 -4
- package/dist/esm/server/resources/integrations.js +13 -13
- package/dist/esm/server/resources/logs.js +3 -3
- package/dist/esm/server/resources/sessions.js +6 -6
- package/dist/esm/server/webhooks.js +4 -4
- package/dist/esm/types.js +1 -1
- package/dist/theme.css +87 -0
- package/dist/types/client/components/access-gate.d.ts +18 -4
- package/dist/types/client/components/account-sessions.d.ts +1 -1
- package/dist/types/client/components/ai-format.d.ts +24 -0
- package/dist/types/client/components/ai-widgets.d.ts +42 -0
- package/dist/types/client/components/app-shell.d.ts +25 -9
- package/dist/types/client/components/apps-grid.d.ts +1 -1
- package/dist/types/client/components/ask-ai.d.ts +73 -0
- package/dist/types/client/components/feature-gate.d.ts +1 -1
- package/dist/types/client/components/integrations-modal.d.ts +1 -1
- package/dist/types/client/components/logo.d.ts +5 -5
- package/dist/types/client/components/nav.d.ts +3 -3
- 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 +1 -1
- package/dist/types/client/components/subscribe-dialog.d.ts +3 -3
- package/dist/types/client/components/usage-meter.d.ts +1 -1
- package/dist/types/client/components/user-menu.d.ts +4 -3
- package/dist/types/client/components/workspace-type-gate.d.ts +48 -0
- package/dist/types/client/index.d.ts +11 -4
- package/dist/types/client/mark.d.ts +1 -1
- package/dist/types/client/provider.d.ts +52 -9
- 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 +1 -1
- package/dist/types/server/client.d.ts +13 -13
- package/dist/types/server/handler.d.ts +2 -2
- package/dist/types/server/http.d.ts +6 -1
- 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 +2 -2
- 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 +37 -5
- package/dist/types/server/resources/identity.d.ts +2 -2
- package/dist/types/server/resources/integrations.d.ts +9 -9
- 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 +4 -4
- package/dist/types/types.d.ts +235 -61
- package/package.json +8 -2
package/dist/esm/server/http.js
CHANGED
|
@@ -40,7 +40,7 @@ function parseRetryAfterMs(response) {
|
|
|
40
40
|
return Number.isFinite(seconds) ? Math.max(0, seconds) * 1000 : null;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* Both error dialects, one shape. `/api/v1/
|
|
43
|
+
* Both error dialects, one shape. `/api/v1/application/*` answers
|
|
44
44
|
* `{ error: { code, message } }`; the `/sso/*` endpoints answer OAuth2's
|
|
45
45
|
* `{ error, error_description }`.
|
|
46
46
|
*/
|
|
@@ -124,33 +124,55 @@ export async function request(options, spec) {
|
|
|
124
124
|
// request for minutes.
|
|
125
125
|
await sleep(Math.min(lastError?.retryAfterMs ?? 250 * 2 ** (attempt - 1), 8_000));
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
// A cancellable timer rather than `AbortSignal.timeout`, because the
|
|
128
|
+
// timeout has to be *stopped* once this attempt is done reading - and a
|
|
129
|
+
// timeout signal cannot be cleared.
|
|
130
|
+
//
|
|
131
|
+
// That distinction is the whole point for a streaming endpoint. `fetch`
|
|
132
|
+
// resolves the moment the headers land, but its signal keeps governing
|
|
133
|
+
// the body: an `AbortSignal.timeout(20_000)` handed to `ai.chat` tears the
|
|
134
|
+
// SSE stream down mid-answer, twenty seconds in, on every conversation
|
|
135
|
+
// long enough to matter. Clearing the timer in `finally` bounds the
|
|
136
|
+
// request the same way it always did (a `raw` caller still gets its
|
|
137
|
+
// timeout on *reaching* Connect, and a JSON caller still gets it across
|
|
138
|
+
// the body read, which happens inside the `try`) while leaving a stream
|
|
139
|
+
// that has actually started to run as long as it needs. A caller that
|
|
140
|
+
// wants to bound the stream itself passes `spec.signal`.
|
|
141
|
+
const timeoutController = new AbortController();
|
|
142
|
+
const timer = setTimeout(() => timeoutController.abort(), options.timeoutMs);
|
|
143
|
+
const signal = spec.signal
|
|
144
|
+
? AbortSignal.any([spec.signal, timeoutController.signal])
|
|
145
|
+
: timeoutController.signal;
|
|
130
146
|
try {
|
|
131
|
-
response
|
|
147
|
+
let response;
|
|
148
|
+
try {
|
|
149
|
+
response = await options.fetch(url.toString(), { method: spec.method, headers, body, signal });
|
|
150
|
+
}
|
|
151
|
+
catch (cause) {
|
|
152
|
+
// A caller-supplied abort is a decision, not a fault - never retried.
|
|
153
|
+
if (spec.signal?.aborted)
|
|
154
|
+
throw cause;
|
|
155
|
+
lastError = new ConnectError({
|
|
156
|
+
status: 0,
|
|
157
|
+
code: "network_error",
|
|
158
|
+
message: `${endpoint} could not reach Connect`,
|
|
159
|
+
endpoint,
|
|
160
|
+
cause,
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (response.ok) {
|
|
165
|
+
if (spec.raw)
|
|
166
|
+
return response;
|
|
167
|
+
return (await readBody(response));
|
|
168
|
+
}
|
|
169
|
+
lastError = toConnectError(response, await readBody(response), endpoint);
|
|
170
|
+
if (!RETRYABLE_STATUS.has(response.status))
|
|
171
|
+
throw lastError;
|
|
132
172
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (spec.signal?.aborted)
|
|
136
|
-
throw cause;
|
|
137
|
-
lastError = new ConnectError({
|
|
138
|
-
status: 0,
|
|
139
|
-
code: "network_error",
|
|
140
|
-
message: `${endpoint} could not reach Connect`,
|
|
141
|
-
endpoint,
|
|
142
|
-
cause,
|
|
143
|
-
});
|
|
144
|
-
continue;
|
|
173
|
+
finally {
|
|
174
|
+
clearTimeout(timer);
|
|
145
175
|
}
|
|
146
|
-
if (response.ok) {
|
|
147
|
-
if (spec.raw)
|
|
148
|
-
return response;
|
|
149
|
-
return (await readBody(response));
|
|
150
|
-
}
|
|
151
|
-
lastError = toConnectError(response, await readBody(response), endpoint);
|
|
152
|
-
if (!RETRYABLE_STATUS.has(response.status))
|
|
153
|
-
throw lastError;
|
|
154
176
|
}
|
|
155
177
|
throw lastError;
|
|
156
178
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pathSegment, request } from "../http.js";
|
|
2
|
-
/** What a signed-in member may do on your
|
|
2
|
+
/** What a signed-in member may do on your application, and the role catalog behind it. */
|
|
3
3
|
export class AccessResource {
|
|
4
4
|
transport;
|
|
5
5
|
constructor(transport) {
|
|
@@ -14,10 +14,10 @@ export class AccessResource {
|
|
|
14
14
|
return request(this.transport, {
|
|
15
15
|
method: "GET",
|
|
16
16
|
path: `/rbac/users/${pathSegment(userId, "userId")}/roles`,
|
|
17
|
-
query: {
|
|
17
|
+
query: { workspaceId: input.workspaceId },
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
/** Your
|
|
20
|
+
/** Your application's own permission catalog and role definitions - the definitions `getUserRoles` resolves against. */
|
|
21
21
|
listRoles() {
|
|
22
22
|
return request(this.transport, { method: "GET", path: "/roles" });
|
|
23
23
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { request } from "../http.js";
|
|
2
2
|
/**
|
|
3
|
-
* Your
|
|
3
|
+
* Your application's own analytics: emit product events, and read back the daily
|
|
4
4
|
* numbers Connect aggregates from them.
|
|
5
5
|
*
|
|
6
|
-
* There is no way to name
|
|
6
|
+
* There is no way to name an application in either call. The scope is always the one
|
|
7
7
|
* your API key belongs to - that is the endpoint's security model, not an
|
|
8
8
|
* omission.
|
|
9
9
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { request } from "../http.js";
|
|
2
|
-
/** The
|
|
2
|
+
/** The applications a workspace can open - the data behind a launcher grid. */
|
|
3
3
|
export class AppsResource {
|
|
4
4
|
transport;
|
|
5
5
|
constructor(transport) {
|
|
@@ -7,13 +7,13 @@ export class AppsResource {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Launchable apps only, so a grid tile is never a dead end. The calling
|
|
10
|
-
*
|
|
10
|
+
* application is included and flagged `isCurrent` rather than filtered out.
|
|
11
11
|
*/
|
|
12
|
-
async list(
|
|
12
|
+
async list(workspaceId) {
|
|
13
13
|
const result = await request(this.transport, {
|
|
14
14
|
method: "GET",
|
|
15
15
|
path: "/apps",
|
|
16
|
-
query: {
|
|
16
|
+
query: { workspaceId },
|
|
17
17
|
});
|
|
18
18
|
// `?? []` for the same reason `billing.listPlans` has it: this is a
|
|
19
19
|
// collection every caller immediately maps or measures, and a body without
|
|
@@ -7,7 +7,7 @@ export class BillingResource {
|
|
|
7
7
|
this.transport = transport;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Your
|
|
10
|
+
* Your application's active plans - render live pricing instead of hardcoding it.
|
|
11
11
|
*
|
|
12
12
|
* `features` is normalised on the way out: Connect used to send bare keys
|
|
13
13
|
* and now sends `{ key, name, description }`, and this SDK is versioned
|
|
@@ -25,7 +25,7 @@ export class BillingResource {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* What is left of every consumable add-on on
|
|
28
|
+
* What is left of every consumable add-on on a workspace's
|
|
29
29
|
* subscription - the read half of `consumeAddon`.
|
|
30
30
|
*
|
|
31
31
|
* Needs a key scoped to `addons:read`, which is separate from
|
|
@@ -35,10 +35,10 @@ export class BillingResource {
|
|
|
35
35
|
* Returns an empty `addons` array rather than throwing when there is
|
|
36
36
|
* nothing to meter, so a caller can render it unconditionally.
|
|
37
37
|
*/
|
|
38
|
-
async getAddonUsage(
|
|
38
|
+
async getAddonUsage(workspaceId) {
|
|
39
39
|
const response = await request(this.transport, {
|
|
40
40
|
method: "GET",
|
|
41
|
-
path: `/addons/
|
|
41
|
+
path: `/addons/workspaces/${pathSegment(workspaceId, "workspaceId")}`,
|
|
42
42
|
});
|
|
43
43
|
// Normalised, not passed through: the promise above resolves for any 2xx,
|
|
44
44
|
// and `ConnectUsageMeter` reads `addons` straight off it. A body without
|
|
@@ -49,7 +49,7 @@ export class BillingResource {
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* Debit a consumable add-on's per-cycle balance on behalf of an
|
|
52
|
-
*
|
|
52
|
+
* workspace's users. Call it at whatever cadence your usage happens.
|
|
53
53
|
*
|
|
54
54
|
* An `Idempotency-Key` is always sent, generated per call when you do not
|
|
55
55
|
* supply one: a balance cannot be un-spent, so a retried consume without a
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pathSegment, request } from "../http.js";
|
|
2
|
-
/** Files in
|
|
2
|
+
/** Files in a workspace's Drive - upload, and signed URLs to read back. */
|
|
3
3
|
export class DriveResource {
|
|
4
4
|
transport;
|
|
5
5
|
constructor(transport) {
|
|
@@ -9,7 +9,7 @@ export class DriveResource {
|
|
|
9
9
|
const form = new FormData();
|
|
10
10
|
const fileName = input.fileName ?? input.file.name ?? "upload";
|
|
11
11
|
form.set("file", input.file, fileName);
|
|
12
|
-
form.set("
|
|
12
|
+
form.set("workspaceId", input.workspaceId);
|
|
13
13
|
if (input.parentId)
|
|
14
14
|
form.set("parentId", input.parentId);
|
|
15
15
|
return request(this.transport, { method: "POST", path: "/drive/files", multipart: form });
|
|
@@ -19,7 +19,7 @@ export class DriveResource {
|
|
|
19
19
|
const result = await request(this.transport, {
|
|
20
20
|
method: "GET",
|
|
21
21
|
path: `/drive/files/${pathSegment(fileId, "fileId")}/url`,
|
|
22
|
-
query: {
|
|
22
|
+
query: { workspaceId: input.workspaceId },
|
|
23
23
|
});
|
|
24
24
|
return result.url;
|
|
25
25
|
}
|
|
@@ -28,7 +28,7 @@ export class DriveResource {
|
|
|
28
28
|
const result = await request(this.transport, {
|
|
29
29
|
method: "GET",
|
|
30
30
|
path: `/drive/files/${pathSegment(fileId, "fileId")}/preview-url`,
|
|
31
|
-
query: {
|
|
31
|
+
query: { workspaceId: input.workspaceId, size: input.size },
|
|
32
32
|
});
|
|
33
33
|
return result.url;
|
|
34
34
|
}
|
|
@@ -9,7 +9,7 @@ export class EntitlementsResource {
|
|
|
9
9
|
this.transport = transport;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Every gate Connect owns between
|
|
12
|
+
* Every gate Connect owns between a workspace and your application -
|
|
13
13
|
* subscription, KYC/KYB, a chargeable payment method, seat headroom -
|
|
14
14
|
* resolved in one call.
|
|
15
15
|
*
|
|
@@ -17,17 +17,17 @@ export class EntitlementsResource {
|
|
|
17
17
|
* Connect screen that fixes it, so you can route them somewhere useful
|
|
18
18
|
* instead of refusing them with nowhere to go.
|
|
19
19
|
*/
|
|
20
|
-
getReadiness(
|
|
20
|
+
getReadiness(workspaceId) {
|
|
21
21
|
return request(this.transport, {
|
|
22
22
|
method: "GET",
|
|
23
|
-
path: `/onboarding/
|
|
23
|
+
path: `/onboarding/workspaces/${pathSegment(workspaceId, "workspaceId")}`,
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
/** The live plan and subscription, including `plan.features`. */
|
|
27
|
-
getSubscription(
|
|
27
|
+
getSubscription(workspaceId) {
|
|
28
28
|
return request(this.transport, {
|
|
29
29
|
method: "GET",
|
|
30
|
-
path: `/subscriptions/
|
|
30
|
+
path: `/subscriptions/workspaces/${pathSegment(workspaceId, "workspaceId")}`,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
@@ -38,8 +38,49 @@ export class EntitlementsResource {
|
|
|
38
38
|
* all: "not entitled" is the same answer either way, and a feature gate
|
|
39
39
|
* should not need a try/catch around it.
|
|
40
40
|
*/
|
|
41
|
-
async hasFeature(
|
|
42
|
-
const result = await this.getSubscription(
|
|
41
|
+
async hasFeature(workspaceId, feature) {
|
|
42
|
+
const result = await this.getSubscription(workspaceId);
|
|
43
43
|
return result.hasActiveSubscription && (result.plan?.features.includes(feature) ?? false);
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* One page of the workspaces subscribed to your application, newest first.
|
|
47
|
+
*
|
|
48
|
+
* The inverse of `getSubscription`, which asks about one workspace you
|
|
49
|
+
* already know. This asks who your subscribers *are* - what a partner
|
|
50
|
+
* dashboard, an MRR total or a churn report is built on.
|
|
51
|
+
*
|
|
52
|
+
* Rate-limited far more tightly than the per-workspace read (10/min per
|
|
53
|
+
* key, against 60): it walks your whole subscriber base and is a reporting
|
|
54
|
+
* call, not a per-request gate. Use `getSubscription` in a hot path.
|
|
55
|
+
*
|
|
56
|
+
* Your application is resolved from the API key, so there is no parameter
|
|
57
|
+
* for it and no way to ask about anyone else's.
|
|
58
|
+
*/
|
|
59
|
+
listSubscribers(options = {}) {
|
|
60
|
+
return request(this.transport, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
path: "/subscriptions",
|
|
63
|
+
query: { status: options.status, before: options.before, limit: options.limit },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Every subscriber, paged through for you.
|
|
68
|
+
*
|
|
69
|
+
* An async generator rather than an array: a large subscriber base is
|
|
70
|
+
* exactly where an accidental full materialisation hurts, and `for await`
|
|
71
|
+
* reads the same as a loop over one page.
|
|
72
|
+
*/
|
|
73
|
+
async *iterateSubscribers(options = {}) {
|
|
74
|
+
let before;
|
|
75
|
+
do {
|
|
76
|
+
const page = await this.listSubscribers({
|
|
77
|
+
status: options.status,
|
|
78
|
+
limit: options.pageSize,
|
|
79
|
+
before,
|
|
80
|
+
});
|
|
81
|
+
for (const item of page.items)
|
|
82
|
+
yield item;
|
|
83
|
+
before = page.nextBefore ?? undefined;
|
|
84
|
+
} while (before);
|
|
85
|
+
}
|
|
45
86
|
}
|
|
@@ -66,7 +66,8 @@ export class IdentityResource {
|
|
|
66
66
|
sessionTokenExpiresIn: wire.session_token_expires_in,
|
|
67
67
|
user: wire.user,
|
|
68
68
|
subscription: wire.subscription,
|
|
69
|
-
|
|
69
|
+
applicationRole: wire.applicationRole,
|
|
70
|
+
audience: wire.audience,
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
@@ -136,7 +137,8 @@ export class IdentityResource {
|
|
|
136
137
|
return {
|
|
137
138
|
user: wire.user,
|
|
138
139
|
subscription: wire.subscription,
|
|
139
|
-
|
|
140
|
+
applicationRole: wire.applicationRole,
|
|
141
|
+
audience: wire.audience,
|
|
140
142
|
sessionExpiresAt: wire.session_expires_at,
|
|
141
143
|
};
|
|
142
144
|
}
|
|
@@ -145,7 +147,7 @@ export class IdentityResource {
|
|
|
145
147
|
* rest of its ~15-day life. Discard your own copy of `sessionToken` too;
|
|
146
148
|
* this does not do that for you.
|
|
147
149
|
*
|
|
148
|
-
* `scope` defaults to `"device"`: this sign-in, every other
|
|
150
|
+
* `scope` defaults to `"device"`: this sign-in, every other application signed
|
|
149
151
|
* into from the same browser, and Connect's own session on that one device.
|
|
150
152
|
* `"all"` ends every session on every device - an explicit "sign out
|
|
151
153
|
* everywhere" action only.
|
|
@@ -177,7 +179,7 @@ export class IdentityResource {
|
|
|
177
179
|
throw new ConnectError({
|
|
178
180
|
status: 0,
|
|
179
181
|
code: "missing_client_id",
|
|
180
|
-
message: "identity.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this
|
|
182
|
+
message: "identity.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this application's Integration tab, under Connect client.",
|
|
181
183
|
endpoint: "identity",
|
|
182
184
|
});
|
|
183
185
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { request } from "../http.js";
|
|
2
2
|
/**
|
|
3
3
|
* Composio-hosted third-party connections (Google Calendar, Slack,
|
|
4
|
-
* Twitter/X, ...)
|
|
5
|
-
* here - your
|
|
4
|
+
* Twitter/X, ...) a workspace has connected. Connect proxies every call
|
|
5
|
+
* here - your application never sees or holds the underlying OAuth token, which
|
|
6
6
|
* stays with Composio.
|
|
7
7
|
*/
|
|
8
8
|
export class IntegrationsResource {
|
|
@@ -10,12 +10,12 @@ export class IntegrationsResource {
|
|
|
10
10
|
constructor(transport) {
|
|
11
11
|
this.transport = transport;
|
|
12
12
|
}
|
|
13
|
-
/** Enabled toolkits and this
|
|
14
|
-
async list(
|
|
13
|
+
/** Enabled toolkits and this workspace's connection status for each. */
|
|
14
|
+
async list(workspaceId) {
|
|
15
15
|
const result = await request(this.transport, {
|
|
16
16
|
method: "GET",
|
|
17
17
|
path: "/integrations",
|
|
18
|
-
query: {
|
|
18
|
+
query: { workspaceId },
|
|
19
19
|
});
|
|
20
20
|
// `?? []` for the same reason `AppsResource.list` has it - a collection
|
|
21
21
|
// every caller immediately maps or measures.
|
|
@@ -32,31 +32,31 @@ export class IntegrationsResource {
|
|
|
32
32
|
* itself. Reconnecting a disconnected/failed toolkit calls this same
|
|
33
33
|
* method - there is no separate reconnect.
|
|
34
34
|
*
|
|
35
|
-
* Meant to be called on behalf of a signed-in
|
|
35
|
+
* Meant to be called on behalf of a signed-in workspace admin (this is what the
|
|
36
36
|
* bridge's fixed `POST /integrations/connect` route does), not as a bare
|
|
37
37
|
* background action with no admin present to complete the OAuth screen.
|
|
38
38
|
*/
|
|
39
|
-
async initiateConnect(
|
|
39
|
+
async initiateConnect(workspaceId, toolkitSlug, returnUrl) {
|
|
40
40
|
return request(this.transport, {
|
|
41
41
|
method: "POST",
|
|
42
42
|
path: "/integrations/connect",
|
|
43
|
-
json: {
|
|
43
|
+
json: { workspaceId, toolkitSlug, returnUrl },
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
/** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
|
|
47
|
-
async disconnect(
|
|
47
|
+
async disconnect(workspaceId, toolkitSlug) {
|
|
48
48
|
return request(this.transport, {
|
|
49
49
|
method: "POST",
|
|
50
50
|
path: "/integrations/disconnect",
|
|
51
|
-
json: {
|
|
51
|
+
json: { workspaceId, toolkitSlug },
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
/** Calls a Composio action against this
|
|
55
|
-
async execute(
|
|
54
|
+
/** Calls a Composio action against this workspace's connected account for the named toolkit. */
|
|
55
|
+
async execute(workspaceId, input) {
|
|
56
56
|
return request(this.transport, {
|
|
57
57
|
method: "POST",
|
|
58
58
|
path: "/integrations/actions/execute",
|
|
59
|
-
json: {
|
|
59
|
+
json: { workspaceId, ...input },
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { request } from "../http.js";
|
|
2
|
-
/** Push feature events into the Console's audit log, filterable to your
|
|
2
|
+
/** Push feature events into the Console's audit log, filterable to your application. */
|
|
3
3
|
export class LogsResource {
|
|
4
4
|
transport;
|
|
5
5
|
constructor(transport) {
|
|
@@ -11,9 +11,9 @@ export class LogsResource {
|
|
|
11
11
|
* reported through the return value.
|
|
12
12
|
*
|
|
13
13
|
* Pass `sessionToken` to attribute the event to the end user it belongs to.
|
|
14
|
-
* Both scopes on the stored entry come from something verified - the
|
|
14
|
+
* Both scopes on the stored entry come from something verified - the application
|
|
15
15
|
* from your API key, the user from that token, which Connect checks was
|
|
16
|
-
* minted for your
|
|
16
|
+
* minted for your application before attributing anything. `attributed` in the
|
|
17
17
|
* result says whether it resolved; a `false` there still means the line was
|
|
18
18
|
* stored, just without a user on it.
|
|
19
19
|
*/
|
|
@@ -5,11 +5,11 @@ import { request } from "../http.js";
|
|
|
5
5
|
* timestamps - the same list Connect's own account settings page shows the
|
|
6
6
|
* user themselves.
|
|
7
7
|
*
|
|
8
|
-
* **Account-wide, not scoped to this
|
|
9
|
-
* browser's login to Connect, and it fans out to every partner
|
|
10
|
-
* `sessionToken` - Connect has no per-
|
|
8
|
+
* **Account-wide, not scoped to this application.** A Connect session is one
|
|
9
|
+
* browser's login to Connect, and it fans out to every partner application's own
|
|
10
|
+
* `sessionToken` - Connect has no per-application session concept to filter by.
|
|
11
11
|
* `list()` returns every session on the account, including ones that
|
|
12
|
-
* originated from a completely different, unrelated
|
|
12
|
+
* originated from a completely different, unrelated application. Requires the
|
|
13
13
|
* `sessions:manage` scope on your API key - unlike `identity`'s base `sso`
|
|
14
14
|
* capability, this is never granted to a zero-scope legacy key by default.
|
|
15
15
|
*
|
|
@@ -35,7 +35,7 @@ export class SessionsResource {
|
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* Ends one specific session by id - not necessarily the one `sessionToken`
|
|
38
|
-
* itself was minted from. Cascades to every partner-
|
|
38
|
+
* itself was minted from. Cascades to every partner-application sign-in that
|
|
39
39
|
* originated from that same browser, same as Connect's own "sign out this
|
|
40
40
|
* device".
|
|
41
41
|
*/
|
|
@@ -69,7 +69,7 @@ export class SessionsResource {
|
|
|
69
69
|
throw new ConnectError({
|
|
70
70
|
status: 0,
|
|
71
71
|
code: "missing_client_id",
|
|
72
|
-
message: "sessions.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this
|
|
72
|
+
message: "sessions.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this application's Integration tab, under Connect client.",
|
|
73
73
|
endpoint: "sessions",
|
|
74
74
|
});
|
|
75
75
|
}
|
|
@@ -54,10 +54,10 @@ export function verifyConnectWebhook(rawBody, signatureHeader, secret, now = new
|
|
|
54
54
|
* export const POST = createConnectWebhookHandler({
|
|
55
55
|
* secret: process.env.CONNECT_WEBHOOK_SECRET!,
|
|
56
56
|
* on: {
|
|
57
|
-
* "
|
|
58
|
-
* freeze(
|
|
59
|
-
* "
|
|
60
|
-
* "
|
|
57
|
+
* "workspace.deletion_scheduled": ({ workspaceId, purgeAfter }) =>
|
|
58
|
+
* freeze(workspaceId, purgeAfter),
|
|
59
|
+
* "workspace.deletion_cancelled": ({ workspaceId }) => unfreeze(workspaceId),
|
|
60
|
+
* "workspace.deleted": ({ workspaceId }) => purge(workspaceId),
|
|
61
61
|
* },
|
|
62
62
|
* })
|
|
63
63
|
* ```
|
package/dist/esm/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Wire types for the `/api/v1/
|
|
2
|
+
* Wire types for the `/api/v1/application/*` surface - the application-partner API,
|
|
3
3
|
* and the only thing this SDK covers.
|
|
4
4
|
*
|
|
5
5
|
* Hand-written rather than generated so the published package carries no
|
package/dist/theme.css
CHANGED
|
@@ -110,3 +110,90 @@ body {
|
|
|
110
110
|
background: var(--background);
|
|
111
111
|
color: var(--foreground);
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
/* ---------------------------------------------------------------------------
|
|
115
|
+
* Ask AI: Streamdown token compatibility.
|
|
116
|
+
*
|
|
117
|
+
* `ConnectAskAi` renders markdown with Streamdown, which paints using
|
|
118
|
+
* shadcn/ui's token names. HeroUI defines a different set - four happen to line
|
|
119
|
+
* up already (`--background`, `--foreground`, `--border`, `--radius`), and the
|
|
120
|
+
* rest are aliased here so an answer inherits the platform's own theme instead
|
|
121
|
+
* of rendering with missing backgrounds and borders.
|
|
122
|
+
*
|
|
123
|
+
* This lives in the shipped theme rather than in each partner's CSS because it
|
|
124
|
+
* is a fact about how these two libraries name colours, not a design choice
|
|
125
|
+
* anyone should have to rediscover.
|
|
126
|
+
*
|
|
127
|
+
* Additive - HeroUI uses none of these names - with exactly one real collision,
|
|
128
|
+
* handled below.
|
|
129
|
+
* ------------------------------------------------------------------------ */
|
|
130
|
+
:root,
|
|
131
|
+
.light,
|
|
132
|
+
.default,
|
|
133
|
+
[data-theme="light"],
|
|
134
|
+
[data-theme="default"],
|
|
135
|
+
.dark,
|
|
136
|
+
[data-theme="dark"] {
|
|
137
|
+
--card: var(--surface);
|
|
138
|
+
--card-foreground: var(--surface-foreground);
|
|
139
|
+
--input: var(--border);
|
|
140
|
+
--primary: var(--accent);
|
|
141
|
+
--primary-foreground: var(--accent-foreground);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ---------------------------------------------------------------------------
|
|
145
|
+
* The collision: `--muted` is a *surface* in shadcn (code-block fills, table
|
|
146
|
+
* header rows) and a *text colour* in HeroUI. Reading it as HeroUI's would
|
|
147
|
+
* paint every code block mid-grey and leave the text on it unreadable.
|
|
148
|
+
*
|
|
149
|
+
* So it is shadowed - but only inside the answer body, and only after the host
|
|
150
|
+
* element has captured HeroUI's value under shadcn's name for it. Splitting it
|
|
151
|
+
* across two elements is what makes this safe: `--muted-foreground` is computed
|
|
152
|
+
* on `.connect-ai-host`, where `--muted` still means HeroUI's text grey, and
|
|
153
|
+
* inherits that resolved colour downward. Both declarations on one element
|
|
154
|
+
* would resolve `var(--muted)` against the new value and lose the original.
|
|
155
|
+
* ------------------------------------------------------------------------ */
|
|
156
|
+
.connect-ai-host {
|
|
157
|
+
--muted-foreground: var(--muted);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.connect-ai-host .connect-ai-body {
|
|
161
|
+
--muted: var(--surface-secondary);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ---------------------------------------------------------------------------
|
|
165
|
+
* The Ask AI "thinking" shimmer - a highlight sweeping through the label while
|
|
166
|
+
* the model works, rather than a spinner.
|
|
167
|
+
*
|
|
168
|
+
* A gradient clipped to the glyphs, so the motion is *in* the word instead of
|
|
169
|
+
* beside it. This is the one state where the panel has nothing at all to show:
|
|
170
|
+
* retrieval and the model's first thought take a second or two, and a static
|
|
171
|
+
* "Thinking" reads as frozen.
|
|
172
|
+
* ------------------------------------------------------------------------ */
|
|
173
|
+
.connect-ai-shimmer {
|
|
174
|
+
background: linear-gradient(100deg, var(--muted) 32%, var(--accent) 48%, var(--muted) 64%);
|
|
175
|
+
background-size: 220% 100%;
|
|
176
|
+
-webkit-background-clip: text;
|
|
177
|
+
background-clip: text;
|
|
178
|
+
color: transparent;
|
|
179
|
+
animation: connect-ai-shimmer 1.7s linear infinite;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@keyframes connect-ai-shimmer {
|
|
183
|
+
from {
|
|
184
|
+
background-position: 180% 0;
|
|
185
|
+
}
|
|
186
|
+
to {
|
|
187
|
+
background-position: -40% 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Motion is the whole affordance here, so with it suppressed the label has to
|
|
192
|
+
go back to being a legible static word rather than a transparent one. */
|
|
193
|
+
@media (prefers-reduced-motion: reduce) {
|
|
194
|
+
.connect-ai-shimmer {
|
|
195
|
+
background: none;
|
|
196
|
+
color: var(--muted);
|
|
197
|
+
animation: none;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -3,8 +3,14 @@ import type { ReadinessBlocker } from "../../types.js";
|
|
|
3
3
|
export type ConnectAccessGateState = "loading"
|
|
4
4
|
/** No signed-in user - not this component's concern, see the doc comment on `ConnectAccessGate`. */
|
|
5
5
|
| "signed_out"
|
|
6
|
-
/** `
|
|
6
|
+
/** `workspace_suspended` - a platform admin took this workspace offline. Hard block, no dismiss. */
|
|
7
7
|
| "suspended"
|
|
8
|
+
/**
|
|
9
|
+
* `audience_mismatch` - this application serves individual accounts and the
|
|
10
|
+
* workspace is a business, or the reverse. Hard block with nothing to
|
|
11
|
+
* offer: `Workspace.type` is fixed at sign-up.
|
|
12
|
+
*/
|
|
13
|
+
| "audience_mismatch"
|
|
8
14
|
/** `subscription_missing` - nothing to subscribe to yet. Hard block. */
|
|
9
15
|
| "no_subscription"
|
|
10
16
|
/** `onboarding_approval_pending` - waiting on a reviewer. Hard block, no action to offer. */
|
|
@@ -33,9 +39,17 @@ export interface ConnectAccessGateResult {
|
|
|
33
39
|
* `ConnectReadinessGate`/`ConnectReadinessBanner` already use. `ConnectAccessGate`
|
|
34
40
|
* is a priority ordering over the same data, not a second opinion about it.
|
|
35
41
|
*
|
|
36
|
-
* Priority, first match wins: `suspended` > `
|
|
37
|
-
* `onboarding_pending` > any other blocking code
|
|
38
|
-
* `payment_retry` (advisory - `children` still render) >
|
|
42
|
+
* Priority, first match wins: `suspended` > `audience_mismatch` >
|
|
43
|
+
* `no_subscription` > `onboarding_pending` > any other blocking code
|
|
44
|
+
* (`blocked`) > `payment_retry` (advisory - `children` still render) >
|
|
45
|
+
* `clear`.
|
|
46
|
+
*
|
|
47
|
+
* `audience_mismatch` outranks `no_subscription` deliberately, and the
|
|
48
|
+
* ordering is the whole point of it: a workspace of the wrong account type
|
|
49
|
+
* carries BOTH blockers at once, and showing the subscription one first sent
|
|
50
|
+
* the customer to a plan picker whose subscribe call
|
|
51
|
+
* (`ActivateSubscriptionUseCase`) rejects them for the audience anyway. The
|
|
52
|
+
* account type is the reason; the missing subscription is a consequence.
|
|
39
53
|
*/
|
|
40
54
|
export declare function useConnectAccessGate(): ConnectAccessGateResult;
|
|
41
55
|
export interface ConnectAccessGateProps {
|
|
@@ -10,7 +10,7 @@ export interface ConnectAccountSessionsProps {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Every device signed into this Connect account - not scoped to this
|
|
13
|
-
*
|
|
13
|
+
* application, see `ConnectUserSession`'s doc comment. Owns the one
|
|
14
14
|
* `useConnectSessions()` call for this modal; every revoke action here
|
|
15
15
|
* mutates through it and re-reads the list itself, since nothing else
|
|
16
16
|
* invalidates it automatically - see `mutateResource`'s doc comment.
|