@jskit-ai/connectors-core 0.1.1

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.
@@ -0,0 +1,364 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
4
+ import { execFile } from "node:child_process";
5
+ import { tmpdir } from "node:os";
6
+ import path from "node:path";
7
+ import { createConnectionService, createEnvironmentReferenceResolver } from "../src/server/index.js";
8
+ import { createFileConnectionStore, createCredentialProtection } from "../src/server/fileStorage.js";
9
+ import { googleCalendarProvider } from "../../connector-google-calendar/src/server/provider.js";
10
+ import { resendProvider } from "../../connectors-catalog/src/server/resend.js";
11
+ import { gmailProvider } from "../../connectors-catalog/src/server/gmail.js";
12
+
13
+ // Execute the authored composition example so documentation cannot drift from
14
+ // the runtime. This trusted repository text is never supplied by an app user.
15
+ const guide = await readFile(new URL("../docs/setup-command.md", import.meta.url), "utf8");
16
+ const source = guide.match(/```js\n([\s\S]*?)\n```/u)?.[1];
17
+ assert.ok(source, "The setup guide must contain its dispatch example.");
18
+ const dispatchSetup = new Function(`${source}\nreturn dispatchSetup;`)();
19
+ const scope = "https://www.googleapis.com/auth/calendar.calendarlist.readonly";
20
+ const callback = "http://127.0.0.1:8080/integrations/google/callback";
21
+ const context = { applicationId: "dogandgroom-dev", subjectId: "business-account" };
22
+
23
+ async function fixture(t) {
24
+ const directory = await mkdtemp(path.join(tmpdir(), "setup-composition-"));
25
+ t.after(() => rm(directory, { recursive: true, force: true }));
26
+ const configuration = {
27
+ schemaVersion: 1,
28
+ registrations: { google: { source: "own", clientId: "fixture-client",
29
+ clientSecretRef: "env:GOOGLE_SECRET", callbackUrlRef: "env:CALLBACK" } },
30
+ integrations: {
31
+ calendar: { provider: "google-calendar", accountMode: "shared", scopes: [scope],
32
+ authentication: { method: "oauth2", registrationRef: "google" }, settings: {} },
33
+ inbox: { provider: "gmail", accountMode: "shared", scopes: ["https://www.googleapis.com/auth/gmail.readonly"],
34
+ authentication: { method: "oauth2", registrationRef: "google" }, settings: {} },
35
+ mail: { provider: "resend", accountMode: "shared", scopes: [],
36
+ authentication: { method: "api-key", secretRef: "env:RESEND_KEY" }, settings: {} }
37
+ }
38
+ };
39
+ const env = { GOOGLE_SECRET: "private-google", CALLBACK: callback, RESEND_KEY: "private-resend" };
40
+ const requests = [];
41
+ const tokenGrants = [];
42
+ let time = Date.now();
43
+ const restart = (runtimeDirectory = directory) => createConnectionService({
44
+ configuration, providers: [googleCalendarProvider, resendProvider, gmailProvider],
45
+ now: () => time,
46
+ store: createFileConnectionStore({ directory: runtimeDirectory, protection: createCredentialProtection({
47
+ activeKeyId: "test", keys: { test: new Uint8Array(32).fill(7) }
48
+ }) }),
49
+ authorize: async (identity) => identity,
50
+ resolveReference: createEnvironmentReferenceResolver(env),
51
+ fetchImpl: async (url, init) => {
52
+ requests.push(String(url));
53
+ if (String(url) === "https://oauth2.googleapis.com/token") {
54
+ tokenGrants.push(new URLSearchParams(init.body).get("grant_type"));
55
+ return Response.json({
56
+ token_type: "Bearer", access_token: "private-access", refresh_token: "private-refresh",
57
+ expires_in: 3600, scope: `${scope} https://www.googleapis.com/auth/gmail.readonly`
58
+ });
59
+ }
60
+ if (String(url).startsWith("https://www.googleapis.com/calendar/v3/users/me/calendarList")) {
61
+ return Response.json({ kind: "calendar#calendarList", items: [] });
62
+ }
63
+ if (String(url) === "https://gmail.googleapis.com/gmail/v1/users/me/profile") {
64
+ return Response.json({ emailAddress: "business@example.test", messagesTotal: 3 });
65
+ }
66
+ if (String(url).startsWith("https://api.resend.com/domains")) {
67
+ assert.equal(new Headers(init.headers).get("authorization"), `Bearer ${env.RESEND_KEY}`);
68
+ if (env.RESEND_KEY === "private-rejected") return Response.json({ message: "Invalid key" }, { status: 401 });
69
+ return Response.json({ object: "list", data: [], has_more: false });
70
+ }
71
+ assert.fail(`Unexpected provider request: ${url}`);
72
+ }
73
+ });
74
+ const dispatch = async (operation, integrationId, extra = {}, identity = context) => {
75
+ const result = await dispatchSetup({ protocol: "vibe64.integration-setup.command.v1",
76
+ requestId: "fixture-request", operation, integrationId, ...extra },
77
+ { configuration, connections: restart(), context: identity });
78
+ assert.equal(result.requestId, "fixture-request");
79
+ assert.equal(JSON.stringify(result).includes("private-"), false);
80
+ return result;
81
+ };
82
+ return { dispatch, restart, env, requests, tokenGrants, configuration, directory, advance: (milliseconds) => { time += milliseconds; } };
83
+ }
84
+
85
+ test("documented dispatcher crosses a CLI process boundary with Env credentials and persistent state", async (t) => {
86
+ const directory = await mkdtemp(path.join(tmpdir(), "setup-cli-"));
87
+ t.after(() => rm(directory, { recursive: true, force: true }));
88
+ const command = path.join(directory, "setup.mjs");
89
+ const configuration = { schemaVersion: 1, registrations: {}, integrations: {
90
+ mail: { provider: "resend", accountMode: "shared", scopes: [],
91
+ authentication: { method: "api-key", secretRef: "env:RESEND_KEY" }, settings: {} }
92
+ } };
93
+ await writeFile(path.join(directory, "integrations.json"), JSON.stringify(configuration));
94
+ // Test-only CLI bootstrap. The authored dispatcher remains the code under test;
95
+ // provider HTTP is controlled, while stdin, Env and encrypted files are real.
96
+ await writeFile(command, `
97
+ import { readFile } from "node:fs/promises";
98
+ import path from "node:path";
99
+ import { createConnectionService, createEnvironmentReferenceResolver } from ${JSON.stringify(new URL("../src/server/index.js", import.meta.url).href)};
100
+ import { createFileConnectionStore, createCredentialProtection } from ${JSON.stringify(new URL("../src/server/fileStorage.js", import.meta.url).href)};
101
+ import { resendProvider } from ${JSON.stringify(new URL("../../connectors-catalog/src/server/resend.js", import.meta.url).href)};
102
+ ${source}
103
+ try {
104
+ let input = "";
105
+ for await (const chunk of process.stdin) {
106
+ input += chunk;
107
+ if (Buffer.byteLength(input) > 32768) throw new Error("Input too large");
108
+ }
109
+ const configuration = JSON.parse(await readFile("integrations.json", "utf8"));
110
+ const context = { applicationId: process.env.APP_ID, subjectId: "setup-operator" };
111
+ const connections = createConnectionService({ configuration, providers: [resendProvider],
112
+ resolveReference: createEnvironmentReferenceResolver(process.env),
113
+ store: createFileConnectionStore({ directory: path.resolve("state"), protection: createCredentialProtection({
114
+ activeKeyId: "fixture", keys: { fixture: Buffer.from(process.env.STORE_KEY, "hex") }
115
+ }) }),
116
+ authorize: async (identity) => {
117
+ if (identity.applicationId !== context.applicationId || identity.subjectId !== context.subjectId) throw new Error("Wrong operator");
118
+ return identity;
119
+ },
120
+ fetchImpl: async (url, init) => {
121
+ if (process.env.ALLOW_PROVIDER_CHECK !== "1" || String(url) !== "https://api.resend.com/domains?limit=20" ||
122
+ new Headers(init.headers).get("authorization") !== "Bearer fixture-private-key") throw new Error("Unexpected provider request");
123
+ return Response.json({ object: "list", data: [], has_more: false });
124
+ }
125
+ });
126
+ const result = await dispatchSetup(JSON.parse(input), { configuration, connections, context });
127
+ process.stdout.write(JSON.stringify(result) + "\\n");
128
+ } catch {
129
+ process.stderr.write("Integration setup failed.\\n");
130
+ process.exitCode = 1;
131
+ }
132
+ `);
133
+ const request = { protocol: "vibe64.integration-setup.command.v1", requestId: "cli-request", integrationId: "mail" };
134
+ for (const [operation, key, applicationId, expected, allowCheck] of [
135
+ ["status", "MISSING", "first-app", "unconfigured", false],
136
+ ["connect", "MISSING", "first-app", "unconfigured", false],
137
+ ["connect", "fixture-private-key", "first-app", "connected", true],
138
+ ["status", "fixture-private-key", "first-app", "connected", false],
139
+ ["status", "fixture-private-key", "other-app", "disconnected", false],
140
+ ["disconnect", "fixture-private-key", "first-app", "disconnected", false],
141
+ ["status", "fixture-private-key", "first-app", "disconnected", false]
142
+ ]) {
143
+ const { stdout, stderr } = await new Promise((resolve, reject) => {
144
+ const child = execFile(process.execPath, [command], { cwd: directory, timeout: 5000, maxBuffer: 32768,
145
+ env: { APP_ID: applicationId, RESEND_KEY: key, STORE_KEY: "07".repeat(32),
146
+ ALLOW_PROVIDER_CHECK: allowCheck ? "1" : "0" }
147
+ }, (error, stdout, stderr) => error ? reject(error) : resolve({ stdout, stderr }));
148
+ child.stdin.end(JSON.stringify({ ...request, operation }) + "\n");
149
+ });
150
+ assert.equal(stderr, "");
151
+ assert.equal(stdout.trim().split("\n").length, 1);
152
+ assert.equal(stdout.includes("fixture-private-key"), false);
153
+ assert.equal(stdout.includes("07".repeat(32)), false);
154
+ const result = JSON.parse(stdout);
155
+ assert.equal(result.protocol, request.protocol);
156
+ assert.equal(result.requestId, request.requestId);
157
+ assert.equal(result.status, expected);
158
+ }
159
+ const failure = await new Promise((resolve) => {
160
+ const child = execFile(process.execPath, [command], { cwd: directory, timeout: 5000, maxBuffer: 32768,
161
+ env: { APP_ID: "first-app", RESEND_KEY: "fixture-private-key", STORE_KEY: "07".repeat(32) }
162
+ }, (error, stdout, stderr) => resolve({ code: error?.code, stdout, stderr }));
163
+ child.stdin.end("invalid-json-with-fixture-private-key\n");
164
+ });
165
+ assert.deepEqual(failure, { code: 1, stdout: "", stderr: "Integration setup failed.\n" });
166
+ assert.deepEqual(JSON.parse(await readFile(path.join(directory, "integrations.json"), "utf8")), configuration);
167
+ });
168
+
169
+ test("documented setup returns a verified mailbox label after restart and removes it on disconnect", async (t) => {
170
+ const { dispatch, restart, requests, env, configuration } = await fixture(t);
171
+ const pending = await dispatch("connect", "inbox");
172
+ assert.equal(pending.accountLabel, undefined);
173
+ await restart().completeAuthorization({ context, integrationId: "inbox",
174
+ callbackUrl: `${callback}?code=fixture-code&state=${pending.attemptId}` });
175
+ assert.equal((await dispatch("status", "inbox")).accountLabel, "business@example.test");
176
+ const replacement = await dispatch("connect", "inbox");
177
+ assert.equal(replacement.accountLabel, undefined);
178
+ await dispatch("cancel", "inbox", { attemptId: replacement.attemptId });
179
+ assert.equal((await dispatch("status", "inbox")).accountLabel, "business@example.test");
180
+ const requestCount = requests.length;
181
+ const previousEnv = { ...env };
182
+ const previousConfiguration = structuredClone(configuration);
183
+ assert.equal((await dispatch("disconnect", "inbox")).accountLabel, undefined);
184
+ assert.equal((await dispatch("status", "inbox")).accountLabel, undefined);
185
+ assert.equal(requests.length, requestCount);
186
+ assert.deepEqual(env, previousEnv);
187
+ assert.deepEqual(configuration, previousConfiguration);
188
+ });
189
+
190
+ test("two Gmail slots sharing a registration keep pending consent and grants independent", async (t) => {
191
+ const { dispatch, restart, configuration } = await fixture(t);
192
+ configuration.integrations["second-inbox"] = structuredClone(configuration.integrations.inbox);
193
+ const first = await dispatch("connect", "inbox");
194
+ const second = await dispatch("connect", "second-inbox");
195
+ assert.notEqual(first.attemptId, second.attemptId);
196
+ await assert.rejects(restart().completeAuthorization({ context, integrationId: "second-inbox",
197
+ callbackUrl: `${callback}?code=fixture-code&state=${first.attemptId}` }));
198
+ await restart().completeAuthorization({ context, integrationId: "inbox",
199
+ callbackUrl: `${callback}?code=fixture-code&state=${first.attemptId}` });
200
+ assert.equal((await dispatch("status", "inbox")).status, "connected");
201
+ assert.equal((await dispatch("status", "second-inbox")).attemptId, second.attemptId);
202
+ await dispatch("cancel", "second-inbox", { attemptId: second.attemptId });
203
+ assert.equal((await dispatch("status", "inbox")).status, "connected");
204
+ const replacement = await dispatch("connect", "second-inbox");
205
+ await restart().completeAuthorization({ context, integrationId: "second-inbox",
206
+ callbackUrl: `${callback}?code=fixture-code&state=${replacement.attemptId}` });
207
+ await dispatch("disconnect", "inbox");
208
+ assert.equal((await dispatch("status", "inbox")).status, "disconnected");
209
+ assert.equal((await dispatch("status", "second-inbox")).status, "connected");
210
+ });
211
+
212
+ test("moving application state preserves grants and refresh while invalidating consent for the old callback", async (t) => {
213
+ const { dispatch, restart, env, directory, advance, requests, tokenGrants } = await fixture(t);
214
+ const pending = await dispatch("connect", "calendar");
215
+ const input = { context, integrationId: "calendar" };
216
+ await restart().completeAuthorization({ ...input,
217
+ callbackUrl: `${callback}?code=fixture-code&state=${pending.attemptId}` });
218
+ await dispatch("connect", "mail");
219
+ await dispatch("connect", "calendar");
220
+ const moved = `${directory}-relocated`;
221
+ t.after(() => rm(moved, { recursive: true, force: true }));
222
+ await rename(directory, moved);
223
+ env.CALLBACK = "https://dogandgroom.example/integrations/google/callback";
224
+ const relocated = restart(moved);
225
+ const requestCount = requests.length;
226
+ assert.equal(await relocated.resumeAuthorization(input), null);
227
+ assert.equal((await relocated.status(input)).status, "connected");
228
+ assert.equal((await relocated.status(input)).callbackUrl, env.CALLBACK);
229
+ assert.equal((await relocated.status({ context, integrationId: "mail" })).status, "connected");
230
+ assert.equal(requests.length, requestCount);
231
+ advance(3_600_001);
232
+ await relocated.invoke({ ...input, operation: "calendars.list" });
233
+ assert.deepEqual(tokenGrants, ["authorization_code", "refresh_token"]);
234
+ assert.deepEqual(requests.slice(requestCount).map((url) => new URL(url).hostname), [
235
+ "oauth2.googleapis.com", "www.googleapis.com"
236
+ ]);
237
+ assert.equal((await restart(moved).status(input)).status, "connected");
238
+ const otherEnvironment = { ...context, applicationId: "dogandgroom-production" };
239
+ assert.equal((await relocated.status({ ...input, context: otherEnvironment })).status, "disconnected");
240
+ await relocated.disconnect({ ...input, context: otherEnvironment });
241
+ assert.equal((await relocated.status(input)).status, "connected");
242
+ });
243
+
244
+ test("documented OAuth setup resumes across restarts, preserves a grant on cancel and isolates apps", async (t) => {
245
+ const { dispatch, restart, env, requests } = await fixture(t);
246
+ env.GOOGLE_SECRET = "MISSING";
247
+ assert.equal((await dispatch("status", "calendar")).setupIssue, "credentials-missing");
248
+ env.GOOGLE_SECRET = "private-google";
249
+ env.CALLBACK = "not-a-callback";
250
+ assert.equal((await dispatch("status", "calendar")).setupIssue, "callback-invalid");
251
+ env.CALLBACK = callback;
252
+ assert.equal((await dispatch("status", "calendar")).status, "disconnected");
253
+ assert.equal((await dispatch("status", "calendar")).callbackUrl, callback);
254
+ assert.equal(requests.length, 0);
255
+ const pending = await dispatch("connect", "calendar");
256
+ assert.equal(pending.status, "pending");
257
+ assert.equal(pending.callbackUrl, callback);
258
+ assert.deepEqual(await dispatch("status", "calendar"), pending);
259
+ const callbackUrl = `${callback}?code=fixture-code&state=${pending.attemptId}`;
260
+ await restart().completeAuthorization({ context, integrationId: "calendar", callbackUrl });
261
+ assert.equal((await dispatch("status", "calendar")).status, "connected");
262
+ env.CALLBACK = "https://dogandgroom.example/integrations/google/callback";
263
+ assert.equal((await dispatch("status", "calendar")).callbackUrl, env.CALLBACK);
264
+ env.CALLBACK = callback;
265
+ const replacement = await dispatch("connect", "calendar");
266
+ assert.equal((await dispatch("cancel", "calendar", { attemptId: replacement.attemptId })).status, "cancelled");
267
+ assert.equal((await dispatch("status", "calendar")).status, "connected");
268
+ await assert.rejects(restart().completeAuthorization({ context, integrationId: "calendar",
269
+ callbackUrl: `${callback}?code=fixture-code&state=${replacement.attemptId}` }));
270
+ const otherApp = { ...context, applicationId: "another-app" };
271
+ assert.equal((await dispatch("status", "calendar", {}, otherApp)).status, "disconnected");
272
+ await dispatch("disconnect", "calendar", {}, otherApp);
273
+ assert.equal((await dispatch("status", "calendar")).status, "connected");
274
+ const count = requests.length;
275
+ env.GOOGLE_SECRET = "MISSING";
276
+ assert.equal((await dispatch("status", "calendar")).status, "reconnect-required");
277
+ assert.equal(requests.length, count);
278
+ assert.equal((await dispatch("disconnect", "calendar")).status, "disconnected");
279
+ });
280
+
281
+ test("individual Calendar accounts keep consent and grants separate across runtime restarts", async (t) => {
282
+ const { configuration, restart, dispatch } = await fixture(t);
283
+ configuration.integrations.calendar.accountMode = "per-user";
284
+ const alice = { ...context, subjectId: "alice" };
285
+ const bob = { ...context, subjectId: "bob" };
286
+ const input = (identity) => ({ context: identity, integrationId: "calendar" });
287
+ const alicePending = await restart().beginAuthorization(input(alice));
288
+ const bobPending = await restart().beginAuthorization(input(bob));
289
+ const callbackFor = (pending) => `${callback}?code=fixture-code&state=${new URL(pending.authorizationUrl).searchParams.get("state")}`;
290
+ await assert.rejects(restart().completeAuthorization({ ...input(bob), callbackUrl: callbackFor(alicePending) }), {
291
+ code: "connector_attempt_invalid"
292
+ });
293
+ await restart().completeAuthorization({ ...input(alice), callbackUrl: callbackFor(alicePending) });
294
+ assert.equal((await restart().status(input(alice))).status, "connected");
295
+ assert.equal((await restart().status(input(bob))).status, "disconnected");
296
+ assert.deepEqual(await restart().resumeAuthorization(input(bob)), bobPending);
297
+ await restart().completeAuthorization({ ...input(bob), callbackUrl: callbackFor(bobPending) });
298
+ await restart().disconnect(input(alice));
299
+ assert.equal((await restart().status(input(alice))).status, "disconnected");
300
+ assert.equal((await restart().status(input(bob))).status, "connected");
301
+ await assert.rejects(dispatch("connect", "calendar"), /Individual users connect inside the application/u);
302
+ });
303
+
304
+ test("documented key setup verifies explicitly, survives restart and reports incomplete credentials", async (t) => {
305
+ const { dispatch, env, requests, configuration, advance } = await fixture(t);
306
+ assert.equal((await dispatch("status", "mail")).status, "disconnected");
307
+ assert.equal(requests.length, 0);
308
+ assert.equal((await dispatch("connect", "mail")).status, "connected");
309
+ assert.equal((await dispatch("status", "mail")).status, "connected");
310
+ assert.equal(requests.length, 1);
311
+ env.RESEND_KEY = "MISSING";
312
+ assert.equal((await dispatch("status", "mail")).status, "reconnect-required");
313
+ await dispatch("disconnect", "mail");
314
+ const readiness = await dispatch("status", "mail");
315
+ assert.equal(readiness.status, "unconfigured");
316
+ assert.equal(readiness.setupIssue, "credentials-missing");
317
+ const incomplete = await dispatch("connect", "mail");
318
+ assert.equal(incomplete.status, "unconfigured");
319
+ assert.equal(incomplete.setupIssue, "credentials-missing");
320
+ assert.equal(requests.length, 1);
321
+ env.RESEND_KEY = "private-replacement";
322
+ assert.equal((await dispatch("connect", "mail")).status, "connected");
323
+ assert.equal(requests.length, 2);
324
+ const previous = await dispatch("status", "mail");
325
+ env.RESEND_KEY = "private-rejected";
326
+ advance(1000);
327
+ await assert.rejects(dispatch("connect", "mail"), { code: "connector_reconnect_required" });
328
+ assert.deepEqual(await dispatch("status", "mail"), { ...previous, status: "reconnect-required" },
329
+ "The replacement key must not inherit the previous key's verified status.");
330
+ env.RESEND_KEY = "private-replacement";
331
+ assert.deepEqual(await dispatch("status", "mail"), previous, "A failed check must preserve the last successful binding.");
332
+ assert.equal(requests.length, 3);
333
+ env.RESEND_KEY = "private-valid-replacement";
334
+ const verified = await dispatch("connect", "mail");
335
+ assert.equal(verified.status, "connected");
336
+ assert.ok(verified.verifiedAt > previous.verifiedAt);
337
+ assert.equal(requests.length, 4);
338
+ configuration.integrations.calendar.accountMode = "per-user";
339
+ await assert.rejects(dispatch("connect", "calendar"), /Individual users connect inside the application/u);
340
+ });
341
+
342
+ test("documented OAuth setup consumes denied and repeated callbacks without replacing a previous grant", async (t) => {
343
+ const { dispatch, restart, requests } = await fixture(t);
344
+ const first = await dispatch("connect", "calendar");
345
+ const complete = (attempt, suffix) => restart().completeAuthorization({
346
+ context, integrationId: "calendar",
347
+ callbackUrl: `${callback}?${suffix}&state=${attempt.attemptId}`
348
+ });
349
+ await complete(first, "code=fixture-code");
350
+ const connected = await dispatch("status", "calendar");
351
+ assert.equal(connected.status, "connected");
352
+ const verifiedRequests = requests.length;
353
+ await assert.rejects(complete(first, "code=fixture-code"), { code: "connector_attempt_invalid" });
354
+ assert.equal(requests.length, verifiedRequests);
355
+ assert.deepEqual(await dispatch("status", "calendar"), connected);
356
+
357
+ const replacement = await dispatch("connect", "calendar");
358
+ await assert.rejects(complete(replacement, "error=access_denied"), { code: "connector_consent_denied" });
359
+ assert.equal(requests.length, verifiedRequests);
360
+ assert.deepEqual(await dispatch("status", "calendar"), connected);
361
+ await assert.rejects(complete(replacement, "code=late-code"), { code: "connector_attempt_invalid" });
362
+ assert.equal(requests.length, verifiedRequests);
363
+ assert.deepEqual(await dispatch("status", "calendar"), connected);
364
+ });