@zackbart/connecta 0.37.1 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +5 -4
  3. package/bin/connecta.mjs +1 -39
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +2 -4
  6. package/dist/agent-identity.js +1 -1
  7. package/dist/agent-routes.js +231 -0
  8. package/dist/auth/clerk-sdk/generated.js +2 -1
  9. package/dist/auth/downstream-oauth/consent.js +519 -0
  10. package/dist/auth/downstream-oauth/grant-store.js +439 -0
  11. package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
  12. package/dist/auth/downstream-oauth/refresh-records.js +447 -0
  13. package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
  14. package/dist/auth/downstream-oauth.js +66 -2516
  15. package/dist/auth/downstream-revocation.js +1 -1
  16. package/dist/auth/native.js +27 -0
  17. package/dist/auth/oauth-grant.js +24 -0
  18. package/dist/auth/oauth-token-response.js +1 -1
  19. package/dist/auth/static-oauth.js +153 -131
  20. package/dist/auth/vercel-agent.js +2 -1
  21. package/dist/auth-elicitation.js +9 -3
  22. package/dist/authorization-handoff.js +11 -5
  23. package/dist/branding.d.ts +1 -15
  24. package/dist/branding.js +2 -2
  25. package/dist/bytes.js +42 -0
  26. package/dist/call-admission-context.js +2 -5
  27. package/dist/call-recovery.js +0 -3
  28. package/dist/catalog-cache.js +130 -43
  29. package/dist/catalog-fingerprint.js +2 -18
  30. package/dist/catalog-policy.js +18 -0
  31. package/dist/catalog-service.js +285 -48
  32. package/dist/catalog.js +18 -2
  33. package/dist/config-defaults.d.ts +1 -0
  34. package/dist/config-defaults.js +1 -0
  35. package/dist/config-schema.d.ts +1 -1
  36. package/dist/config-schema.js +6 -2
  37. package/dist/config.d.ts +57 -25
  38. package/dist/config.js +30 -77
  39. package/dist/connector-caller.d.ts +1 -1
  40. package/dist/connector-health.js +59 -0
  41. package/dist/connector-status.js +117 -0
  42. package/dist/connectors/api-connector.d.ts +7 -2
  43. package/dist/connectors/api-connector.js +11 -7
  44. package/dist/connectors/guarded-fetch.d.ts +0 -6
  45. package/dist/connectors/guarded-fetch.js +1 -11
  46. package/dist/connectors/negotiation-cache.js +22 -4
  47. package/dist/connectors/remote-mcp/call.js +164 -0
  48. package/dist/connectors/remote-mcp/catalog.js +278 -0
  49. package/dist/connectors/remote-mcp/config.js +263 -0
  50. package/dist/connectors/remote-mcp/connection.js +671 -0
  51. package/dist/connectors/remote-mcp/errors.js +488 -0
  52. package/dist/connectors/remote-mcp/oauth.js +469 -0
  53. package/dist/connectors/remote-mcp/resources.js +473 -0
  54. package/dist/connectors/remote-mcp/runtime.js +1 -0
  55. package/dist/connectors/remote-mcp/transport.js +544 -0
  56. package/dist/connectors/remote-mcp.js +61 -2898
  57. package/dist/connectors/resource-uri.js +0 -4
  58. package/dist/credential-contract.d.ts +23 -0
  59. package/dist/credential-reads.js +17 -0
  60. package/dist/credential-verification.js +53 -0
  61. package/dist/credentials.d.ts +7 -0
  62. package/dist/credentials.js +60 -25
  63. package/dist/d1.d.ts +0 -89
  64. package/dist/d1.js +0 -34
  65. package/dist/describe-config.d.ts +12 -2
  66. package/dist/describe-config.js +2 -0
  67. package/dist/discovery-health.js +199 -0
  68. package/dist/downstream-input.js +4 -2
  69. package/dist/errors.d.ts +55 -17
  70. package/dist/errors.js +95 -41
  71. package/dist/execute.js +172 -125
  72. package/dist/executor-result.js +5 -2
  73. package/dist/executors/quickjs-runtime.js +2 -25
  74. package/dist/executors/quickjs.js +1 -1
  75. package/dist/guest-functions.js +83 -0
  76. package/dist/guest-runtime.js +63 -20
  77. package/dist/guest-types.d.ts +24 -10
  78. package/dist/http/argument-problems.js +35 -0
  79. package/dist/http/client.js +1 -1
  80. package/dist/http/errors.js +7 -6
  81. package/dist/http/fields.js +192 -13
  82. package/dist/http/hatch-response.js +73 -0
  83. package/dist/http/hatch.js +88 -23
  84. package/dist/http/json-schema.js +1 -1
  85. package/dist/http/node-transport.js +0 -1
  86. package/dist/http/oauth.js +4 -3
  87. package/dist/http/paging.js +6 -9
  88. package/dist/http/provider.js +33 -5
  89. package/dist/http/safety.js +9 -1
  90. package/dist/http/services.js +1 -8
  91. package/dist/http/tools.js +45 -30
  92. package/dist/http/vendor.js +7 -7
  93. package/dist/identity.js +2 -6
  94. package/dist/index.d.ts +1 -1
  95. package/dist/index.js +9 -2
  96. package/dist/invocation-auth.js +2 -1
  97. package/dist/invocation.js +21 -6
  98. package/dist/membership.d.ts +13 -5
  99. package/dist/membership.js +34 -2
  100. package/dist/meta-tools.js +50 -35
  101. package/dist/native-recovery.js +4 -16
  102. package/dist/native-tools.js +6 -2
  103. package/dist/oauth-handoff.js +2 -2
  104. package/dist/oauth-sealing.js +10 -0
  105. package/dist/operation-vocabulary.js +136 -14
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +2 -49
  108. package/dist/operator-ui/contract.d.ts +12 -2
  109. package/dist/operator-ui/generated.js +3 -3
  110. package/dist/operator-ui/model.d.ts +22 -1
  111. package/dist/program-writes.js +3 -8
  112. package/dist/provider-call.js +0 -7
  113. package/dist/provider.d.ts +1 -1
  114. package/dist/providers/_shared/google/kit.js +1 -6
  115. package/dist/providers/_shared/google/service-account.js +1 -3
  116. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  117. package/dist/providers/_shared/guide.js +3 -0
  118. package/dist/providers/_shared/json.js +12 -0
  119. package/dist/providers/_shared/rest/compact-index.js +1 -1
  120. package/dist/providers/_shared/rest/kit.js +1 -3
  121. package/dist/providers/_shared/rest/operation-index.js +49 -53
  122. package/dist/providers/_shared/rest/value-safety.js +106 -28
  123. package/dist/providers/breeze/api.js +9 -8
  124. package/dist/providers/breeze/index.js +4 -2
  125. package/dist/providers/breeze/skill.generated.js +1 -1
  126. package/dist/providers/breeze/tools.js +7 -7
  127. package/dist/providers/ccb/api.js +3 -7
  128. package/dist/providers/ccb/index.js +7 -1
  129. package/dist/providers/ccb/skill.generated.js +1 -1
  130. package/dist/providers/ccb/tools.js +1 -6
  131. package/dist/providers/cloudflare/api.js +10 -3
  132. package/dist/providers/cloudflare/index.js +2 -1
  133. package/dist/providers/cloudflare/safety.js +3 -3
  134. package/dist/providers/cloudflare/skill.generated.js +1 -1
  135. package/dist/providers/docs/lib/schemas.js +24 -22
  136. package/dist/providers/docs/safety.js +2 -0
  137. package/dist/providers/docs/skill.generated.js +1 -1
  138. package/dist/providers/docs/tools.js +4 -14
  139. package/dist/providers/drive/api.js +1 -0
  140. package/dist/providers/drive/lib/contracts.js +8 -5
  141. package/dist/providers/drive/skill.generated.js +1 -1
  142. package/dist/providers/drive/tools.js +23 -41
  143. package/dist/providers/forms/api.js +1 -0
  144. package/dist/providers/forms/skill.generated.js +1 -1
  145. package/dist/providers/forms/tools.js +11 -16
  146. package/dist/providers/github/api.js +1 -0
  147. package/dist/providers/github/lib/discovery.js +1 -1
  148. package/dist/providers/github/skill.generated.js +1 -1
  149. package/dist/providers/github/tools.js +61 -17
  150. package/dist/providers/gmail/lib/schemas.js +60 -50
  151. package/dist/providers/gmail/skill.generated.js +1 -1
  152. package/dist/providers/gmail/tools.js +12 -26
  153. package/dist/providers/granola/index.js +2 -0
  154. package/dist/providers/granola/skill.generated.js +1 -1
  155. package/dist/providers/overflow/api.js +8 -4
  156. package/dist/providers/overflow/index.js +2 -1
  157. package/dist/providers/overflow/skill.generated.js +1 -1
  158. package/dist/providers/overflow/tools.js +3 -7
  159. package/dist/providers/planning-center/api.js +5 -2
  160. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  161. package/dist/providers/planning-center/lib/json-api.js +1 -9
  162. package/dist/providers/planning-center/skill.generated.js +1 -1
  163. package/dist/providers/planning-center/tools.js +2 -1
  164. package/dist/providers/quickbooks/safety.js +80 -39
  165. package/dist/providers/quickbooks/skill.generated.js +1 -1
  166. package/dist/providers/sheets/api.js +1 -0
  167. package/dist/providers/sheets/skill.generated.js +1 -1
  168. package/dist/providers/sheets/tools.js +14 -23
  169. package/dist/providers/slides/skill.generated.js +1 -1
  170. package/dist/providers/slides/tools.js +19 -26
  171. package/dist/providers/stripe/index.js +2 -1
  172. package/dist/providers/stripe/safety.js +2 -2
  173. package/dist/providers/stripe/skill.generated.js +1 -1
  174. package/dist/providers/tithely/api.js +5 -8
  175. package/dist/providers/tithely/index.js +3 -2
  176. package/dist/providers/tithely/skill.generated.js +1 -1
  177. package/dist/providers/tithely/tools.js +7 -5
  178. package/dist/providers/vercel/api.js +2 -7
  179. package/dist/providers/vercel/lib/pin.js +1 -1
  180. package/dist/providers/vercel/safety.js +1 -1
  181. package/dist/providers/vercel/skill.generated.js +1 -1
  182. package/dist/providers/vercel/tools.js +6 -4
  183. package/dist/redaction-marker.js +2 -2
  184. package/dist/registry.d.ts +11 -5
  185. package/dist/registry.js +46 -20
  186. package/dist/request-principal.js +1 -1
  187. package/dist/request-state.js +2 -2
  188. package/dist/routes/access-tokens.js +1 -1
  189. package/dist/routes/activity-disclosure.js +1 -1
  190. package/dist/routes/connect.js +2 -2
  191. package/dist/routes/credentials.js +130 -61
  192. package/dist/routes/dispatch.js +5 -1
  193. package/dist/routes/mcp.js +34 -17
  194. package/dist/routes/oauth-management.js +1 -1
  195. package/dist/routes/operator.js +6 -5
  196. package/dist/routes/shared.d.ts +13 -8
  197. package/dist/routes/shared.js +15 -9
  198. package/dist/routes/ui-config.js +3 -3
  199. package/dist/routes/ui-data.js +126 -20
  200. package/dist/routes/ui.js +17 -1
  201. package/dist/runtime/call-admission.js +1 -1
  202. package/dist/runtime/call.js +8 -3
  203. package/dist/runtime/native-storage.js +9 -0
  204. package/dist/runtime/request-reads.js +182 -0
  205. package/dist/runtime/request-scope.js +16 -0
  206. package/dist/runtime/settlement.js +1 -1
  207. package/dist/runtime/shared-read.js +2 -15
  208. package/dist/runtime/storage.js +12 -0
  209. package/dist/sent-secrets.d.ts +1 -2
  210. package/dist/sent-secrets.js +3 -8
  211. package/dist/server.js +4 -4
  212. package/dist/skills.js +44 -54
  213. package/dist/sqlite.d.ts +0 -19
  214. package/dist/sqlite.js +2 -83
  215. package/dist/storage/keys.js +69 -128
  216. package/dist/storage/memory.js +8 -2
  217. package/dist/storage/sql-native.js +27 -101
  218. package/dist/storage/sql.d.ts +1 -24
  219. package/dist/storage/sql.js +13 -14
  220. package/dist/storage-digest.js +32 -0
  221. package/dist/timeout.js +8 -0
  222. package/dist/tool-error.js +4 -0
  223. package/dist/types.d.ts +33 -0
  224. package/dist/url-safety.js +0 -4
  225. package/dist/usage-guide.js +18 -37
  226. package/dist/validate.d.ts +3 -0
  227. package/dist/validate.js +220 -158
  228. package/dist/version.d.ts +1 -1
  229. package/dist/version.js +1 -1
  230. package/dist/worker.js +1 -1
  231. package/dist/write-outcome.js +0 -3
  232. package/documentation/README.md +19 -55
  233. package/documentation/architecture.md +436 -515
  234. package/documentation/auth.md +229 -88
  235. package/documentation/code-mode.md +201 -94
  236. package/documentation/deploying.md +25 -43
  237. package/documentation/integrating.md +18 -19
  238. package/documentation/meta-tools.md +225 -51
  239. package/documentation/migration-0.33.md +10 -6
  240. package/documentation/migration-0.34.md +5 -5
  241. package/documentation/migration-retired-layouts.md +74 -0
  242. package/documentation/operator-tests.md +20 -120
  243. package/documentation/operator-ui.md +108 -16
  244. package/documentation/pool-membership-upgrade.md +44 -0
  245. package/documentation/provider-auth-migration.md +4 -10
  246. package/documentation/provider-migration-0.29.md +4 -15
  247. package/documentation/providers.md +105 -0
  248. package/examples/worker/AGENTS.md +14 -16
  249. package/examples/worker/README.md +24 -118
  250. package/examples/worker/src/connecta.config.ts +1 -0
  251. package/guides/connecting-agents.md +9 -31
  252. package/guides/how-it-works.md +6 -4
  253. package/guides/safety-and-trust.md +4 -1
  254. package/package.json +19 -19
  255. package/templates/node/AGENTS.md +13 -12
  256. package/templates/node/README.md +9 -26
  257. package/templates/node/package.json +1 -1
  258. package/templates/node/src/connecta.config.ts +1 -0
  259. package/dist/auth/oauth-v2-migration.js +0 -135
  260. package/dist/provider-call-facts.d.ts +0 -115
  261. package/dist/provider-call.d.ts +0 -112
  262. package/dist/storage/d1-copy.js +0 -255
  263. package/dist/storage/sql-schema.d.ts +0 -6
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
@@ -0,0 +1,519 @@
1
+ // Consent flow records for KvOAuthProvider: the consent's state and verifier,
2
+ // its flow record and binding, the authorization URL it hands out, and the
3
+ // callback's single code exchange.
4
+ import { bytesToHex, sha256Hex } from "../../bytes.js";
5
+ import { byteReadResponse } from "../../byte-read-response.js";
6
+ import { ConnectorCallError } from "../../errors.js";
7
+ import { keepErrorName } from "../../redaction-identity.js";
8
+ import { runEdge } from "../../runtime/run.js";
9
+ import { OAUTH_FLOW_TTL_SECONDS, oauthFlowKeys, oauthRefreshSpentKeys } from "../../storage/keys.js";
10
+ import { authCall, authCatch, authFlow, authInvoke, authMethod, authProgram, authStorage, authThen, authValue, registerAuth, } from "../native.js";
11
+ import { discoveryIssuer } from "../oauth-grant.js";
12
+ import { GRANT, epochOf, parsed, plainObject } from "./grant-store.js";
13
+ import { aborted } from "./refresh-coordinator.js";
14
+ import { storedRefreshSpent } from "./refresh-records.js";
15
+ /** @internal Keep the SDK's cloned body reads inside the code exchange's request. */
16
+ export function codeExchangeResponse(response, signal) {
17
+ if (!response.body)
18
+ return response;
19
+ const reader = response.body.getReader();
20
+ void reader.closed.catch(() => { });
21
+ let closed = false;
22
+ let abort;
23
+ const release = () => {
24
+ closed = true;
25
+ signal.removeEventListener("abort", abort);
26
+ reader.releaseLock();
27
+ };
28
+ const body = new ReadableStream({
29
+ start(controller) {
30
+ abort = () => {
31
+ if (closed)
32
+ return;
33
+ void reader.cancel().catch(() => { });
34
+ release();
35
+ controller.error(signal.reason);
36
+ };
37
+ signal.addEventListener("abort", abort, { once: true });
38
+ if (signal.aborted)
39
+ abort();
40
+ },
41
+ async pull(controller) {
42
+ try {
43
+ const next = await reader.read();
44
+ if (closed)
45
+ return;
46
+ if (next.done) {
47
+ release();
48
+ controller.close();
49
+ }
50
+ else
51
+ controller.enqueue(next.value);
52
+ }
53
+ catch (error) {
54
+ if (!closed) {
55
+ release();
56
+ controller.error(error);
57
+ }
58
+ }
59
+ },
60
+ cancel() {
61
+ if (!closed) {
62
+ void reader.cancel().catch(() => { });
63
+ release();
64
+ }
65
+ },
66
+ });
67
+ return byteReadResponse(new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers }));
68
+ }
69
+ /**
70
+ * A callback whose consent another callback already claimed: fixed text, and
71
+ * nothing was sent. Its own class so the callback route can answer it as the
72
+ * already-used link it is, not as an exchange the provider refused.
73
+ */
74
+ export class OAuthCallbackClaimedError extends ConnectorCallError {
75
+ constructor(connectorId) {
76
+ super("connector_call_failed", `Connector "${connectorId}" authorization callback was already used by another request; nothing was exchanged.`);
77
+ this.name = "OAuthCallbackClaimedError";
78
+ }
79
+ }
80
+ keepErrorName(OAuthCallbackClaimedError, "OAuthCallbackClaimedError");
81
+ /** A 256-bit random opaque value, hex-encoded — used for the OAuth `state`. */
82
+ export function randomState() {
83
+ const bytes = new Uint8Array(32);
84
+ crypto.getRandomValues(bytes);
85
+ return bytesToHex(bytes);
86
+ }
87
+ /** Lowercase hex SHA-256 of a consent's `state`: the name of its flow record. */
88
+ export async function oauthStateDigest(state) {
89
+ return sha256Hex(state);
90
+ }
91
+ const FLOW_VERSION = 1;
92
+ /**
93
+ * How long a pending authorization URL may be handed out again instead of
94
+ * starting a fresh flow. The authorization server's half of it expires: a
95
+ * pushed request URI lives seconds to minutes (RFC 9126), and login
96
+ * transactions are commonly held for minutes. Past this age a reissued URL
97
+ * more likely lands on an expired session than on consent, and a fresh start
98
+ * costs one authorization request, not a registration. It sits inside the
99
+ * flow record's fifteen minutes.
100
+ */
101
+ const PENDING_AUTHORIZATION_MAX_AGE_MS = 10 * 60 * 1000;
102
+ export function discoveryBinding(state) {
103
+ return runEdge(authProgram(discoveryBindingNative, [state]));
104
+ }
105
+ const discoveryBindingNative = /* @__PURE__ */ registerAuth(discoveryBinding, function (state) {
106
+ return authFlow(function* () {
107
+ return state
108
+ ? {
109
+ issuer: discoveryIssuer(state),
110
+ tokenEndpoint: state.authorizationServerMetadata?.token_endpoint,
111
+ ...(state.authorizationServerMetadata?.authorization_response_iss_parameter_supported === true
112
+ ? { issRequired: true }
113
+ : {}),
114
+ discovery: (yield authCall(oauthStateDigest, [JSON.stringify(state)])),
115
+ }
116
+ : {};
117
+ });
118
+ });
119
+ /** KvOAuthProvider's consent and code exchange methods; `authMethods` installs them. */
120
+ export const consentMethods = {
121
+ encodeFlow: function (key, flow) {
122
+ return authFlow(function* () {
123
+ const { verifier, ...rest } = flow;
124
+ const stored = { connectaOAuthFlow: FLOW_VERSION, ...rest };
125
+ if (verifier !== undefined) {
126
+ if (this.sealer) {
127
+ stored.verifier = (yield authInvoke(this, "wait", [
128
+ authMethod(() => {
129
+ return authInvoke(this.sealer, "seal", [this.flowLabel(key, flow.epoch), verifier]);
130
+ }),
131
+ ]));
132
+ stored.sealed = true;
133
+ }
134
+ else {
135
+ stored.verifier = verifier;
136
+ }
137
+ }
138
+ return JSON.stringify(stored);
139
+ }.bind(this));
140
+ },
141
+ openFlow: function (key, raw, withVerifier) {
142
+ return authFlow(function* () {
143
+ const value = parsed(raw);
144
+ if (!plainObject(value) ||
145
+ value.connectaOAuthFlow !== FLOW_VERSION ||
146
+ typeof value.epoch !== "string" ||
147
+ typeof value.at !== "number" ||
148
+ (value.url !== undefined && typeof value.url !== "string") ||
149
+ (value.verifier !== undefined && typeof value.verifier !== "string")) {
150
+ return undefined;
151
+ }
152
+ const flow = {
153
+ epoch: value.epoch,
154
+ at: value.at,
155
+ ...(plainObject(value.binding) ? { binding: value.binding } : {}),
156
+ ...(typeof value.url === "string" ? { url: value.url } : {}),
157
+ ...(value.consumed === true ? { consumed: true } : {}),
158
+ };
159
+ if (!withVerifier || typeof value.verifier !== "string")
160
+ return flow;
161
+ if (value.sealed !== true)
162
+ return { ...flow, verifier: value.verifier };
163
+ try {
164
+ if (!this.sealer)
165
+ return undefined;
166
+ return {
167
+ ...flow,
168
+ verifier: (yield authInvoke(this, "wait", [
169
+ authMethod(() => {
170
+ return authInvoke(this.sealer, "open", [
171
+ this.flowLabel(key, flow.epoch),
172
+ value.verifier,
173
+ ]);
174
+ }),
175
+ ])),
176
+ };
177
+ }
178
+ catch {
179
+ if (this.signal?.aborted)
180
+ throw aborted(this.signal);
181
+ this.sealer?.warn(`[connecta] connector "${this.connectorId}" has a sealed OAuth consent ` +
182
+ "the configured vault cannot open; its callback is refused.");
183
+ return undefined;
184
+ }
185
+ }.bind(this));
186
+ },
187
+ latestConsent: function () {
188
+ return authFlow(function* () {
189
+ const grant = (yield authInvoke(this, "boundGrant", []));
190
+ if (grant.flow === undefined)
191
+ return undefined;
192
+ const key = oauthFlowKeys.flow(grant.flow);
193
+ const raw = (yield authInvoke(this, "wait", [
194
+ authMethod(() => {
195
+ return authStorage(this.storage, "get", [key]);
196
+ }),
197
+ ]));
198
+ const flow = raw === null ? undefined : (yield authInvoke(this, "openFlow", [key, raw, false]));
199
+ return flow && !flow.consumed && flow.epoch === grant.epoch && flow.url !== undefined
200
+ ? { ...flow, url: flow.url }
201
+ : undefined;
202
+ }.bind(this));
203
+ },
204
+ verifyCallbackState: function (state) {
205
+ return authFlow(function* () {
206
+ if (this.callback?.key ===
207
+ oauthFlowKeys.flow((yield authInvoke(this, "wait", [
208
+ authMethod(() => {
209
+ return authCall(oauthStateDigest, [state]);
210
+ }),
211
+ ]))))
212
+ return true;
213
+ return (yield authInvoke(this, "verifyState", [state]));
214
+ }.bind(this));
215
+ },
216
+ redirectToAuthorization: function (authorizationUrl) {
217
+ return authFlow(function* () {
218
+ if (this.signal?.aborted)
219
+ return;
220
+ if (!this.allowAuthorization)
221
+ throw this.authorizationRefused();
222
+ for (const [name, value] of this.clientOptions?.authorizationParams ?? []) {
223
+ authorizationUrl.searchParams.set(name, value);
224
+ }
225
+ this.assertCredentialFree(authorizationUrl.toString(), (yield authInvoke(this, "boundGrant", [])).body, true);
226
+ const state = this.consent.state ?? authorizationUrl.searchParams.get("state");
227
+ if (!state)
228
+ throw new Error(`OAuth consent for "${this.connectorId}" carries no state`);
229
+ const client = this.clientOptions?.client;
230
+ if (client && !(yield authInvoke(this, "boundGrant", [])).body.client) {
231
+ (yield authInvoke(this, "saveClientInformation", [
232
+ { client_id: client.clientId },
233
+ { issuer: client.issuer },
234
+ ]));
235
+ }
236
+ const configured = this.configuredAuthorizationBinding();
237
+ if (configured?.issuer !== undefined) {
238
+ const issuer = configured.issuer;
239
+ // Static adapters do not save discovery or client configuration. Publish
240
+ // their server identity before consent so a changed endpoint fences the
241
+ // old grant before any code exchange can leave.
242
+ (yield authInvoke(this, "updateGrant", [
243
+ (grant) => (grant.body.issuer === issuer ? undefined : this.withIssuer(grant, { issuer })),
244
+ ]));
245
+ }
246
+ const epoch = (yield authInvoke(this, "flowEpoch", []));
247
+ this.epoch = epoch;
248
+ const digest = (yield authInvoke(this, "wait", [
249
+ authMethod(() => {
250
+ return authCall(oauthStateDigest, [state]);
251
+ }),
252
+ ]));
253
+ const key = oauthFlowKeys.flow(digest);
254
+ const url = authorizationUrl.toString();
255
+ const { verifier } = this.consent;
256
+ const grant = (yield authInvoke(this, "boundGrant", []));
257
+ this.assertCredentialFree(url, grant.body, true);
258
+ const binding = this.configuredAuthorizationBinding() ?? {
259
+ ...(yield authInvoke(this, "wait", [
260
+ authMethod(() => {
261
+ return authCall(discoveryBinding, [this.consentDiscovery ?? grant.body.discovery]);
262
+ }),
263
+ ])),
264
+ issuer: discoveryIssuer(this.consentDiscovery) ?? grant.body.issuer,
265
+ clientId: authorizationUrl.searchParams.get("client_id") ?? grant.body.client?.value.client_id,
266
+ };
267
+ const record = (yield authInvoke(this, "encodeFlow", [
268
+ key,
269
+ {
270
+ epoch,
271
+ at: Date.now(),
272
+ binding,
273
+ url,
274
+ ...(verifier !== undefined ? { verifier } : {}),
275
+ },
276
+ ]));
277
+ if (this.signal?.aborted)
278
+ return;
279
+ if (!(yield authInvoke(this, "wait", [
280
+ authMethod(() => {
281
+ return authStorage(this.storage, "compareAndSet", [
282
+ key,
283
+ null,
284
+ record,
285
+ { ttlSeconds: OAUTH_FLOW_TTL_SECONDS },
286
+ ]);
287
+ }),
288
+ ]))) {
289
+ throw new Error(`OAuth consent for "${this.connectorId}" reused a state`);
290
+ }
291
+ let published = false;
292
+ try {
293
+ published = (yield authInvoke(this, "updateGrant", [
294
+ (grant) => ({ ...grant, flow: digest }),
295
+ { epoch },
296
+ ]));
297
+ }
298
+ finally {
299
+ if (!published)
300
+ (yield authCatch(authInvoke(this, "wait", [
301
+ authMethod(() => {
302
+ return authStorage(this.storage, "compareAndSet", [key, record, null]);
303
+ }),
304
+ ]), () => { }));
305
+ }
306
+ if (published)
307
+ this.published = { epoch, url };
308
+ }.bind(this));
309
+ },
310
+ consentUrl: function (signal) {
311
+ signal = signal === undefined ? this.signal : signal;
312
+ return authFlow(function* () {
313
+ const published = this.published;
314
+ if (!published)
315
+ return undefined;
316
+ if (epochOf((yield authInvoke(this, "wait", [
317
+ authMethod(() => {
318
+ return authStorage(this.storage, "get", [GRANT]);
319
+ }),
320
+ signal,
321
+ ]))) !== published.epoch)
322
+ throw this.flowSuperseded();
323
+ this.assertCredentialFree(published.url, (yield authInvoke(this, "boundGrant", [signal])).body, true);
324
+ return published.url;
325
+ }.bind(this));
326
+ },
327
+ pendingAuthorizationUrl: function () {
328
+ return authFlow(function* () {
329
+ const url = (yield authInvoke(this, "latestConsent", []))?.url;
330
+ this.assertCredentialFree(url, (yield authInvoke(this, "boundGrant", [])).body, true);
331
+ return url;
332
+ }.bind(this));
333
+ },
334
+ reusablePendingAuthorizationUrl: function () {
335
+ return authFlow(function* () {
336
+ const consent = (yield authInvoke(this, "latestConsent", []));
337
+ if (!consent || Math.abs(Date.now() - consent.at) >= PENDING_AUTHORIZATION_MAX_AGE_MS) {
338
+ return undefined;
339
+ }
340
+ this.assertCredentialFree(consent.url, (yield authInvoke(this, "boundGrant", [])).body, true);
341
+ let clientId;
342
+ try {
343
+ clientId = new URL(consent.url).searchParams.get("client_id");
344
+ }
345
+ catch {
346
+ return undefined;
347
+ }
348
+ const client = (yield authInvoke(this, "clientInformation", []));
349
+ return clientId !== null && client?.client_id === clientId ? consent.url : undefined;
350
+ }.bind(this));
351
+ },
352
+ verifyState: function (candidate) {
353
+ return authFlow(function* () {
354
+ this.callback = undefined;
355
+ if (candidate === null)
356
+ return false;
357
+ const key = oauthFlowKeys.flow((yield authInvoke(this, "wait", [
358
+ authMethod(() => {
359
+ return authCall(oauthStateDigest, [candidate]);
360
+ }),
361
+ ])));
362
+ const raw = (yield authInvoke(this, "wait", [
363
+ authMethod(() => {
364
+ return authStorage(this.storage, "get", [key]);
365
+ }),
366
+ ]));
367
+ const flow = raw === null ? undefined : (yield authInvoke(this, "openFlow", [key, raw, true]));
368
+ if (raw === null || !flow || flow.consumed)
369
+ return false;
370
+ this.callback = { key, raw, flow, claimed: false };
371
+ this.epoch = flow.epoch;
372
+ return true;
373
+ }.bind(this));
374
+ },
375
+ claimCodeExchange: function () {
376
+ return authFlow(function* () {
377
+ if (this.exchangeRefusal)
378
+ return this.exchangeRefusal.clone();
379
+ (yield authInvoke(this, "claimConsent", []));
380
+ return undefined;
381
+ }.bind(this));
382
+ },
383
+ consumeAuthError: function () {
384
+ return authFlow(function* () {
385
+ const { grant } = (yield authInvoke(this, "readGrant", []));
386
+ if (!this.callback || grant.epoch !== this.callback.flow.epoch)
387
+ throw this.flowSuperseded();
388
+ (yield authInvoke(this, "checkFlowBinding", [grant]));
389
+ (yield authInvoke(this, "claimConsent", []));
390
+ this.callback = undefined;
391
+ }.bind(this));
392
+ },
393
+ claimConsent: function () {
394
+ return authFlow(function* () {
395
+ const callback = this.callback;
396
+ if (!callback)
397
+ throw this.flowSuperseded();
398
+ if (callback.claimed)
399
+ throw new OAuthCallbackClaimedError(this.connectorId);
400
+ callback.claimed = true;
401
+ const { epoch, at } = callback.flow;
402
+ const claimed = JSON.stringify({
403
+ connectaOAuthFlow: FLOW_VERSION,
404
+ epoch,
405
+ at,
406
+ consumed: true,
407
+ });
408
+ const remaining = Math.ceil((at + OAUTH_FLOW_TTL_SECONDS * 1000 - Date.now()) / 1000);
409
+ const ttlSeconds = Math.min(OAUTH_FLOW_TTL_SECONDS, Math.max(1, remaining));
410
+ if ((yield authInvoke(this, "wait", [
411
+ authMethod(() => {
412
+ return authStorage(this.storage, "compareAndSet", [
413
+ callback.key,
414
+ callback.raw,
415
+ claimed,
416
+ { ttlSeconds },
417
+ ]);
418
+ }),
419
+ ]))) {
420
+ return;
421
+ }
422
+ if (epochOf((yield authInvoke(this, "wait", [
423
+ authMethod(() => {
424
+ return authStorage(this.storage, "get", [GRANT]);
425
+ }),
426
+ ]))) !== epoch)
427
+ throw this.flowSuperseded();
428
+ throw new OAuthCallbackClaimedError(this.connectorId);
429
+ }.bind(this));
430
+ },
431
+ dispatchCodeExchange: function (send, endpoint) {
432
+ return authFlow(function* () {
433
+ const epoch = this.epoch;
434
+ if (!this.codeExchange || epoch === undefined)
435
+ throw this.flowSuperseded();
436
+ const raw = (yield authInvoke(this, "wait", [
437
+ authMethod(() => {
438
+ return authStorage(this.storage, "get", [GRANT]);
439
+ }),
440
+ ]));
441
+ const grant = (yield authInvoke(this, "openGrant", [raw]));
442
+ if (grant.epoch !== epoch)
443
+ throw this.flowSuperseded();
444
+ (yield authInvoke(this, "checkFlowBinding", [grant]));
445
+ if (endpoint !== undefined && this.callback?.flow.binding?.tokenEndpoint !== String(endpoint)) {
446
+ throw this.flowSuperseded();
447
+ }
448
+ // Opening ciphertext may yield. Re-read the exact record after opening,
449
+ // then check and send in the same reaction, without another await.
450
+ return (yield authInvoke(this, "wait", [
451
+ authMethod(() => {
452
+ return authThen(authStorage(this.storage, "get", [GRANT]), authMethod((current) => {
453
+ return authFlow(function* () {
454
+ if (this.signal?.aborted)
455
+ throw aborted(this.signal);
456
+ if (current !== raw)
457
+ throw this.flowSuperseded();
458
+ const sent = send();
459
+ sent.catch(() => { });
460
+ return (yield authValue(() => sent));
461
+ }.bind(this));
462
+ }));
463
+ }),
464
+ ]));
465
+ }.bind(this));
466
+ },
467
+ recordCodeExchangeTokens: function (tokens) {
468
+ return authFlow(function* () {
469
+ this.exchangeConsent = undefined;
470
+ if (tokens.refresh_token === undefined)
471
+ return;
472
+ try {
473
+ const digest = (yield authInvoke(this, "wait", [
474
+ authMethod(() => {
475
+ return authCall(oauthStateDigest, [tokens.refresh_token]);
476
+ }),
477
+ ]));
478
+ const resolution = (yield authInvoke(this, "wait", [
479
+ authMethod(() => {
480
+ return authStorage(this.storage, "get", [oauthRefreshSpentKeys.spent(digest)]);
481
+ }),
482
+ ]));
483
+ const spent = storedRefreshSpent(resolution);
484
+ // Observing the exact resolved storage write before consent completion
485
+ // proves their order without comparing isolate clocks. A consent that
486
+ // saw outstanding/ambiguous cannot become eligible on a delayed save.
487
+ if (spent?.state === "resolved")
488
+ this.exchangeConsent = { digest, resolution: resolution };
489
+ }
490
+ catch {
491
+ if (this.signal?.aborted)
492
+ throw aborted(this.signal);
493
+ throw this.credentialWriteError();
494
+ }
495
+ }.bind(this));
496
+ },
497
+ sweepConsents: function () {
498
+ return authFlow(function* () {
499
+ try {
500
+ const consents = [];
501
+ for (const key of (yield authStorage(this.storage, "list", [oauthFlowKeys.prefix]))) {
502
+ const raw = (yield authStorage(this.storage, "get", [key]));
503
+ if (raw !== null)
504
+ consents.push([key, raw]);
505
+ }
506
+ const live = epochOf((yield authStorage(this.storage, "get", [GRANT])));
507
+ for (const [key, raw] of consents) {
508
+ const flow = parsed(raw);
509
+ if (plainObject(flow) && flow.epoch === live)
510
+ continue;
511
+ (yield authStorage(this.storage, "compareAndSet", [key, raw, null]));
512
+ }
513
+ }
514
+ catch {
515
+ // Hygiene only: the epoch, not this, fences a stale consent.
516
+ }
517
+ }.bind(this));
518
+ },
519
+ };