@irogane/kaji 0.2.0-beta.11

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.
Files changed (136) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +598 -0
  3. package/contracts/README.md +14 -0
  4. package/contracts/beta-core-v1.json +52 -0
  5. package/contracts/cli/init-cases-v1.json +27 -0
  6. package/contracts/errors/error-codes.json +48 -0
  7. package/contracts/errors/integration-recovery-v1.json +127 -0
  8. package/contracts/errors/provider-normalization.json +111 -0
  9. package/contracts/events/conformance-invalid.json +20 -0
  10. package/contracts/events/conformance.json +511 -0
  11. package/contracts/events/new-kaji-event-v1.schema.json +1021 -0
  12. package/contracts/events/stored-kaji-event-v1.schema.json +1025 -0
  13. package/contracts/feature-tiers-v1.json +488 -0
  14. package/contracts/integrations/abi-index-v1.json +8 -0
  15. package/contracts/integrations/conformance-invalid.json +443 -0
  16. package/contracts/integrations/conformance-valid.json +121 -0
  17. package/contracts/integrations/copy-provenance-v1.schema.json +61 -0
  18. package/contracts/integrations/echo-tool-abi-v1.json +37 -0
  19. package/contracts/integrations/github-api-conformance-v1.json +644 -0
  20. package/contracts/integrations/github-tool-abi-typescript-v1.json +369 -0
  21. package/contracts/integrations/github-tool-abi-v1.json +146 -0
  22. package/contracts/integrations/gmail-api-conformance-v1.json +750 -0
  23. package/contracts/integrations/gmail-tool-abi-v1.json +62 -0
  24. package/contracts/integrations/index.schema.json +37 -0
  25. package/contracts/integrations/manifest.schema.json +119 -0
  26. package/contracts/parity/expected-normalized.json +4906 -0
  27. package/contracts/parity/scenarios.json +100 -0
  28. package/contracts/parity/scenarios.schema.json +214 -0
  29. package/contracts/providers/cost-conformance.json +112 -0
  30. package/contracts/release/github-proof-v1.schema.json +138 -0
  31. package/contracts/release/gmail-proof-v1.schema.json +138 -0
  32. package/contracts/release/kaji-ts-consumer-handoff-v1.schema.json +1289 -0
  33. package/contracts/release/publisher-identity-receipt-v1.schema.json +319 -0
  34. package/contracts/release/typescript-onboarding-evidence-v1.schema.json +740 -0
  35. package/contracts/tools/conformance-invalid.json +271 -0
  36. package/contracts/tools/conformance-valid.json +78 -0
  37. package/contracts/tools/tool-schema-v1.schema.json +18 -0
  38. package/dist/anthropic.cjs +1231 -0
  39. package/dist/anthropic.cjs.map +1 -0
  40. package/dist/anthropic.d.cts +26 -0
  41. package/dist/anthropic.d.ts +26 -0
  42. package/dist/anthropic.js +270 -0
  43. package/dist/anthropic.js.map +1 -0
  44. package/dist/auth.cjs +1507 -0
  45. package/dist/auth.cjs.map +1 -0
  46. package/dist/auth.d.cts +129 -0
  47. package/dist/auth.d.ts +129 -0
  48. package/dist/auth.js +1039 -0
  49. package/dist/auth.js.map +1 -0
  50. package/dist/base-B9FRMcP8.d.cts +140 -0
  51. package/dist/base-nHQd1VtS.d.ts +140 -0
  52. package/dist/chunk-AAM33KAO.js +4367 -0
  53. package/dist/chunk-AAM33KAO.js.map +1 -0
  54. package/dist/chunk-KAJ6BM64.js +153 -0
  55. package/dist/chunk-KAJ6BM64.js.map +1 -0
  56. package/dist/chunk-KCAXIOZS.js +308 -0
  57. package/dist/chunk-KCAXIOZS.js.map +1 -0
  58. package/dist/chunk-LSJ4AVO2.js +243 -0
  59. package/dist/chunk-LSJ4AVO2.js.map +1 -0
  60. package/dist/chunk-TM7ZGOJX.js +716 -0
  61. package/dist/chunk-TM7ZGOJX.js.map +1 -0
  62. package/dist/cli/bin.d.ts +2 -0
  63. package/dist/cli/bin.js +13 -0
  64. package/dist/cli/bin.js.map +1 -0
  65. package/dist/cli/chunk-2RCWPRVY.js +6277 -0
  66. package/dist/cli/chunk-2RCWPRVY.js.map +1 -0
  67. package/dist/cli/chunk-SEBX54TR.js +681 -0
  68. package/dist/cli/chunk-SEBX54TR.js.map +1 -0
  69. package/dist/cli/index.d.ts +232 -0
  70. package/dist/cli/index.js +11 -0
  71. package/dist/cli/index.js.map +1 -0
  72. package/dist/cli/init-worker.d.ts +2 -0
  73. package/dist/cli/init-worker.js +18 -0
  74. package/dist/cli/init-worker.js.map +1 -0
  75. package/dist/cli/integration-copy-worker.js +48 -0
  76. package/dist/cli/integration-copy-worker.js.map +1 -0
  77. package/dist/cli/package-entry-cjs.cjs +21 -0
  78. package/dist/cli/package-entry-cjs.cjs.map +1 -0
  79. package/dist/cli/package-entry-cjs.d.cts +2 -0
  80. package/dist/cli/package-entry.d.ts +2 -0
  81. package/dist/cli/package-entry.js +13 -0
  82. package/dist/cli/package-entry.js.map +1 -0
  83. package/dist/context-BaFHrQHv.d.cts +21 -0
  84. package/dist/context-BaFHrQHv.d.ts +21 -0
  85. package/dist/context-C-YPY-GS.d.cts +1538 -0
  86. package/dist/context-C-YPY-GS.d.ts +1538 -0
  87. package/dist/index.cjs +12346 -0
  88. package/dist/index.cjs.map +1 -0
  89. package/dist/index.d.cts +1560 -0
  90. package/dist/index.d.ts +1560 -0
  91. package/dist/index.js +7852 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/integrations/github.cjs +2092 -0
  94. package/dist/integrations/github.cjs.map +1 -0
  95. package/dist/integrations/github.d.cts +21 -0
  96. package/dist/integrations/github.d.ts +21 -0
  97. package/dist/integrations/github.js +2088 -0
  98. package/dist/integrations/github.js.map +1 -0
  99. package/dist/integrations.cjs +3370 -0
  100. package/dist/integrations.cjs.map +1 -0
  101. package/dist/integrations.d.cts +202 -0
  102. package/dist/integrations.d.ts +202 -0
  103. package/dist/integrations.js +2650 -0
  104. package/dist/integrations.js.map +1 -0
  105. package/dist/observability-Cj--OkME.d.cts +96 -0
  106. package/dist/observability-Cj--OkME.d.ts +96 -0
  107. package/dist/openai.cjs +1235 -0
  108. package/dist/openai.cjs.map +1 -0
  109. package/dist/openai.d.cts +32 -0
  110. package/dist/openai.d.ts +32 -0
  111. package/dist/openai.js +272 -0
  112. package/dist/openai.js.map +1 -0
  113. package/dist/testing.cjs +554 -0
  114. package/dist/testing.cjs.map +1 -0
  115. package/dist/testing.d.cts +43 -0
  116. package/dist/testing.d.ts +43 -0
  117. package/dist/testing.js +118 -0
  118. package/dist/testing.js.map +1 -0
  119. package/package.json +188 -0
  120. package/registry/echo/index.ts +53 -0
  121. package/registry/echo/manifest.json +53 -0
  122. package/registry/github/LICENSE +105 -0
  123. package/registry/github/client.ts +1727 -0
  124. package/registry/github/index.ts +263 -0
  125. package/registry/github/manifest.json +227 -0
  126. package/registry/github/owner-fixtures.json +10 -0
  127. package/registry/github/tests/github.test.ts +32 -0
  128. package/registry/gmail/LICENSE +105 -0
  129. package/registry/gmail/client.ts +548 -0
  130. package/registry/gmail/index.ts +165 -0
  131. package/registry/gmail/manifest.json +102 -0
  132. package/registry/gmail/owner-fixtures.json +10 -0
  133. package/registry/gmail/tests/gmail.test.ts +32 -0
  134. package/registry/index.json +21 -0
  135. package/registry/index.schema.json +37 -0
  136. package/registry/schema.json +119 -0
