@cosmoops/connect-sdk 0.23.0 → 0.24.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 CHANGED
@@ -3,6 +3,43 @@
3
3
  `@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
4
4
  versions may carry breaking changes** and patch versions never do.
5
5
 
6
+ ## 0.24.0
7
+
8
+ ### Fixed
9
+
10
+ - **`connect.integrations.initiateConnect()` could never succeed.** Connect's
11
+ `POST /integrations/connect` requires a `userId` - it stores it as the
12
+ connection's `connectedByUserId` - and the SDK sent `workspaceId`,
13
+ `toolkitSlug` and `returnUrl` and nothing else. Every call 400'd, including
14
+ the one `createConnectHandler`'s own `POST /integrations/connect` route
15
+ makes, so **`<ConnectIntegrationsModal />`'s Connect button was broken too**:
16
+ the browser reached the bridge, the bridge reached Connect, and Connect
17
+ refused with `Invalid input: expected string, received undefined` - a Zod
18
+ message that names no field, which is most of why this survived.
19
+
20
+ The conformance suite pinned the *response* shape and not the request, so
21
+ nothing failed the build. It now pins the request body against the
22
+ use-case command, and `src/conformance.test.ts` fails to compile if the
23
+ route grows a fourth required field.
24
+
25
+ ### Changed
26
+
27
+ - **`initiateConnect` takes an input object** - `initiateConnect(workspaceId,
28
+ { toolkitSlug, returnUrl, userId })`, replacing the three positional
29
+ strings. Breaking in name only: the old signature had no working call to
30
+ break. The object shape matches `execute()` on the same resource, and it is
31
+ what keeps `returnUrl` and `userId` from being silently transposable.
32
+
33
+ `userId` is the signed-in member starting the connection. Take it from your
34
+ own session, never from a form: the toolkit connection is shared by the
35
+ whole workspace, so this attribution is the only record of who turned it on.
36
+ The bridge already does this for you - it reads the session's user, and a
37
+ `userId` in the request body is ignored, the same way a crafted
38
+ `workspaceId` always was.
39
+
40
+ - **`InitiateIntegrationConnectionInput` is exported** from
41
+ `@cosmoops/connect-sdk/server`, beside `ExecuteIntegrationActionInput`.
42
+
6
43
  ## 0.23.0
7
44
 
8
45
  ### Added
@@ -161,4 +161,4 @@ class ConnectClient {
161
161
  }
162
162
  exports.ConnectClient = ConnectClient;
163
163
  /** Kept in step with `package.json` by `sdk.test.ts`. */
164
- exports.SDK_VERSION = "0.23.0";
164
+ exports.SDK_VERSION = "0.24.0";
@@ -605,8 +605,14 @@ function createConnectHandler(options) {
605
605
  const { workspaceId } = await requireWorkspace(request);
606
606
  return json({ toolkits: await client.integrations.list(workspaceId) });
607
607
  },
608
+ /*
609
+ * `userId` comes from the session alongside `workspaceId`, never from the
610
+ * body - it is stored as `connectedByUserId`, so letting the browser name
611
+ * it would let anyone sign a workspace-shared connection with someone
612
+ * else's name.
613
+ */
608
614
  "POST /integrations/connect": async (request) => {
609
- const { workspaceId } = await requireWorkspace(request);
615
+ const { workspaceId, user } = await requireWorkspace(request);
610
616
  const body = (await request.json().catch(() => null));
611
617
  if (!body?.toolkitSlug || !body?.returnUrl) {
612
618
  throw new errors_js_1.ConnectError({
@@ -616,7 +622,11 @@ function createConnectHandler(options) {
616
622
  endpoint: "POST /integrations/connect",
617
623
  });
618
624
  }
619
- return json(await client.integrations.initiateConnect(workspaceId, body.toolkitSlug, body.returnUrl));
625
+ return json(await client.integrations.initiateConnect(workspaceId, {
626
+ toolkitSlug: body.toolkitSlug,
627
+ returnUrl: body.returnUrl,
628
+ userId: user.userId,
629
+ }));
620
630
  },
621
631
  "POST /integrations/disconnect": async (request) => {
622
632
  const { workspaceId } = await requireWorkspace(request);
@@ -35,15 +35,17 @@ class IntegrationsResource {
35
35
  * itself. Reconnecting a disconnected/failed toolkit calls this same
36
36
  * method - there is no separate reconnect.
37
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.
38
+ * Meant to be called on behalf of a signed-in workspace admin (this is what
39
+ * the bridge's fixed `POST /integrations/connect` route does), not as a bare
40
+ * background action with no admin present to complete the OAuth screen. That
41
+ * is why `userId` is part of the input rather than inferred - see
42
+ * `InitiateIntegrationConnectionInput`.
41
43
  */
42
- async initiateConnect(workspaceId, toolkitSlug, returnUrl) {
44
+ async initiateConnect(workspaceId, input) {
43
45
  return (0, http_js_1.request)(this.transport, {
44
46
  method: "POST",
45
47
  path: "/integrations/connect",
46
- json: { workspaceId, toolkitSlug, returnUrl },
48
+ json: { workspaceId, ...input },
47
49
  });
48
50
  }
49
51
  /** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
@@ -157,4 +157,4 @@ export class ConnectClient {
157
157
  }
158
158
  }
159
159
  /** Kept in step with `package.json` by `sdk.test.ts`. */
160
- export const SDK_VERSION = "0.23.0";
160
+ export const SDK_VERSION = "0.24.0";
@@ -602,8 +602,14 @@ export function createConnectHandler(options) {
602
602
  const { workspaceId } = await requireWorkspace(request);
603
603
  return json({ toolkits: await client.integrations.list(workspaceId) });
604
604
  },
605
+ /*
606
+ * `userId` comes from the session alongside `workspaceId`, never from the
607
+ * body - it is stored as `connectedByUserId`, so letting the browser name
608
+ * it would let anyone sign a workspace-shared connection with someone
609
+ * else's name.
610
+ */
605
611
  "POST /integrations/connect": async (request) => {
606
- const { workspaceId } = await requireWorkspace(request);
612
+ const { workspaceId, user } = await requireWorkspace(request);
607
613
  const body = (await request.json().catch(() => null));
608
614
  if (!body?.toolkitSlug || !body?.returnUrl) {
609
615
  throw new ConnectError({
@@ -613,7 +619,11 @@ export function createConnectHandler(options) {
613
619
  endpoint: "POST /integrations/connect",
614
620
  });
615
621
  }
616
- return json(await client.integrations.initiateConnect(workspaceId, body.toolkitSlug, body.returnUrl));
622
+ return json(await client.integrations.initiateConnect(workspaceId, {
623
+ toolkitSlug: body.toolkitSlug,
624
+ returnUrl: body.returnUrl,
625
+ userId: user.userId,
626
+ }));
617
627
  },
618
628
  "POST /integrations/disconnect": async (request) => {
619
629
  const { workspaceId } = await requireWorkspace(request);
@@ -32,15 +32,17 @@ 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 workspace admin (this is what the
36
- * bridge's fixed `POST /integrations/connect` route does), not as a bare
37
- * background action with no admin present to complete the OAuth screen.
35
+ * Meant to be called on behalf of a signed-in workspace admin (this is what
36
+ * the bridge's fixed `POST /integrations/connect` route does), not as a bare
37
+ * background action with no admin present to complete the OAuth screen. That
38
+ * is why `userId` is part of the input rather than inferred - see
39
+ * `InitiateIntegrationConnectionInput`.
38
40
  */
39
- async initiateConnect(workspaceId, toolkitSlug, returnUrl) {
41
+ async initiateConnect(workspaceId, input) {
40
42
  return request(this.transport, {
41
43
  method: "POST",
42
44
  path: "/integrations/connect",
43
- json: { workspaceId, toolkitSlug, returnUrl },
45
+ json: { workspaceId, ...input },
44
46
  });
45
47
  }
46
48
  /** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
@@ -123,4 +123,4 @@ export declare class ConnectClient {
123
123
  constructor(options: ConnectClientOptions);
124
124
  }
125
125
  /** Kept in step with `package.json` by `sdk.test.ts`. */
126
- export declare const SDK_VERSION = "0.23.0";
126
+ export declare const SDK_VERSION = "0.24.0";
@@ -26,7 +26,7 @@ export type { OtpResource } from "./resources/otp.js";
26
26
  export type { LogsResource } from "./resources/logs.js";
27
27
  export type { AnalyticsResource } from "./resources/analytics.js";
28
28
  export type { SessionsResource } from "./resources/sessions.js";
29
- export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./resources/integrations.js";
29
+ export type { ExecuteIntegrationActionInput, InitiateIntegrationConnectionInput, IntegrationsResource, } from "./resources/integrations.js";
30
30
  export type { PlatformResource } from "./resources/platform.js";
31
31
  export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, RescheduleBookingInput, } from "./resources/scheduling.js";
32
32
  export type { ContactsResource, LookupContactInput } from "./resources/contacts.js";
@@ -1,5 +1,19 @@
1
1
  import { type TransportOptions } from "../http.js";
2
2
  import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationsListResponse } from "../../types.js";
3
+ export interface InitiateIntegrationConnectionInput {
4
+ toolkitSlug: string;
5
+ returnUrl: string;
6
+ /**
7
+ * The signed-in member starting the connection, recorded by Connect as
8
+ * `connectedByUserId`.
9
+ *
10
+ * Required, and deliberately not defaulted: the connection is *shared by
11
+ * the whole workspace*, so "who turned this on" is the only attribution
12
+ * anyone gets - and a call that cannot name a person is one where nobody
13
+ * is present to finish the OAuth screen either.
14
+ */
15
+ userId: string;
16
+ }
3
17
  export interface ExecuteIntegrationActionInput {
4
18
  toolkitSlug: string;
5
19
  actionSlug: string;
@@ -28,11 +42,13 @@ export declare class IntegrationsResource {
28
42
  * itself. Reconnecting a disconnected/failed toolkit calls this same
29
43
  * method - there is no separate reconnect.
30
44
  *
31
- * Meant to be called on behalf of a signed-in workspace admin (this is what the
32
- * bridge's fixed `POST /integrations/connect` route does), not as a bare
33
- * background action with no admin present to complete the OAuth screen.
45
+ * Meant to be called on behalf of a signed-in workspace admin (this is what
46
+ * the bridge's fixed `POST /integrations/connect` route does), not as a bare
47
+ * background action with no admin present to complete the OAuth screen. That
48
+ * is why `userId` is part of the input rather than inferred - see
49
+ * `InitiateIntegrationConnectionInput`.
34
50
  */
35
- initiateConnect(workspaceId: string, toolkitSlug: string, returnUrl: string): Promise<InitiateIntegrationConnectionResponse>;
51
+ initiateConnect(workspaceId: string, input: InitiateIntegrationConnectionInput): Promise<InitiateIntegrationConnectionResponse>;
36
52
  /** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
37
53
  disconnect(workspaceId: string, toolkitSlug: string): Promise<DisconnectIntegrationResponse>;
38
54
  /** Calls a Composio action against this workspace's connected account for the named toolkit. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmoops/connect-sdk",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "commonjs",