@open-connector/sdk 1.0.2
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/LICENSE +661 -0
- package/README.md +72 -0
- package/dist/index-96n68fp1.d.cts +1495 -0
- package/dist/index-96n68fp1.d.cts.map +1 -0
- package/dist/index-BXEKBblV.d.ts +1495 -0
- package/dist/index-BXEKBblV.d.ts.map +1 -0
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/src-19LKPmdg.cjs +118 -0
- package/dist/src-19LKPmdg.cjs.map +1 -0
- package/dist/src-CvNjrj1s.js +89 -0
- package/dist/src-CvNjrj1s.js.map +1 -0
- package/dist/testing.cjs +7 -0
- package/dist/testing.d.cts +2 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +2 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BXEKBblV.d.ts","names":[],"sources":["../../authorization/src/index.ts","../../api-contracts/src/lib/native/index.ts","../src/client.ts","../src/connect.ts"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_src = require("./src-19LKPmdg.cjs");
|
|
3
|
+
exports.connectOAuth = require_src.connectOAuth;
|
|
4
|
+
exports.createClient = require_src.createClient;
|
|
5
|
+
exports.waitForConnection = require_src.waitForConnection;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { c as ClientOptions, i as WaitOptions, l as OpenConnectorClient, n as ConnectOAuthOptions, o as connectOAuth, r as ConnectResult, s as waitForConnection, u as createClient } from "./index-96n68fp1.cjs";
|
|
2
|
+
export { type ClientOptions, type ConnectOAuthOptions, type ConnectResult, type OpenConnectorClient, type WaitOptions, connectOAuth, createClient, waitForConnection };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { c as ClientOptions, i as WaitOptions, l as OpenConnectorClient, n as ConnectOAuthOptions, o as connectOAuth, r as ConnectResult, s as waitForConnection, u as createClient } from "./index-BXEKBblV.js";
|
|
2
|
+
export { type ClientOptions, type ConnectOAuthOptions, type ConnectResult, type OpenConnectorClient, type WaitOptions, connectOAuth, createClient, waitForConnection };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
let _orpc_client = require("@orpc/client");
|
|
2
|
+
let _orpc_client_fetch = require("@orpc/client/fetch");
|
|
3
|
+
let evlog = require("evlog");
|
|
4
|
+
//#region src/client.ts
|
|
5
|
+
/** The native RPC handler is mounted at `/api/v1` (the console appRouter owns
|
|
6
|
+
* `/api/v1/rpc`). Trim any trailing slash so we never produce `//api/v1`. */
|
|
7
|
+
function resolveRpcUrl(baseUrl) {
|
|
8
|
+
return `${baseUrl.replace(/\/+$/, "")}/api/v1`;
|
|
9
|
+
}
|
|
10
|
+
/** Create a typed Open Connector client bound to a Project key or Human OAuth capability. */
|
|
11
|
+
function createClient(opts) {
|
|
12
|
+
const link = new _orpc_client_fetch.RPCLink({
|
|
13
|
+
url: resolveRpcUrl(opts.baseUrl),
|
|
14
|
+
headers: "apiKey" in opts && opts.apiKey ? { "x-api-key": opts.apiKey } : async () => ({
|
|
15
|
+
authorization: `Bearer ${typeof opts.accessToken === "function" ? await opts.accessToken() : opts.accessToken}`,
|
|
16
|
+
"x-organization-id": opts.organizationId,
|
|
17
|
+
"x-project-id": opts.projectId
|
|
18
|
+
}),
|
|
19
|
+
...opts.fetch ? { fetch: opts.fetch } : {}
|
|
20
|
+
});
|
|
21
|
+
return (0, _orpc_client.createORPCClient)(link);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/connect.ts
|
|
25
|
+
/** Statuses that mean "keep waiting" — the OAuth round-trip is still in flight. */
|
|
26
|
+
const PENDING_STATUSES = /* @__PURE__ */ new Set(["pending", "initiated"]);
|
|
27
|
+
/** A connect/wait failure: code `connect.timeout` | `connect.failed`. The last connection status is in the message. */
|
|
28
|
+
function connectError(reason, message) {
|
|
29
|
+
return (0, evlog.createError)({
|
|
30
|
+
code: reason === "timeout" ? "connect.timeout" : "connect.failed",
|
|
31
|
+
message,
|
|
32
|
+
status: reason === "timeout" ? 408 : 502,
|
|
33
|
+
why: reason === "timeout" ? "The connection did not reach `active` before the timeout" : "The connection reached a terminal non-active status",
|
|
34
|
+
fix: reason === "timeout" ? "Increase timeoutMs, or have the user finish provider authorization or credential entry" : "Create a new connect link or re-initiate the connection"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
38
|
+
/**
|
|
39
|
+
* Poll `getConnection` until it reaches `active`, mirroring Composio's
|
|
40
|
+
* `waitForConnection`. Resolves with the active connection id; throws an
|
|
41
|
+
* `EvlogError` (code `connect.failed` | `connect.timeout`) on a terminal-failure status or timeout.
|
|
42
|
+
*/
|
|
43
|
+
async function waitForConnection(client, connectionId, opts = {}) {
|
|
44
|
+
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
45
|
+
const intervalMs = opts.intervalMs ?? 2e3;
|
|
46
|
+
const now = opts.now ?? Date.now;
|
|
47
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
48
|
+
const deadline = now() + timeoutMs;
|
|
49
|
+
for (;;) {
|
|
50
|
+
const conn = await client.getConnection({ connectionId });
|
|
51
|
+
if (conn.status === "active") return {
|
|
52
|
+
connectionId,
|
|
53
|
+
status: "active"
|
|
54
|
+
};
|
|
55
|
+
if (!PENDING_STATUSES.has(conn.status)) throw connectError("failed", `connection ${connectionId} is ${conn.status}`);
|
|
56
|
+
if (now() >= deadline) throw connectError("timeout", `timed out after ${timeoutMs}ms waiting for connection ${connectionId} (last status: ${conn.status})`);
|
|
57
|
+
await sleep(intervalMs);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Begin an OAuth2 connection and (by default) wait for it to become active.
|
|
62
|
+
* The caller is responsible for getting the user to the `redirectUrl` (the CLI
|
|
63
|
+
* opens it / prints it). With `waitForActive: false` it returns immediately
|
|
64
|
+
* after `initiate`.
|
|
65
|
+
*/
|
|
66
|
+
async function connectOAuth(client, opts) {
|
|
67
|
+
const init = await client.initiate({
|
|
68
|
+
slug: opts.slug,
|
|
69
|
+
authConfigId: opts.authConfigId,
|
|
70
|
+
authMethodId: opts.authMethodId,
|
|
71
|
+
userId: opts.userId,
|
|
72
|
+
scopes: opts.scopes
|
|
73
|
+
});
|
|
74
|
+
if (init.redirectUrl) opts.onRedirect?.(init.redirectUrl);
|
|
75
|
+
if (opts.waitForActive === false) return {
|
|
76
|
+
connectionId: init.connectionId,
|
|
77
|
+
status: "pending",
|
|
78
|
+
redirectUrl: init.redirectUrl
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
connectionId: (await waitForConnection(client, init.connectionId, opts)).connectionId,
|
|
82
|
+
status: "active",
|
|
83
|
+
redirectUrl: init.redirectUrl
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
Object.defineProperty(exports, "connectError", {
|
|
88
|
+
enumerable: true,
|
|
89
|
+
get: function() {
|
|
90
|
+
return connectError;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
Object.defineProperty(exports, "connectOAuth", {
|
|
94
|
+
enumerable: true,
|
|
95
|
+
get: function() {
|
|
96
|
+
return connectOAuth;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
Object.defineProperty(exports, "createClient", {
|
|
100
|
+
enumerable: true,
|
|
101
|
+
get: function() {
|
|
102
|
+
return createClient;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
Object.defineProperty(exports, "resolveRpcUrl", {
|
|
106
|
+
enumerable: true,
|
|
107
|
+
get: function() {
|
|
108
|
+
return resolveRpcUrl;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
Object.defineProperty(exports, "waitForConnection", {
|
|
112
|
+
enumerable: true,
|
|
113
|
+
get: function() {
|
|
114
|
+
return waitForConnection;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=src-19LKPmdg.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-19LKPmdg.cjs","names":["RPCLink","createORPCClient","createError"],"sources":["../src/client.ts","../src/connect.ts"],"sourcesContent":["import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { NativeContract } from \"@open-connector/api-contracts\";\nimport type { ContractRouterClient } from \"@orpc/contract\";\n\n/**\n * The first-party Open Connector SDK client — a fully-typed `@orpc/client`\n * over the native oRPC contract (`/api/v1`). The published declarations bundle\n * this contract so consumers do not need any private workspace packages.\n */\nexport type OpenConnectorClient = ContractRouterClient<NativeContract>;\n\ninterface BaseClientOptions {\n /** Base URL of the Open Connector server, e.g. `http://localhost:3000`. */\n baseUrl: string;\n /** Optional fetch override (used by tests to route into an in-process app). */\n fetch?: typeof fetch;\n}\n\nexport type ClientOptions = BaseClientOptions &\n (\n | {\n /** Project (agent) API key — sent as the `x-api-key` header. */\n apiKey: string;\n accessToken?: never;\n organizationId?: never;\n projectId?: never;\n }\n | {\n apiKey?: never;\n /** Human OAuth token, or an async provider that refreshes it before each request. */\n accessToken: string | (() => string | Promise<string>);\n /** Explicit live Organization selected by the Human client. */\n organizationId: string;\n /** Explicit live Project selected by the Human client. */\n projectId: string;\n }\n );\n\n/** The native RPC handler is mounted at `/api/v1` (the console appRouter owns\n * `/api/v1/rpc`). Trim any trailing slash so we never produce `//api/v1`. */\nexport function resolveRpcUrl(baseUrl: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}/api/v1`;\n}\n\n/** Create a typed Open Connector client bound to a Project key or Human OAuth capability. */\nexport function createClient(opts: ClientOptions): OpenConnectorClient {\n const link = new RPCLink({\n url: resolveRpcUrl(opts.baseUrl),\n headers:\n \"apiKey\" in opts && opts.apiKey\n ? { \"x-api-key\": opts.apiKey }\n : async () => ({\n authorization: `Bearer ${\n typeof opts.accessToken === \"function\" ? await opts.accessToken() : opts.accessToken\n }`,\n \"x-organization-id\": opts.organizationId,\n \"x-project-id\": opts.projectId,\n }),\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n });\n return createORPCClient(link);\n}\n","import { createError, type EvlogError } from \"evlog\";\n\nimport type { OpenConnectorClient } from \"./client\";\n\n/** Statuses that mean \"keep waiting\" — the OAuth round-trip is still in flight. */\nconst PENDING_STATUSES = new Set([\"pending\", \"initiated\"]);\n\nexport type ConnectErrorReason = \"timeout\" | \"failed\";\n\n/** A connect/wait failure: code `connect.timeout` | `connect.failed`. The last connection status is in the message. */\nexport function connectError(reason: ConnectErrorReason, message: string): EvlogError {\n return createError({\n code: reason === \"timeout\" ? \"connect.timeout\" : \"connect.failed\",\n message,\n status: reason === \"timeout\" ? 408 : 502,\n why:\n reason === \"timeout\"\n ? \"The connection did not reach `active` before the timeout\"\n : \"The connection reached a terminal non-active status\",\n fix:\n reason === \"timeout\"\n ? \"Increase timeoutMs, or have the user finish provider authorization or credential entry\"\n : \"Create a new connect link or re-initiate the connection\",\n });\n}\n\nexport interface WaitOptions {\n /** Give up after this many ms (default 120_000). */\n timeoutMs?: number;\n /** Poll cadence in ms (default 2_000). */\n intervalMs?: number;\n /** Injectable clock + sleep so the loop is deterministic under test. */\n now?: () => number;\n sleep?: (ms: number) => Promise<void>;\n}\n\n// Only the two methods the wait loop calls — lets callers (and tests) pass a\n// narrower object than the full client.\ntype ConnectClient = Pick<OpenConnectorClient, \"initiate\" | \"getConnection\">;\n\nconst defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/**\n * Poll `getConnection` until it reaches `active`, mirroring Composio's\n * `waitForConnection`. Resolves with the active connection id; throws an\n * `EvlogError` (code `connect.failed` | `connect.timeout`) on a terminal-failure status or timeout.\n */\nexport async function waitForConnection(\n client: ConnectClient,\n connectionId: string,\n opts: WaitOptions = {},\n): Promise<{ connectionId: string; status: \"active\" }> {\n const timeoutMs = opts.timeoutMs ?? 120_000;\n const intervalMs = opts.intervalMs ?? 2_000;\n const now = opts.now ?? Date.now;\n const sleep = opts.sleep ?? defaultSleep;\n const deadline = now() + timeoutMs;\n\n for (;;) {\n const conn = await client.getConnection({ connectionId });\n if (conn.status === \"active\") return { connectionId, status: \"active\" };\n if (!PENDING_STATUSES.has(conn.status)) {\n throw connectError(\"failed\", `connection ${connectionId} is ${conn.status}`);\n }\n if (now() >= deadline) {\n throw connectError(\n \"timeout\",\n `timed out after ${timeoutMs}ms waiting for connection ${connectionId} (last status: ${conn.status})`,\n );\n }\n await sleep(intervalMs);\n }\n}\n\nexport interface ConnectOAuthOptions extends WaitOptions {\n slug: string;\n authConfigId?: string;\n authMethodId?: string;\n /** Project-local connector business partition; independent from the authenticated Human. */\n userId: string;\n scopes?: string[];\n /** Called with the provider authorize URL the user must visit (if any). */\n onRedirect?: (url: string) => void;\n /** When true (default), poll until the connection is active before resolving. */\n waitForActive?: boolean;\n}\n\nexport interface ConnectResult {\n connectionId: string;\n status: \"pending\" | \"active\";\n redirectUrl?: string;\n}\n\n/**\n * Begin an OAuth2 connection and (by default) wait for it to become active.\n * The caller is responsible for getting the user to the `redirectUrl` (the CLI\n * opens it / prints it). With `waitForActive: false` it returns immediately\n * after `initiate`.\n */\nexport async function connectOAuth(\n client: ConnectClient,\n opts: ConnectOAuthOptions,\n): Promise<ConnectResult> {\n const init = await client.initiate({\n slug: opts.slug,\n authConfigId: opts.authConfigId,\n authMethodId: opts.authMethodId,\n userId: opts.userId,\n scopes: opts.scopes,\n });\n if (init.redirectUrl) opts.onRedirect?.(init.redirectUrl);\n\n if (opts.waitForActive === false) {\n return { connectionId: init.connectionId, status: \"pending\", redirectUrl: init.redirectUrl };\n }\n const done = await waitForConnection(client, init.connectionId, opts);\n return { connectionId: done.connectionId, status: \"active\", redirectUrl: init.redirectUrl };\n}\n"],"mappings":";;;;;;AAyCA,SAAgB,cAAc,SAAyB;CACrD,OAAO,GAAG,QAAQ,QAAQ,QAAQ,EAAE,EAAE;AACxC;;AAGA,SAAgB,aAAa,MAA0C;CACrE,MAAM,OAAO,IAAIA,mBAAAA,QAAQ;EACvB,KAAK,cAAc,KAAK,OAAO;EAC/B,SACE,YAAY,QAAQ,KAAK,SACrB,EAAE,aAAa,KAAK,OAAO,IAC3B,aAAa;GACX,eAAe,UACb,OAAO,KAAK,gBAAgB,aAAa,MAAM,KAAK,YAAY,IAAI,KAAK;GAE3E,qBAAqB,KAAK;GAC1B,gBAAgB,KAAK;EACvB;EACN,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;CAC5C,CAAC;CACD,QAAA,GAAOC,aAAAA,iBAAAA,CAAiB,IAAI;AAC9B;;;;ACzDA,MAAM,mCAAmB,IAAI,IAAI,CAAC,WAAW,WAAW,CAAC;;AAKzD,SAAgB,aAAa,QAA4B,SAA6B;CACpF,QAAA,GAAOC,MAAAA,YAAAA,CAAY;EACjB,MAAM,WAAW,YAAY,oBAAoB;EACjD;EACA,QAAQ,WAAW,YAAY,MAAM;EACrC,KACE,WAAW,YACP,6DACA;EACN,KACE,WAAW,YACP,2FACA;CACR,CAAC;AACH;AAgBA,MAAM,gBAAgB,OAAe,IAAI,SAAe,MAAM,WAAW,GAAG,EAAE,CAAC;;;;;;AAO/E,eAAsB,kBACpB,QACA,cACA,OAAoB,CAAC,GACgC;CACrD,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,WAAW,IAAI,IAAI;CAEzB,SAAS;EACP,MAAM,OAAO,MAAM,OAAO,cAAc,EAAE,aAAa,CAAC;EACxD,IAAI,KAAK,WAAW,UAAU,OAAO;GAAE;GAAc,QAAQ;EAAS;EACtE,IAAI,CAAC,iBAAiB,IAAI,KAAK,MAAM,GACnC,MAAM,aAAa,UAAU,cAAc,aAAa,MAAM,KAAK,QAAQ;EAE7E,IAAI,IAAI,KAAK,UACX,MAAM,aACJ,WACA,mBAAmB,UAAU,4BAA4B,aAAa,iBAAiB,KAAK,OAAO,EACrG;EAEF,MAAM,MAAM,UAAU;CACxB;AACF;;;;;;;AA2BA,eAAsB,aACpB,QACA,MACwB;CACxB,MAAM,OAAO,MAAM,OAAO,SAAS;EACjC,MAAM,KAAK;EACX,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,QAAQ,KAAK;EACb,QAAQ,KAAK;CACf,CAAC;CACD,IAAI,KAAK,aAAa,KAAK,aAAa,KAAK,WAAW;CAExD,IAAI,KAAK,kBAAkB,OACzB,OAAO;EAAE,cAAc,KAAK;EAAc,QAAQ;EAAW,aAAa,KAAK;CAAY;CAG7F,OAAO;EAAE,eAAc,MADJ,kBAAkB,QAAQ,KAAK,cAAc,IAAI,EAAA,CACxC;EAAc,QAAQ;EAAU,aAAa,KAAK;CAAY;AAC5F"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createORPCClient } from "@orpc/client";
|
|
2
|
+
import { RPCLink } from "@orpc/client/fetch";
|
|
3
|
+
import { createError } from "evlog";
|
|
4
|
+
//#region src/client.ts
|
|
5
|
+
/** The native RPC handler is mounted at `/api/v1` (the console appRouter owns
|
|
6
|
+
* `/api/v1/rpc`). Trim any trailing slash so we never produce `//api/v1`. */
|
|
7
|
+
function resolveRpcUrl(baseUrl) {
|
|
8
|
+
return `${baseUrl.replace(/\/+$/, "")}/api/v1`;
|
|
9
|
+
}
|
|
10
|
+
/** Create a typed Open Connector client bound to a Project key or Human OAuth capability. */
|
|
11
|
+
function createClient(opts) {
|
|
12
|
+
const link = new RPCLink({
|
|
13
|
+
url: resolveRpcUrl(opts.baseUrl),
|
|
14
|
+
headers: "apiKey" in opts && opts.apiKey ? { "x-api-key": opts.apiKey } : async () => ({
|
|
15
|
+
authorization: `Bearer ${typeof opts.accessToken === "function" ? await opts.accessToken() : opts.accessToken}`,
|
|
16
|
+
"x-organization-id": opts.organizationId,
|
|
17
|
+
"x-project-id": opts.projectId
|
|
18
|
+
}),
|
|
19
|
+
...opts.fetch ? { fetch: opts.fetch } : {}
|
|
20
|
+
});
|
|
21
|
+
return createORPCClient(link);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/connect.ts
|
|
25
|
+
/** Statuses that mean "keep waiting" — the OAuth round-trip is still in flight. */
|
|
26
|
+
const PENDING_STATUSES = /* @__PURE__ */ new Set(["pending", "initiated"]);
|
|
27
|
+
/** A connect/wait failure: code `connect.timeout` | `connect.failed`. The last connection status is in the message. */
|
|
28
|
+
function connectError(reason, message) {
|
|
29
|
+
return createError({
|
|
30
|
+
code: reason === "timeout" ? "connect.timeout" : "connect.failed",
|
|
31
|
+
message,
|
|
32
|
+
status: reason === "timeout" ? 408 : 502,
|
|
33
|
+
why: reason === "timeout" ? "The connection did not reach `active` before the timeout" : "The connection reached a terminal non-active status",
|
|
34
|
+
fix: reason === "timeout" ? "Increase timeoutMs, or have the user finish provider authorization or credential entry" : "Create a new connect link or re-initiate the connection"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
38
|
+
/**
|
|
39
|
+
* Poll `getConnection` until it reaches `active`, mirroring Composio's
|
|
40
|
+
* `waitForConnection`. Resolves with the active connection id; throws an
|
|
41
|
+
* `EvlogError` (code `connect.failed` | `connect.timeout`) on a terminal-failure status or timeout.
|
|
42
|
+
*/
|
|
43
|
+
async function waitForConnection(client, connectionId, opts = {}) {
|
|
44
|
+
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
45
|
+
const intervalMs = opts.intervalMs ?? 2e3;
|
|
46
|
+
const now = opts.now ?? Date.now;
|
|
47
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
48
|
+
const deadline = now() + timeoutMs;
|
|
49
|
+
for (;;) {
|
|
50
|
+
const conn = await client.getConnection({ connectionId });
|
|
51
|
+
if (conn.status === "active") return {
|
|
52
|
+
connectionId,
|
|
53
|
+
status: "active"
|
|
54
|
+
};
|
|
55
|
+
if (!PENDING_STATUSES.has(conn.status)) throw connectError("failed", `connection ${connectionId} is ${conn.status}`);
|
|
56
|
+
if (now() >= deadline) throw connectError("timeout", `timed out after ${timeoutMs}ms waiting for connection ${connectionId} (last status: ${conn.status})`);
|
|
57
|
+
await sleep(intervalMs);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Begin an OAuth2 connection and (by default) wait for it to become active.
|
|
62
|
+
* The caller is responsible for getting the user to the `redirectUrl` (the CLI
|
|
63
|
+
* opens it / prints it). With `waitForActive: false` it returns immediately
|
|
64
|
+
* after `initiate`.
|
|
65
|
+
*/
|
|
66
|
+
async function connectOAuth(client, opts) {
|
|
67
|
+
const init = await client.initiate({
|
|
68
|
+
slug: opts.slug,
|
|
69
|
+
authConfigId: opts.authConfigId,
|
|
70
|
+
authMethodId: opts.authMethodId,
|
|
71
|
+
userId: opts.userId,
|
|
72
|
+
scopes: opts.scopes
|
|
73
|
+
});
|
|
74
|
+
if (init.redirectUrl) opts.onRedirect?.(init.redirectUrl);
|
|
75
|
+
if (opts.waitForActive === false) return {
|
|
76
|
+
connectionId: init.connectionId,
|
|
77
|
+
status: "pending",
|
|
78
|
+
redirectUrl: init.redirectUrl
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
connectionId: (await waitForConnection(client, init.connectionId, opts)).connectionId,
|
|
82
|
+
status: "active",
|
|
83
|
+
redirectUrl: init.redirectUrl
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { resolveRpcUrl as a, createClient as i, connectOAuth as n, waitForConnection as r, connectError as t };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=src-CvNjrj1s.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-CvNjrj1s.js","names":[],"sources":["../src/client.ts","../src/connect.ts"],"sourcesContent":["import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { NativeContract } from \"@open-connector/api-contracts\";\nimport type { ContractRouterClient } from \"@orpc/contract\";\n\n/**\n * The first-party Open Connector SDK client — a fully-typed `@orpc/client`\n * over the native oRPC contract (`/api/v1`). The published declarations bundle\n * this contract so consumers do not need any private workspace packages.\n */\nexport type OpenConnectorClient = ContractRouterClient<NativeContract>;\n\ninterface BaseClientOptions {\n /** Base URL of the Open Connector server, e.g. `http://localhost:3000`. */\n baseUrl: string;\n /** Optional fetch override (used by tests to route into an in-process app). */\n fetch?: typeof fetch;\n}\n\nexport type ClientOptions = BaseClientOptions &\n (\n | {\n /** Project (agent) API key — sent as the `x-api-key` header. */\n apiKey: string;\n accessToken?: never;\n organizationId?: never;\n projectId?: never;\n }\n | {\n apiKey?: never;\n /** Human OAuth token, or an async provider that refreshes it before each request. */\n accessToken: string | (() => string | Promise<string>);\n /** Explicit live Organization selected by the Human client. */\n organizationId: string;\n /** Explicit live Project selected by the Human client. */\n projectId: string;\n }\n );\n\n/** The native RPC handler is mounted at `/api/v1` (the console appRouter owns\n * `/api/v1/rpc`). Trim any trailing slash so we never produce `//api/v1`. */\nexport function resolveRpcUrl(baseUrl: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}/api/v1`;\n}\n\n/** Create a typed Open Connector client bound to a Project key or Human OAuth capability. */\nexport function createClient(opts: ClientOptions): OpenConnectorClient {\n const link = new RPCLink({\n url: resolveRpcUrl(opts.baseUrl),\n headers:\n \"apiKey\" in opts && opts.apiKey\n ? { \"x-api-key\": opts.apiKey }\n : async () => ({\n authorization: `Bearer ${\n typeof opts.accessToken === \"function\" ? await opts.accessToken() : opts.accessToken\n }`,\n \"x-organization-id\": opts.organizationId,\n \"x-project-id\": opts.projectId,\n }),\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n });\n return createORPCClient(link);\n}\n","import { createError, type EvlogError } from \"evlog\";\n\nimport type { OpenConnectorClient } from \"./client\";\n\n/** Statuses that mean \"keep waiting\" — the OAuth round-trip is still in flight. */\nconst PENDING_STATUSES = new Set([\"pending\", \"initiated\"]);\n\nexport type ConnectErrorReason = \"timeout\" | \"failed\";\n\n/** A connect/wait failure: code `connect.timeout` | `connect.failed`. The last connection status is in the message. */\nexport function connectError(reason: ConnectErrorReason, message: string): EvlogError {\n return createError({\n code: reason === \"timeout\" ? \"connect.timeout\" : \"connect.failed\",\n message,\n status: reason === \"timeout\" ? 408 : 502,\n why:\n reason === \"timeout\"\n ? \"The connection did not reach `active` before the timeout\"\n : \"The connection reached a terminal non-active status\",\n fix:\n reason === \"timeout\"\n ? \"Increase timeoutMs, or have the user finish provider authorization or credential entry\"\n : \"Create a new connect link or re-initiate the connection\",\n });\n}\n\nexport interface WaitOptions {\n /** Give up after this many ms (default 120_000). */\n timeoutMs?: number;\n /** Poll cadence in ms (default 2_000). */\n intervalMs?: number;\n /** Injectable clock + sleep so the loop is deterministic under test. */\n now?: () => number;\n sleep?: (ms: number) => Promise<void>;\n}\n\n// Only the two methods the wait loop calls — lets callers (and tests) pass a\n// narrower object than the full client.\ntype ConnectClient = Pick<OpenConnectorClient, \"initiate\" | \"getConnection\">;\n\nconst defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/**\n * Poll `getConnection` until it reaches `active`, mirroring Composio's\n * `waitForConnection`. Resolves with the active connection id; throws an\n * `EvlogError` (code `connect.failed` | `connect.timeout`) on a terminal-failure status or timeout.\n */\nexport async function waitForConnection(\n client: ConnectClient,\n connectionId: string,\n opts: WaitOptions = {},\n): Promise<{ connectionId: string; status: \"active\" }> {\n const timeoutMs = opts.timeoutMs ?? 120_000;\n const intervalMs = opts.intervalMs ?? 2_000;\n const now = opts.now ?? Date.now;\n const sleep = opts.sleep ?? defaultSleep;\n const deadline = now() + timeoutMs;\n\n for (;;) {\n const conn = await client.getConnection({ connectionId });\n if (conn.status === \"active\") return { connectionId, status: \"active\" };\n if (!PENDING_STATUSES.has(conn.status)) {\n throw connectError(\"failed\", `connection ${connectionId} is ${conn.status}`);\n }\n if (now() >= deadline) {\n throw connectError(\n \"timeout\",\n `timed out after ${timeoutMs}ms waiting for connection ${connectionId} (last status: ${conn.status})`,\n );\n }\n await sleep(intervalMs);\n }\n}\n\nexport interface ConnectOAuthOptions extends WaitOptions {\n slug: string;\n authConfigId?: string;\n authMethodId?: string;\n /** Project-local connector business partition; independent from the authenticated Human. */\n userId: string;\n scopes?: string[];\n /** Called with the provider authorize URL the user must visit (if any). */\n onRedirect?: (url: string) => void;\n /** When true (default), poll until the connection is active before resolving. */\n waitForActive?: boolean;\n}\n\nexport interface ConnectResult {\n connectionId: string;\n status: \"pending\" | \"active\";\n redirectUrl?: string;\n}\n\n/**\n * Begin an OAuth2 connection and (by default) wait for it to become active.\n * The caller is responsible for getting the user to the `redirectUrl` (the CLI\n * opens it / prints it). With `waitForActive: false` it returns immediately\n * after `initiate`.\n */\nexport async function connectOAuth(\n client: ConnectClient,\n opts: ConnectOAuthOptions,\n): Promise<ConnectResult> {\n const init = await client.initiate({\n slug: opts.slug,\n authConfigId: opts.authConfigId,\n authMethodId: opts.authMethodId,\n userId: opts.userId,\n scopes: opts.scopes,\n });\n if (init.redirectUrl) opts.onRedirect?.(init.redirectUrl);\n\n if (opts.waitForActive === false) {\n return { connectionId: init.connectionId, status: \"pending\", redirectUrl: init.redirectUrl };\n }\n const done = await waitForConnection(client, init.connectionId, opts);\n return { connectionId: done.connectionId, status: \"active\", redirectUrl: init.redirectUrl };\n}\n"],"mappings":";;;;;;AAyCA,SAAgB,cAAc,SAAyB;CACrD,OAAO,GAAG,QAAQ,QAAQ,QAAQ,EAAE,EAAE;AACxC;;AAGA,SAAgB,aAAa,MAA0C;CACrE,MAAM,OAAO,IAAI,QAAQ;EACvB,KAAK,cAAc,KAAK,OAAO;EAC/B,SACE,YAAY,QAAQ,KAAK,SACrB,EAAE,aAAa,KAAK,OAAO,IAC3B,aAAa;GACX,eAAe,UACb,OAAO,KAAK,gBAAgB,aAAa,MAAM,KAAK,YAAY,IAAI,KAAK;GAE3E,qBAAqB,KAAK;GAC1B,gBAAgB,KAAK;EACvB;EACN,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;CAC5C,CAAC;CACD,OAAO,iBAAiB,IAAI;AAC9B;;;;ACzDA,MAAM,mCAAmB,IAAI,IAAI,CAAC,WAAW,WAAW,CAAC;;AAKzD,SAAgB,aAAa,QAA4B,SAA6B;CACpF,OAAO,YAAY;EACjB,MAAM,WAAW,YAAY,oBAAoB;EACjD;EACA,QAAQ,WAAW,YAAY,MAAM;EACrC,KACE,WAAW,YACP,6DACA;EACN,KACE,WAAW,YACP,2FACA;CACR,CAAC;AACH;AAgBA,MAAM,gBAAgB,OAAe,IAAI,SAAe,MAAM,WAAW,GAAG,EAAE,CAAC;;;;;;AAO/E,eAAsB,kBACpB,QACA,cACA,OAAoB,CAAC,GACgC;CACrD,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,WAAW,IAAI,IAAI;CAEzB,SAAS;EACP,MAAM,OAAO,MAAM,OAAO,cAAc,EAAE,aAAa,CAAC;EACxD,IAAI,KAAK,WAAW,UAAU,OAAO;GAAE;GAAc,QAAQ;EAAS;EACtE,IAAI,CAAC,iBAAiB,IAAI,KAAK,MAAM,GACnC,MAAM,aAAa,UAAU,cAAc,aAAa,MAAM,KAAK,QAAQ;EAE7E,IAAI,IAAI,KAAK,UACX,MAAM,aACJ,WACA,mBAAmB,UAAU,4BAA4B,aAAa,iBAAiB,KAAK,OAAO,EACrG;EAEF,MAAM,MAAM,UAAU;CACxB;AACF;;;;;;;AA2BA,eAAsB,aACpB,QACA,MACwB;CACxB,MAAM,OAAO,MAAM,OAAO,SAAS;EACjC,MAAM,KAAK;EACX,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,QAAQ,KAAK;EACb,QAAQ,KAAK;CACf,CAAC;CACD,IAAI,KAAK,aAAa,KAAK,aAAa,KAAK,WAAW;CAExD,IAAI,KAAK,kBAAkB,OACzB,OAAO;EAAE,cAAc,KAAK;EAAc,QAAQ;EAAW,aAAa,KAAK;CAAY;CAG7F,OAAO;EAAE,eAAc,MADJ,kBAAkB,QAAQ,KAAK,cAAc,IAAI,EAAA,CACxC;EAAc,QAAQ;EAAU,aAAa,KAAK;CAAY;AAC5F"}
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_src = require("./src-19LKPmdg.cjs");
|
|
3
|
+
exports.connectError = require_src.connectError;
|
|
4
|
+
exports.connectOAuth = require_src.connectOAuth;
|
|
5
|
+
exports.createClient = require_src.createClient;
|
|
6
|
+
exports.resolveRpcUrl = require_src.resolveRpcUrl;
|
|
7
|
+
exports.waitForConnection = require_src.waitForConnection;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as connectError, c as ClientOptions, d as resolveRpcUrl, i as WaitOptions, l as OpenConnectorClient, n as ConnectOAuthOptions, o as connectOAuth, r as ConnectResult, s as waitForConnection, t as ConnectErrorReason, u as createClient } from "./index-96n68fp1.cjs";
|
|
2
|
+
export { type ClientOptions, type ConnectErrorReason, type ConnectOAuthOptions, type ConnectResult, type OpenConnectorClient, type WaitOptions, connectError, connectOAuth, createClient, resolveRpcUrl, waitForConnection };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as connectError, c as ClientOptions, d as resolveRpcUrl, i as WaitOptions, l as OpenConnectorClient, n as ConnectOAuthOptions, o as connectOAuth, r as ConnectResult, s as waitForConnection, t as ConnectErrorReason, u as createClient } from "./index-BXEKBblV.js";
|
|
2
|
+
export { type ClientOptions, type ConnectErrorReason, type ConnectOAuthOptions, type ConnectResult, type OpenConnectorClient, type WaitOptions, connectError, connectOAuth, createClient, resolveRpcUrl, waitForConnection };
|
package/dist/testing.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-connector/sdk",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Typed TypeScript SDK for the native Open Connector API",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/okxiaoliang4/open-connector.git",
|
|
9
|
+
"directory": "packages/sdk"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"typesVersions": {
|
|
21
|
+
"*": {
|
|
22
|
+
"testing": [
|
|
23
|
+
"./dist/testing.d.ts"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"require": {
|
|
34
|
+
"types": "./dist/index.d.cts",
|
|
35
|
+
"default": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"./testing": {
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "./dist/testing.d.ts",
|
|
41
|
+
"default": "./dist/testing.js"
|
|
42
|
+
},
|
|
43
|
+
"require": {
|
|
44
|
+
"types": "./dist/testing.d.cts",
|
|
45
|
+
"default": "./dist/testing.cjs"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@opentelemetry/api": "1.9.1",
|
|
54
|
+
"@orpc/client": "1.14.4",
|
|
55
|
+
"@orpc/contract": "1.14.4",
|
|
56
|
+
"evlog": "2.18.1",
|
|
57
|
+
"zod": "4.4.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
61
|
+
"typescript": "6.0.3",
|
|
62
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
|
|
63
|
+
"vite-plus": "0.3.0",
|
|
64
|
+
"vitest": "4.1.11",
|
|
65
|
+
"@open-connector/api-contracts": "0.0.0",
|
|
66
|
+
"@open-connector/config": "0.0.0"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=20"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "pnpm run build:esm && pnpm run build:cjs",
|
|
73
|
+
"build:esm": "vp pack src/index.ts src/testing.ts --dts.eager --dts.generator tsc --dts.resolver tsc --format esm --sourcemap --platform neutral",
|
|
74
|
+
"build:cjs": "vp pack src/index.ts src/testing.ts --dts.eager --dts.generator tsc --dts.resolver tsc --format cjs --sourcemap --platform neutral --no-clean",
|
|
75
|
+
"test": "vp test run",
|
|
76
|
+
"test:coverage": "vp test run --coverage",
|
|
77
|
+
"check-types": "tsc --noEmit"
|
|
78
|
+
}
|
|
79
|
+
}
|