package/dist/auth.js ADDED
@@ -0,0 +1,1039 @@
1
+ import { systemClock, IntegrationExecutionError, IntegrationAuthError, IntegrationAuthRequiredError, IntegrationPolicyError } from './chunk-KCAXIOZS.js';
2
+ import { NOOP_METRICS, NOOP_TRACE, startSpan, recordMetric } from './chunk-LSJ4AVO2.js';
3
+ import { randomBytes, createHash, timingSafeEqual } from 'crypto';
4
+ import { createServer } from 'http';
5
+ import { request } from 'https';
6
+ import { spawn } from 'child_process';
7
+ import { accessSync, constants } from 'fs';
8
+ import { platform } from 'os';
9
+
10
+ var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
11
+ var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
12
+ var GOOGLE_REVOKE_URL = "https://oauth2.googleapis.com/revoke";
13
+ var PRINCIPAL = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
14
+ var MAX_CREDENTIAL_BYTES = 16 * 1024;
15
+ var MAX_PROVIDER_BYTES = 64 * 1024;
16
+ var MAX_TOKEN_CHARACTERS = 8192;
17
+ var REFRESH_BUFFER_MS = 6e4;
18
+ var OPERATION_MS = 3e4;
19
+ var CALLBACK_MS = 5 * 6e4;
20
+ var OAuthOperationError = class extends Error {
21
+ constructor() {
22
+ super("OAuth operation failed");
23
+ }
24
+ };
25
+ var OAuthScopeDriftError = class extends OAuthOperationError {
26
+ };
27
+ var Mutex = class {
28
+ tail = Promise.resolve();
29
+ async run(operation) {
30
+ const previous = this.tail;
31
+ const next = Promise.withResolvers();
32
+ this.tail = next.promise;
33
+ await previous;
34
+ try {
35
+ return await operation();
36
+ } finally {
37
+ next.resolve();
38
+ }
39
+ }
40
+ };
41
+ function policyError() {
42
+ return new IntegrationPolicyError();
43
+ }
44
+ function authRequired(reason) {
45
+ return new IntegrationAuthRequiredError(reason);
46
+ }
47
+ function validateOAuthPrincipal(value) {
48
+ if (typeof value !== "string" || !PRINCIPAL.test(value)) throw policyError();
49
+ return value;
50
+ }
51
+ function scalarLength(value) {
52
+ for (let index = 0; index < value.length; index += 1) {
53
+ const unit = value.charCodeAt(index);
54
+ if (unit >= 55296 && unit <= 56319) {
55
+ const next = value.charCodeAt(index + 1);
56
+ if (next < 56320 || next > 57343) throw new OAuthOperationError();
57
+ index += 1;
58
+ } else if (unit >= 56320 && unit <= 57343) {
59
+ throw new OAuthOperationError();
60
+ }
61
+ }
62
+ return Array.from(value).length;
63
+ }
64
+ function boundedString(value, maximum = MAX_TOKEN_CHARACTERS) {
65
+ if (typeof value !== "string" || scalarLength(value) < 1 || scalarLength(value) > maximum) {
66
+ throw new OAuthOperationError();
67
+ }
68
+ return value;
69
+ }
70
+ function normalizedScopes(value, provider = false) {
71
+ if (!Array.isArray(value)) throw provider ? new OAuthOperationError() : policyError();
72
+ const scopes = [...new Set(value)].sort();
73
+ if (scopes.length === 0 || scopes.length > 64 || scopes.some(
74
+ (scope) => typeof scope !== "string" || scope.length === 0 || scalarLength(scope) > 2048 || /\s/.test(scope)
75
+ )) {
76
+ throw provider ? new OAuthOperationError() : policyError();
77
+ }
78
+ return Object.freeze(scopes);
79
+ }
80
+ function snapshotOAuthCredentialRecord(value) {
81
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
82
+ throw new OAuthOperationError();
83
+ }
84
+ const root = value;
85
+ if (Object.keys(root).sort().join(",") !== "schemaVersion,state,tokens") {
86
+ throw new OAuthOperationError();
87
+ }
88
+ if (root.schemaVersion !== 1 || root.state !== "active" && root.state !== "revocation_pending") {
89
+ throw new OAuthOperationError();
90
+ }
91
+ if (root.tokens === null || typeof root.tokens !== "object" || Array.isArray(root.tokens)) {
92
+ throw new OAuthOperationError();
93
+ }
94
+ const tokens = root.tokens;
95
+ if (Object.keys(tokens).sort().join(",") !== "accessToken,expiresAtEpochMs,grantedScopes,refreshToken,tokenType") {
96
+ throw new OAuthOperationError();
97
+ }
98
+ if (tokens.tokenType !== "Bearer" || !Number.isSafeInteger(tokens.expiresAtEpochMs) || tokens.expiresAtEpochMs < 1 || !Array.isArray(tokens.grantedScopes)) {
99
+ throw new OAuthOperationError();
100
+ }
101
+ const grantedScopes = normalizedScopes(tokens.grantedScopes, true);
102
+ if (grantedScopes.length !== tokens.grantedScopes.length) throw new OAuthOperationError();
103
+ const record = {
104
+ schemaVersion: 1,
105
+ state: root.state,
106
+ tokens: {
107
+ accessToken: boundedString(tokens.accessToken),
108
+ refreshToken: boundedString(tokens.refreshToken),
109
+ expiresAtEpochMs: tokens.expiresAtEpochMs,
110
+ grantedScopes,
111
+ tokenType: "Bearer"
112
+ }
113
+ };
114
+ return Object.freeze({ ...record, tokens: Object.freeze(record.tokens) });
115
+ }
116
+ function canonicalOAuthCredentialJson(value) {
117
+ const record = snapshotOAuthCredentialRecord(value);
118
+ const encoded = JSON.stringify({
119
+ schemaVersion: 1,
120
+ state: record.state,
121
+ tokens: {
122
+ accessToken: record.tokens.accessToken,
123
+ expiresAtEpochMs: record.tokens.expiresAtEpochMs,
124
+ grantedScopes: record.tokens.grantedScopes,
125
+ refreshToken: record.tokens.refreshToken,
126
+ tokenType: "Bearer"
127
+ }
128
+ });
129
+ if (new TextEncoder().encode(encoded).byteLength > MAX_CREDENTIAL_BYTES) {
130
+ throw new OAuthOperationError();
131
+ }
132
+ return encoded;
133
+ }
134
+ function parseProviderJson(bytes) {
135
+ if (bytes.byteLength > MAX_PROVIDER_BYTES) throw new OAuthOperationError();
136
+ try {
137
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
138
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
139
+ throw new OAuthOperationError();
140
+ }
141
+ return value;
142
+ } catch {
143
+ throw new OAuthOperationError();
144
+ }
145
+ }
146
+ function providerErrorCode(bytes) {
147
+ try {
148
+ const error = parseProviderJson(bytes).error;
149
+ return error === "invalid_grant" ? error : void 0;
150
+ } catch {
151
+ return void 0;
152
+ }
153
+ }
154
+ function abortError() {
155
+ return new DOMException("OAuth operation cancelled", "AbortError");
156
+ }
157
+ function timeoutError() {
158
+ return new DOMException("OAuth operation timed out", "TimeoutError");
159
+ }
160
+ function throwIfAborted(signal) {
161
+ if (signal.aborted) throw abortError();
162
+ }
163
+ var NodeOAuthTransport = class {
164
+ postForm(endpoint, form, signal, deadlineMonotonicMs) {
165
+ if (endpoint !== GOOGLE_TOKEN_URL && endpoint !== GOOGLE_REVOKE_URL) throw policyError();
166
+ throwIfAborted(signal);
167
+ const remaining = Math.min(OPERATION_MS, deadlineMonotonicMs - performance.now());
168
+ if (remaining <= 0) return Promise.reject(timeoutError());
169
+ return new Promise((resolve, reject) => {
170
+ const body = new URLSearchParams(form).toString();
171
+ const request$1 = request(
172
+ endpoint,
173
+ {
174
+ method: "POST",
175
+ headers: {
176
+ "content-type": "application/x-www-form-urlencoded",
177
+ "content-length": Buffer.byteLength(body)
178
+ },
179
+ timeout: remaining
180
+ },
181
+ (response) => {
182
+ const chunks = [];
183
+ let size = 0;
184
+ response.on("data", (chunk) => {
185
+ size += chunk.byteLength;
186
+ if (size > MAX_PROVIDER_BYTES) request$1.destroy(new OAuthOperationError());
187
+ else chunks.push(chunk);
188
+ });
189
+ response.once(
190
+ "end",
191
+ () => resolve({ status: response.statusCode ?? 0, bytes: Buffer.concat(chunks) })
192
+ );
193
+ }
194
+ );
195
+ const onAbort = () => request$1.destroy(abortError());
196
+ signal.addEventListener("abort", onAbort, { once: true });
197
+ const absoluteTimeout = setTimeout(() => request$1.destroy(timeoutError()), remaining);
198
+ request$1.once("timeout", () => request$1.destroy(timeoutError()));
199
+ request$1.once(
200
+ "error",
201
+ (error) => reject(error instanceof DOMException ? error : new OAuthOperationError())
202
+ );
203
+ request$1.once("close", () => {
204
+ clearTimeout(absoluteTimeout);
205
+ signal.removeEventListener("abort", onAbort);
206
+ });
207
+ request$1.end(body);
208
+ });
209
+ }
210
+ };
211
+ var LoopbackCallback = class {
212
+ constructor(server, port, sockets) {
213
+ this.server = server;
214
+ this.sockets = sockets;
215
+ this.redirectUri = `http://127.0.0.1:${port}/oauth/callback`;
216
+ }
217
+ server;
218
+ sockets;
219
+ redirectUri;
220
+ result = Promise.withResolvers();
221
+ accept(target) {
222
+ try {
223
+ const url = new URL(target, this.redirectUri);
224
+ const code = url.searchParams.has("error") ? "" : url.searchParams.get("code") ?? "";
225
+ this.result.resolve([
226
+ (url.searchParams.get("state") ?? "").slice(0, 256),
227
+ code.slice(0, 8192)
228
+ ]);
229
+ return code.length > 0;
230
+ } catch {
231
+ this.result.resolve(["", ""]);
232
+ return false;
233
+ }
234
+ }
235
+ async waitForCode(expectedState, signal, deadlineMonotonicMs) {
236
+ const [state, code] = await raceSignal(this.result.promise, signal, deadlineMonotonicMs);
237
+ const left = Buffer.from(state);
238
+ const right = Buffer.from(expectedState);
239
+ const leftHash = createHash("sha256").update(left).digest();
240
+ const rightHash = createHash("sha256").update(right).digest();
241
+ if (left.byteLength !== right.byteLength || !timingSafeEqual(leftHash, rightHash)) {
242
+ throw new OAuthOperationError();
243
+ }
244
+ return boundedString(code);
245
+ }
246
+ async close() {
247
+ for (const socket of this.sockets) socket.destroy();
248
+ if (!this.server.listening) return;
249
+ await new Promise((resolve) => this.server.close(() => resolve()));
250
+ }
251
+ };
252
+ var LoopbackCallbackFactory = class {
253
+ open(signal, deadlineMonotonicMs) {
254
+ throwIfAborted(signal);
255
+ const remaining = deadlineMonotonicMs - performance.now();
256
+ if (remaining <= 0) return Promise.reject(timeoutError());
257
+ return new Promise((resolve, reject) => {
258
+ let callback;
259
+ const sockets = /* @__PURE__ */ new Set();
260
+ const server = createServer((request, response) => {
261
+ if (request.method !== "GET" || request.url === void 0 || request.url.length > 8192) {
262
+ response.writeHead(400).end();
263
+ callback?.accept("");
264
+ return;
265
+ }
266
+ let validPath = false;
267
+ try {
268
+ validPath = new URL(request.url, "http://127.0.0.1").pathname === "/oauth/callback";
269
+ } catch {
270
+ }
271
+ const accepted = validPath && callback !== void 0 && callback.accept(request.url);
272
+ response.writeHead(accepted ? 200 : 400, { "content-length": "0", connection: "close" }).end();
273
+ });
274
+ server.on("connection", (socket) => {
275
+ sockets.add(socket);
276
+ socket.once("close", () => sockets.delete(socket));
277
+ });
278
+ let settled = false;
279
+ const cleanup = () => {
280
+ clearTimeout(timeout);
281
+ signal.removeEventListener("abort", onAbort);
282
+ };
283
+ const fail = (error) => {
284
+ if (settled) return;
285
+ settled = true;
286
+ cleanup();
287
+ try {
288
+ server.close();
289
+ } catch {
290
+ }
291
+ reject(error);
292
+ };
293
+ const onAbort = () => fail(abortError());
294
+ const timeout = setTimeout(() => fail(timeoutError()), remaining);
295
+ signal.addEventListener("abort", onAbort, { once: true });
296
+ server.once("error", () => fail(new OAuthOperationError()));
297
+ server.listen(0, "127.0.0.1", () => {
298
+ if (settled) {
299
+ for (const socket of sockets) socket.destroy();
300
+ server.close();
301
+ return;
302
+ }
303
+ const address = server.address();
304
+ if (address === null || typeof address === "string") {
305
+ fail(new OAuthOperationError());
306
+ return;
307
+ }
308
+ settled = true;
309
+ cleanup();
310
+ callback = new LoopbackCallback(server, address.port, sockets);
311
+ resolve(callback);
312
+ });
313
+ });
314
+ }
315
+ };
316
+ var SystemBrowser = class {
317
+ async open(url, signal, deadlineMonotonicMs) {
318
+ throwIfAborted(signal);
319
+ const remaining = deadlineMonotonicMs - performance.now();
320
+ if (remaining <= 0) throw timeoutError();
321
+ await new Promise((resolve, reject) => {
322
+ const child = spawn("/usr/bin/open", [url], { stdio: "ignore", shell: false });
323
+ let failure;
324
+ let grace;
325
+ const terminate = (error) => {
326
+ failure ??= error;
327
+ if (child.exitCode !== null || child.signalCode !== null) return;
328
+ child.kill("SIGTERM");
329
+ grace ??= setTimeout(() => {
330
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
331
+ }, 250);
332
+ };
333
+ const onAbort = () => terminate(abortError());
334
+ signal.addEventListener("abort", onAbort, { once: true });
335
+ const timeout = setTimeout(() => terminate(timeoutError()), remaining);
336
+ child.once("error", () => terminate(new OAuthOperationError()));
337
+ child.once("close", (code) => {
338
+ clearTimeout(timeout);
339
+ if (grace !== void 0) clearTimeout(grace);
340
+ signal.removeEventListener("abort", onAbort);
341
+ if (failure !== void 0) reject(failure);
342
+ else if (code === 0) resolve();
343
+ else reject(new OAuthOperationError());
344
+ });
345
+ });
346
+ }
347
+ };
348
+ async function raceSignal(operation, signal, deadlineMonotonicMs, nowMonotonic = () => performance.now()) {
349
+ throwIfAborted(signal);
350
+ let remaining;
351
+ if (deadlineMonotonicMs !== void 0) {
352
+ remaining = deadlineMonotonicMs - nowMonotonic();
353
+ if (remaining <= 0) throw timeoutError();
354
+ }
355
+ const cancellation = Promise.withResolvers();
356
+ const onAbort = () => cancellation.reject(abortError());
357
+ signal.addEventListener("abort", onAbort, { once: true });
358
+ let timer;
359
+ const candidates = [operation, cancellation.promise];
360
+ if (remaining !== void 0) {
361
+ const timeout = new Promise((_, reject) => {
362
+ timer = setTimeout(() => reject(timeoutError()), remaining);
363
+ });
364
+ candidates.push(timeout);
365
+ }
366
+ try {
367
+ return await Promise.race(candidates);
368
+ } finally {
369
+ if (timer !== void 0) clearTimeout(timer);
370
+ signal.removeEventListener("abort", onAbort);
371
+ }
372
+ }
373
+ function base64Url(value) {
374
+ return Buffer.from(value).toString("base64url");
375
+ }
376
+ var GoogleOAuthClient = class _GoogleOAuthClient {
377
+ options;
378
+ dependencies;
379
+ metrics;
380
+ trace;
381
+ slots = /* @__PURE__ */ new Map();
382
+ slotsGate = new Mutex();
383
+ constructor(options) {
384
+ this.options = validatedOptions(options);
385
+ this.metrics = options.metricsSink ?? NOOP_METRICS;
386
+ this.trace = options.traceSink ?? NOOP_TRACE;
387
+ this.dependencies = {
388
+ http: new NodeOAuthTransport(),
389
+ callbackFactory: new LoopbackCallbackFactory(),
390
+ browser: new SystemBrowser(),
391
+ clock: systemClock,
392
+ randomBytes: (count) => randomBytes(count)
393
+ };
394
+ }
395
+ /** @internal Source-relative tests only. */
396
+ static _create(options, dependencies) {
397
+ const client = Object.create(_GoogleOAuthClient.prototype);
398
+ Object.defineProperty(client, "options", { value: validatedOptions(options) });
399
+ Object.defineProperty(client, "dependencies", { value: dependencies });
400
+ Object.defineProperty(client, "metrics", { value: options.metricsSink ?? NOOP_METRICS });
401
+ Object.defineProperty(client, "trace", { value: options.traceSink ?? NOOP_TRACE });
402
+ Object.defineProperty(client, "slots", { value: /* @__PURE__ */ new Map() });
403
+ Object.defineProperty(client, "slotsGate", { value: new Mutex() });
404
+ return client;
405
+ }
406
+ async connect(principalId, signal) {
407
+ principalId = validateOAuthPrincipal(principalId);
408
+ throwIfAborted(signal);
409
+ const clientId = this.requiredClientId();
410
+ const slot = await this.acquireSlot(principalId);
411
+ try {
412
+ let operation;
413
+ while (operation === void 0) {
414
+ let pendingDisconnect;
415
+ await slot.gate.run(() => {
416
+ if (slot.disconnect !== void 0) {
417
+ pendingDisconnect = slot.disconnect;
418
+ return;
419
+ }
420
+ slot.generation += 1;
421
+ slot.blocked = true;
422
+ const previous = [];
423
+ if (slot.connect !== void 0) {
424
+ slot.connect.controller.abort(abortError());
425
+ previous.push(slot.connect.promise);
426
+ }
427
+ if (slot.refresh !== void 0) {
428
+ slot.refresh.controller.abort(abortError());
429
+ previous.push(slot.refresh.promise);
430
+ }
431
+ const controller = new AbortController();
432
+ const generation = slot.generation;
433
+ const promise = (async () => {
434
+ await Promise.allSettled(previous);
435
+ throwIfAborted(controller.signal);
436
+ await slot.gate.run(() => {
437
+ if (slot.generation !== generation || slot.connect?.generation !== generation || slot.connect.controller !== controller) {
438
+ throw authRequired("gmail_grant_missing");
439
+ }
440
+ });
441
+ await this.runConnect(principalId, slot, generation, clientId, controller.signal);
442
+ await slot.gate.run(() => {
443
+ if (slot.generation !== generation || slot.connect?.generation !== generation || slot.connect.controller !== controller) {
444
+ throw authRequired("gmail_grant_missing");
445
+ }
446
+ slot.blocked = false;
447
+ });
448
+ })();
449
+ operation = { generation, controller, promise };
450
+ slot.connect = operation;
451
+ });
452
+ if (pendingDisconnect !== void 0) await raceSignal(pendingDisconnect, signal);
453
+ }
454
+ try {
455
+ await raceSignal(operation.promise, signal);
456
+ } catch (error) {
457
+ operation.controller.abort(abortError());
458
+ await Promise.allSettled([operation.promise]);
459
+ throw error;
460
+ } finally {
461
+ await slot.gate.run(() => {
462
+ if (slot.connect === operation) delete slot.connect;
463
+ });
464
+ }
465
+ } finally {
466
+ await this.releaseSlot(principalId, slot);
467
+ }
468
+ }
469
+ async accessToken(context) {
470
+ const started = this.dependencies.clock.nowMonotonic();
471
+ const span = startSpan(this.trace, "kaji.integration.auth", {
472
+ "integration.name": "gmail",
473
+ "integration.operation": "token",
474
+ "http.status_family": "none"
475
+ });
476
+ let outcome = "error";
477
+ try {
478
+ const token = await this.accessTokenInternal(context);
479
+ outcome = "success";
480
+ return token;
481
+ } catch (error) {
482
+ if (error instanceof DOMException && error.name === "AbortError") outcome = "cancelled";
483
+ span.recordError(error);
484
+ throw error;
485
+ } finally {
486
+ recordMetric(
487
+ this.metrics,
488
+ "kaji.integration.auth_ms",
489
+ Math.max(0, this.dependencies.clock.nowMonotonic() - started),
490
+ { integration: "gmail", operation: "token", outcome }
491
+ );
492
+ span.end();
493
+ }
494
+ }
495
+ async accessTokenInternal(context) {
496
+ const principalId = validateOAuthPrincipal(context.principalId);
497
+ throwIfAborted(context.signal);
498
+ this.checkDeadline(context.deadlineMonotonicMs);
499
+ const slot = await this.acquireSlot(principalId);
500
+ try {
501
+ if (slot.blocked) throw authRequired("gmail_grant_missing");
502
+ const generation = await slot.gate.run(() => {
503
+ if (slot.blocked) throw authRequired("gmail_grant_missing");
504
+ return slot.generation;
505
+ });
506
+ const loaded = await raceSignal(
507
+ this.options.storage.load(principalId, context.signal),
508
+ context.signal,
509
+ context.deadlineMonotonicMs,
510
+ () => this.dependencies.clock.nowMonotonic()
511
+ );
512
+ if (loaded === void 0) throw authRequired("gmail_grant_missing");
513
+ const record = snapshotOAuthCredentialRecord(loaded);
514
+ if (record.state === "revocation_pending") throw authRequired("gmail_grant_missing");
515
+ if (!isSuperset(record.tokens.grantedScopes, this.options.scopes)) {
516
+ this.checkDeadline(context.deadlineMonotonicMs);
517
+ await this.deleteIfCurrent(principalId, slot, generation, context.signal);
518
+ throw authRequired("gmail_scope_drift");
519
+ }
520
+ if (record.tokens.expiresAtEpochMs > this.dependencies.clock.nowWallSeconds() * 1e3 + REFRESH_BUFFER_MS) {
521
+ return slot.gate.run(() => {
522
+ this.checkDeadline(context.deadlineMonotonicMs);
523
+ if (slot.blocked || slot.generation !== generation) {
524
+ throw authRequired("gmail_grant_missing");
525
+ }
526
+ return record.tokens.accessToken;
527
+ });
528
+ }
529
+ return await this.joinRefresh(principalId, slot, generation, record, context);
530
+ } finally {
531
+ await this.releaseSlot(principalId, slot);
532
+ }
533
+ }
534
+ async disconnect(principalId, signal, options = {}) {
535
+ principalId = validateOAuthPrincipal(principalId);
536
+ throwIfAborted(signal);
537
+ const slot = await this.acquireSlot(principalId);
538
+ const completion = Promise.withResolvers();
539
+ let previousDisconnect;
540
+ await slot.gate.run(() => {
541
+ previousDisconnect = slot.disconnect;
542
+ slot.disconnect = completion.promise;
543
+ });
544
+ try {
545
+ if (previousDisconnect !== void 0) await raceSignal(previousDisconnect, signal);
546
+ let generation = 0;
547
+ let operations = [];
548
+ await slot.gate.run(() => {
549
+ slot.generation += 1;
550
+ generation = slot.generation;
551
+ slot.blocked = true;
552
+ if (slot.connect !== void 0) {
553
+ slot.connect.controller.abort(abortError());
554
+ operations.push(slot.connect.promise);
555
+ }
556
+ if (slot.refresh !== void 0) {
557
+ slot.refresh.controller.abort(abortError());
558
+ operations.push(slot.refresh.promise);
559
+ }
560
+ });
561
+ await Promise.allSettled(operations);
562
+ const loaded = await this.options.storage.load(principalId, signal);
563
+ if (loaded === void 0)
564
+ return Object.freeze({ localState: "missing", remoteRevoked: false });
565
+ const record = snapshotOAuthCredentialRecord(loaded);
566
+ if (options.forceLocal === true) {
567
+ await this.deleteBlockedIfCurrent(principalId, slot, generation, signal);
568
+ return Object.freeze({ localState: "deleted", remoteRevoked: false });
569
+ }
570
+ const pending = snapshotOAuthCredentialRecord({ ...record, state: "revocation_pending" });
571
+ const cleanup = new AbortController();
572
+ let response;
573
+ try {
574
+ response = await this.dependencies.http.postForm(
575
+ GOOGLE_REVOKE_URL,
576
+ { token: record.tokens.refreshToken },
577
+ signal,
578
+ this.dependencies.clock.nowMonotonic() + OPERATION_MS
579
+ );
580
+ } catch {
581
+ await this.saveIfCurrent(principalId, slot, generation, pending, cleanup.signal);
582
+ if (signal.aborted) throw abortError();
583
+ return Object.freeze({ localState: "revocation_pending", remoteRevoked: false });
584
+ }
585
+ if (response.status === 200) {
586
+ try {
587
+ await this.deleteBlockedIfCurrent(principalId, slot, generation, cleanup.signal);
588
+ } catch {
589
+ await this.saveIfCurrent(principalId, slot, generation, pending, cleanup.signal);
590
+ return Object.freeze({ localState: "revocation_pending", remoteRevoked: true });
591
+ }
592
+ return Object.freeze({ localState: "deleted", remoteRevoked: true });
593
+ }
594
+ await this.saveIfCurrent(principalId, slot, generation, pending, cleanup.signal);
595
+ return Object.freeze({ localState: "revocation_pending", remoteRevoked: false });
596
+ } finally {
597
+ completion.resolve();
598
+ await slot.gate.run(() => {
599
+ if (slot.disconnect === completion.promise) delete slot.disconnect;
600
+ });
601
+ await this.releaseSlot(principalId, slot);
602
+ }
603
+ }
604
+ requiredClientId() {
605
+ if (this.options.clientId === void 0 || this.options.clientId.length === 0) {
606
+ throw authRequired("gmail_grant_missing");
607
+ }
608
+ return this.options.clientId;
609
+ }
610
+ checkDeadline(deadline) {
611
+ if (deadline !== void 0 && deadline <= this.dependencies.clock.nowMonotonic()) {
612
+ throw timeoutError();
613
+ }
614
+ }
615
+ async runConnect(principalId, slot, generation, clientId, signal) {
616
+ const callbackDeadline = this.dependencies.clock.nowMonotonic() + CALLBACK_MS;
617
+ const callback = await this.dependencies.callbackFactory.open(signal, callbackDeadline);
618
+ try {
619
+ const state = base64Url(this.dependencies.randomBytes(32));
620
+ const verifier = base64Url(this.dependencies.randomBytes(64));
621
+ if (verifier.length < 43 || verifier.length > 128) throw new OAuthOperationError();
622
+ const challenge = createHash("sha256").update(verifier, "ascii").digest("base64url");
623
+ const authorization = new URL(GOOGLE_AUTH_URL);
624
+ authorization.search = new URLSearchParams({
625
+ client_id: clientId,
626
+ redirect_uri: callback.redirectUri,
627
+ response_type: "code",
628
+ scope: this.options.scopes.join(" "),
629
+ state,
630
+ access_type: "offline",
631
+ prompt: "consent",
632
+ code_challenge: challenge,
633
+ code_challenge_method: "S256"
634
+ }).toString();
635
+ await this.dependencies.browser.open(authorization.href, signal, callbackDeadline);
636
+ const code = boundedString(await callback.waitForCode(state, signal, callbackDeadline));
637
+ const form = {
638
+ code,
639
+ code_verifier: verifier,
640
+ client_id: clientId,
641
+ redirect_uri: callback.redirectUri,
642
+ grant_type: "authorization_code"
643
+ };
644
+ if (this.options.clientSecret !== void 0) form.client_secret = this.options.clientSecret;
645
+ const response = await this.dependencies.http.postForm(
646
+ GOOGLE_TOKEN_URL,
647
+ form,
648
+ signal,
649
+ this.dependencies.clock.nowMonotonic() + OPERATION_MS
650
+ );
651
+ if (response.status !== 200) throw new OAuthOperationError();
652
+ const tokens = this.tokensFromResponse(response.bytes, void 0, true);
653
+ await this.saveIfCurrent(
654
+ principalId,
655
+ slot,
656
+ generation,
657
+ snapshotOAuthCredentialRecord({ schemaVersion: 1, state: "active", tokens }),
658
+ signal,
659
+ true
660
+ );
661
+ } catch (error) {
662
+ if (error instanceof DOMException || error instanceof IntegrationExecutionError) throw error;
663
+ throw new OAuthOperationError();
664
+ } finally {
665
+ await callback.close();
666
+ }
667
+ }
668
+ async joinRefresh(principalId, slot, generation, record, context) {
669
+ let flight;
670
+ await slot.gate.run(() => {
671
+ if (slot.blocked || slot.generation !== generation) throw authRequired("gmail_grant_missing");
672
+ if (slot.refresh === void 0 || slot.refresh.generation !== generation) {
673
+ const controller = new AbortController();
674
+ const identity = {};
675
+ const deadline = this.dependencies.clock.nowMonotonic() + OPERATION_MS;
676
+ const promise = this.runRefreshWithDeadline(
677
+ () => this.runRefresh(principalId, slot, generation, record, controller.signal, deadline),
678
+ controller
679
+ ).finally(async () => {
680
+ await slot.gate.run(() => {
681
+ if (slot.refresh?.identity === identity) delete slot.refresh;
682
+ });
683
+ });
684
+ slot.refresh = { identity, generation, controller, promise, waiters: 0 };
685
+ }
686
+ flight = slot.refresh;
687
+ flight.waiters += 1;
688
+ });
689
+ try {
690
+ return await raceSignal(
691
+ flight.promise,
692
+ context.signal,
693
+ context.deadlineMonotonicMs,
694
+ () => this.dependencies.clock.nowMonotonic()
695
+ );
696
+ } finally {
697
+ let abort = false;
698
+ await slot.gate.run(() => {
699
+ flight.waiters -= 1;
700
+ abort = flight.waiters === 0 && slot.refresh?.identity === flight.identity;
701
+ });
702
+ if (abort) {
703
+ flight.controller.abort(abortError());
704
+ await Promise.allSettled([flight.promise]);
705
+ }
706
+ }
707
+ }
708
+ async runRefresh(principalId, slot, generation, record, signal, deadlineMonotonicMs) {
709
+ const form = {
710
+ refresh_token: record.tokens.refreshToken,
711
+ client_id: this.requiredClientId(),
712
+ grant_type: "refresh_token"
713
+ };
714
+ if (this.options.clientSecret !== void 0) form.client_secret = this.options.clientSecret;
715
+ const response = await this.dependencies.http.postForm(
716
+ GOOGLE_TOKEN_URL,
717
+ form,
718
+ signal,
719
+ deadlineMonotonicMs
720
+ );
721
+ if (response.status !== 200) {
722
+ if (providerErrorCode(response.bytes) === "invalid_grant") {
723
+ await this.deleteIfCurrent(principalId, slot, generation, signal);
724
+ throw authRequired("gmail_grant_missing");
725
+ }
726
+ throw new IntegrationExecutionError("api_rejected");
727
+ }
728
+ let tokens;
729
+ try {
730
+ tokens = this.tokensFromResponse(response.bytes, record.tokens, false);
731
+ } catch (error) {
732
+ if (!(error instanceof OAuthScopeDriftError)) throw error;
733
+ await this.deleteIfCurrent(principalId, slot, generation, signal);
734
+ throw authRequired("gmail_scope_drift");
735
+ }
736
+ await this.saveIfCurrent(
737
+ principalId,
738
+ slot,
739
+ generation,
740
+ snapshotOAuthCredentialRecord({ schemaVersion: 1, state: "active", tokens }),
741
+ signal
742
+ );
743
+ return tokens.accessToken;
744
+ }
745
+ async runRefreshWithDeadline(start, controller) {
746
+ const operation = start();
747
+ let timedOut = false;
748
+ let timer;
749
+ const timeout = new Promise((_, reject) => {
750
+ timer = setTimeout(() => {
751
+ timedOut = true;
752
+ controller.abort(abortError());
753
+ reject(timeoutError());
754
+ }, OPERATION_MS);
755
+ });
756
+ try {
757
+ return await Promise.race([operation, timeout]);
758
+ } catch (error) {
759
+ if (!timedOut) throw error;
760
+ await Promise.allSettled([operation]);
761
+ throw timeoutError();
762
+ } finally {
763
+ if (timer !== void 0) clearTimeout(timer);
764
+ }
765
+ }
766
+ tokensFromResponse(bytes, fallback, requireScope) {
767
+ const value = parseProviderJson(bytes);
768
+ if (value.token_type !== "Bearer") throw new OAuthOperationError();
769
+ const expires = value.expires_in;
770
+ if (typeof expires !== "number" || !Number.isFinite(expires) || expires <= 0 || expires > 604800) {
771
+ throw new OAuthOperationError();
772
+ }
773
+ const refreshToken = value.refresh_token === void 0 && fallback !== void 0 ? fallback.refreshToken : boundedString(value.refresh_token);
774
+ let grantedScopes;
775
+ if (value.scope === void 0) {
776
+ if (requireScope || fallback === void 0) throw new OAuthOperationError();
777
+ grantedScopes = fallback.grantedScopes;
778
+ } else {
779
+ if (typeof value.scope !== "string") throw new OAuthOperationError();
780
+ grantedScopes = normalizedScopes(value.scope.split(/\s+/).filter(Boolean), true);
781
+ if (!isSuperset(grantedScopes, this.options.scopes)) {
782
+ if (fallback !== void 0) throw new OAuthScopeDriftError();
783
+ throw new OAuthOperationError();
784
+ }
785
+ }
786
+ return Object.freeze({
787
+ accessToken: boundedString(value.access_token),
788
+ refreshToken,
789
+ expiresAtEpochMs: Math.floor(
790
+ this.dependencies.clock.nowWallSeconds() * 1e3 + expires * 1e3
791
+ ),
792
+ grantedScopes,
793
+ tokenType: "Bearer"
794
+ });
795
+ }
796
+ async saveIfCurrent(principalId, slot, generation, record, signal, allowBlockedActive = false) {
797
+ await slot.gate.run(() => {
798
+ if (slot.generation !== generation || slot.blocked && record.state === "active" && !allowBlockedActive) {
799
+ throw authRequired("gmail_grant_missing");
800
+ }
801
+ return this.options.storage.save(principalId, record, signal);
802
+ });
803
+ }
804
+ async deleteIfCurrent(principalId, slot, generation, signal) {
805
+ await slot.gate.run(() => {
806
+ if (slot.generation !== generation || slot.blocked) throw authRequired("gmail_grant_missing");
807
+ return this.options.storage.delete(principalId, signal);
808
+ });
809
+ }
810
+ async deleteBlockedIfCurrent(principalId, slot, generation, signal) {
811
+ await slot.gate.run(() => {
812
+ if (slot.generation !== generation || !slot.blocked) {
813
+ throw authRequired("gmail_grant_missing");
814
+ }
815
+ return this.options.storage.delete(principalId, signal);
816
+ });
817
+ }
818
+ async acquireSlot(principalId) {
819
+ return this.slotsGate.run(() => {
820
+ let slot = this.slots.get(principalId);
821
+ if (slot === void 0) {
822
+ slot = { gate: new Mutex(), generation: 0, blocked: false, references: 0 };
823
+ this.slots.set(principalId, slot);
824
+ }
825
+ slot.references += 1;
826
+ return slot;
827
+ });
828
+ }
829
+ async releaseSlot(principalId, slot) {
830
+ await this.slotsGate.run(() => {
831
+ slot.references -= 1;
832
+ if (slot.references === 0 && slot.connect === void 0 && slot.refresh === void 0 && slot.disconnect === void 0 && this.slots.get(principalId) === slot) {
833
+ this.slots.delete(principalId);
834
+ }
835
+ });
836
+ }
837
+ };
838
+ function isSuperset(candidate, required) {
839
+ const values = new Set(candidate);
840
+ return required.every((scope) => values.has(scope));
841
+ }
842
+ function validatedOptions(options) {
843
+ if (options === null || typeof options !== "object" || typeof options.storage !== "object") {
844
+ throw policyError();
845
+ }
846
+ const scopes = normalizedScopes(options.scopes);
847
+ const clientId = options.clientId === void 0 || options.clientId === "" ? void 0 : boundedString(options.clientId, 4096);
848
+ const clientSecret = options.clientSecret === void 0 ? void 0 : boundedString(options.clientSecret);
849
+ return Object.freeze({
850
+ ...clientId === void 0 ? {} : { clientId },
851
+ ...clientSecret === void 0 ? {} : { clientSecret },
852
+ scopes,
853
+ storage: options.storage
854
+ });
855
+ }
856
+ var SECURITY = "/usr/bin/security";
857
+ var INTEGRATION_NAME = /^[a-z][a-z0-9_-]*$/;
858
+ var MAX_INTEGRATION_NAME_LENGTH = 128;
859
+ var TIMEOUT_MS = 1e4;
860
+ var MAX_STDOUT_BYTES = 16 * 1024 + 1;
861
+ var MAX_STDERR_BYTES = 8 * 1024;
862
+ var TERM_GRACE_MS = 250;
863
+ var ProcessFailure = class extends Error {
864
+ constructor(kind) {
865
+ super("Keychain process failed");
866
+ this.kind = kind;
867
+ }
868
+ kind;
869
+ };
870
+ var SpawnKeychainProcess = class {
871
+ constructor(spawnChild = spawn) {
872
+ this.spawnChild = spawnChild;
873
+ }
874
+ spawnChild;
875
+ run(args, options) {
876
+ if (options.signal.aborted) return Promise.reject(new ProcessFailure("cancelled"));
877
+ return new Promise((resolve, reject) => {
878
+ let child;
879
+ try {
880
+ child = this.spawnChild(SECURITY, args, {
881
+ shell: false,
882
+ stdio: ["pipe", "pipe", "pipe"]
883
+ });
884
+ } catch {
885
+ reject(new ProcessFailure("failed"));
886
+ return;
887
+ }
888
+ const stdout = [];
889
+ let stdoutBytes = 0;
890
+ let stderrBytes = 0;
891
+ let failure;
892
+ let grace;
893
+ const terminate = (next) => {
894
+ if (failure === void 0) failure = next;
895
+ if (child.exitCode !== null || child.signalCode !== null) return;
896
+ child.kill("SIGTERM");
897
+ grace ??= setTimeout(() => {
898
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
899
+ }, TERM_GRACE_MS);
900
+ };
901
+ child.stdout.on("data", (chunk) => {
902
+ stdoutBytes += chunk.byteLength;
903
+ if (stdoutBytes > options.maxStdoutBytes) terminate(new ProcessFailure("overflow"));
904
+ else stdout.push(chunk);
905
+ });
906
+ child.stderr.on("data", (chunk) => {
907
+ stderrBytes += chunk.byteLength;
908
+ if (stderrBytes > MAX_STDERR_BYTES) terminate(new ProcessFailure("overflow"));
909
+ });
910
+ const onAbort = () => terminate(new ProcessFailure("cancelled"));
911
+ options.signal.addEventListener("abort", onAbort, { once: true });
912
+ const timeout = setTimeout(() => terminate(new ProcessFailure("timeout")), options.timeoutMs);
913
+ child.once("error", () => terminate(new ProcessFailure("failed")));
914
+ child.once("close", (code) => {
915
+ clearTimeout(timeout);
916
+ if (grace !== void 0) clearTimeout(grace);
917
+ options.signal.removeEventListener("abort", onAbort);
918
+ if (failure !== void 0) {
919
+ reject(failure);
920
+ return;
921
+ }
922
+ try {
923
+ resolve({
924
+ code: code ?? 1,
925
+ stdout: new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(stdout))
926
+ });
927
+ } catch {
928
+ reject(new ProcessFailure("overflow"));
929
+ }
930
+ });
931
+ child.stdin.once("error", () => terminate(new ProcessFailure("failed")));
932
+ child.stdin.end(options.stdin);
933
+ });
934
+ }
935
+ };
936
+ function executable() {
937
+ try {
938
+ accessSync(SECURITY, constants.X_OK);
939
+ return true;
940
+ } catch {
941
+ return false;
942
+ }
943
+ }
944
+ function serviceFor(integrationName) {
945
+ if (typeof integrationName !== "string" || integrationName.length > MAX_INTEGRATION_NAME_LENGTH || !INTEGRATION_NAME.test(integrationName)) {
946
+ throw new IntegrationPolicyError();
947
+ }
948
+ return `dev.kaji.oauth.${integrationName}`;
949
+ }
950
+ function accountFor(service, principalId) {
951
+ principalId = validateOAuthPrincipal(principalId);
952
+ return createHash("sha256").update(`${service}\0${principalId}`, "utf8").digest("hex");
953
+ }
954
+ var MacOSKeychainTokenStorage = class _MacOSKeychainTokenStorage {
955
+ process;
956
+ platform;
957
+ executable;
958
+ service;
959
+ constructor(integrationName = "gmail") {
960
+ this.service = serviceFor(integrationName);
961
+ this.process = new SpawnKeychainProcess();
962
+ this.platform = platform();
963
+ this.executable = executable();
964
+ }
965
+ /** @internal Source-relative tests only. */
966
+ static _create(options) {
967
+ const service = serviceFor(options.integrationName ?? "gmail");
968
+ const storage = Object.create(_MacOSKeychainTokenStorage.prototype);
969
+ Object.defineProperties(storage, {
970
+ process: { value: options.process },
971
+ platform: { value: options.platform },
972
+ executable: { value: options.executable },
973
+ service: { value: service }
974
+ });
975
+ return storage;
976
+ }
977
+ async load(principalId, signal) {
978
+ const account = this.preflight(principalId);
979
+ const result = await this.run(
980
+ ["find-generic-password", "-a", account, "-s", this.service, "-w"],
981
+ { signal }
982
+ );
983
+ if (result.code === 44) return void 0;
984
+ if (result.code !== 0) throw new IntegrationAuthError("keychain_locked");
985
+ const output = result.stdout.endsWith("\n") ? result.stdout.slice(0, -1) : result.stdout;
986
+ try {
987
+ if (new TextEncoder().encode(output).byteLength > 16 * 1024) throw new Error();
988
+ return snapshotOAuthCredentialRecord(JSON.parse(output));
989
+ } catch {
990
+ throw new IntegrationAuthError("keychain_corrupt");
991
+ }
992
+ }
993
+ async save(principalId, record, signal) {
994
+ const account = this.preflight(principalId);
995
+ const stdin = canonicalOAuthCredentialJson(record);
996
+ const result = await this.run(
997
+ ["add-generic-password", "-a", account, "-s", this.service, "-U", "-w"],
998
+ { signal, stdin }
999
+ );
1000
+ if (result.code !== 0) throw new IntegrationAuthError("keychain_locked");
1001
+ }
1002
+ async delete(principalId, signal) {
1003
+ const account = this.preflight(principalId);
1004
+ const result = await this.run(["delete-generic-password", "-a", account, "-s", this.service], {
1005
+ signal
1006
+ });
1007
+ if (result.code !== 0 && result.code !== 44) {
1008
+ throw new IntegrationAuthError("keychain_locked");
1009
+ }
1010
+ }
1011
+ preflight(principalId) {
1012
+ const account = accountFor(this.service, principalId);
1013
+ if (this.platform !== "darwin" || !this.executable) {
1014
+ throw new IntegrationAuthError("keychain_unsupported");
1015
+ }
1016
+ return account;
1017
+ }
1018
+ async run(args, options) {
1019
+ try {
1020
+ return await this.process.run(args, {
1021
+ ...options,
1022
+ timeoutMs: TIMEOUT_MS,
1023
+ maxStdoutBytes: MAX_STDOUT_BYTES
1024
+ });
1025
+ } catch (error) {
1026
+ if (error instanceof ProcessFailure && error.kind === "cancelled") {
1027
+ throw new DOMException("Keychain operation cancelled", "AbortError");
1028
+ }
1029
+ if (error instanceof ProcessFailure && error.kind === "overflow") {
1030
+ throw new IntegrationAuthError("keychain_corrupt");
1031
+ }
1032
+ throw new IntegrationAuthError("keychain_locked");
1033
+ }
1034
+ }
1035
+ };
1036
+
1037
+ export { GoogleOAuthClient, MacOSKeychainTokenStorage, canonicalOAuthCredentialJson, snapshotOAuthCredentialRecord };
1038
+ //# sourceMappingURL=auth.js.map
1039
+ //# sourceMappingURL=auth.js.map