@tangle-network/agent-integrations 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/bin/tangle-catalog-runtime.js +7 -6
- package/dist/bin/tangle-catalog-runtime.js.map +1 -1
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +11 -6
- package/dist/{chunk-SVQ4PHDZ.js → chunk-5ASL5XNX.js} +2 -2
- package/dist/{chunk-P24T3MLM.js → chunk-DACSERTI.js} +2 -2
- package/dist/chunk-FDZIQVK7.js +284 -0
- package/dist/chunk-FDZIQVK7.js.map +1 -0
- package/dist/{chunk-JU25UDN2.js → chunk-JOILC44P.js} +11 -5
- package/dist/chunk-JOILC44P.js.map +1 -0
- package/dist/{chunk-UWRYFPJW.js → chunk-M2RFFAMB.js} +559 -411
- package/dist/chunk-M2RFFAMB.js.map +1 -0
- package/dist/{chunk-4JQ754PA.js → chunk-VVC7U7W7.js} +28 -1
- package/dist/{chunk-4JQ754PA.js.map → chunk-VVC7U7W7.js.map} +1 -1
- package/dist/{chunk-ATYHZXLL.js → chunk-Y6O3MIBW.js} +1 -1
- package/dist/chunk-Y6O3MIBW.js.map +1 -0
- package/dist/connect/index.d.ts +1 -1
- package/dist/connect/index.js +2 -2
- package/dist/connectors/adapters/index.d.ts +2 -2
- package/dist/connectors/adapters/index.js +2 -2
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +2 -2
- package/dist/consumer.d.ts +8 -0
- package/dist/consumer.js +12 -0
- package/dist/consumer.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +29 -11
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +2 -2
- package/dist/registry.d.ts +439 -92
- package/dist/registry.js +9 -6
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +7 -6
- package/dist/specs.d.ts +2 -2
- package/dist/specs.js +3 -1
- package/dist/tangle-catalog-runtime.d.ts +2 -2
- package/dist/tangle-catalog-runtime.js +7 -6
- package/dist/{tangle-id-CTU4kGId.d.ts → tangle-id-C6s2NT2r.d.ts} +7 -0
- package/docs/integration-execution-audit.md +1 -1
- package/package.json +23 -10
- package/dist/chunk-ATYHZXLL.js.map +0 -1
- package/dist/chunk-JU25UDN2.js.map +0 -1
- package/dist/chunk-UWRYFPJW.js.map +0 -1
- /package/dist/{chunk-SVQ4PHDZ.js.map → chunk-5ASL5XNX.js.map} +0 -0
- /package/dist/{chunk-P24T3MLM.js.map → chunk-DACSERTI.js.map} +0 -0
package/README.md
CHANGED
|
@@ -288,6 +288,46 @@ For a full product checklist, see
|
|
|
288
288
|
[External Product Integration](./docs/external-product-integration.md) and
|
|
289
289
|
[Platform Control Plane Adoption](./docs/platform-control-plane.md).
|
|
290
290
|
|
|
291
|
+
## Consuming a Hosted Hub
|
|
292
|
+
|
|
293
|
+
`createIntegrationRuntime` is for a product that *hosts* a hub — it owns the
|
|
294
|
+
stores, the vault, and OAuth. A product that instead *consumes* a hosted hub
|
|
295
|
+
(`id.tangle.tools`) from its backend talks to it over HTTP with
|
|
296
|
+
`createIntegrationHubClient`:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import { createIntegrationHubClient } from '@tangle-network/agent-integrations/consumer'
|
|
300
|
+
|
|
301
|
+
const hub = createIntegrationHubClient({
|
|
302
|
+
product: 'blueprint-agent',
|
|
303
|
+
auth: {
|
|
304
|
+
mode: 'service',
|
|
305
|
+
serviceToken: process.env.SERVICE_TOKEN!,
|
|
306
|
+
serviceName: 'blueprint-agent',
|
|
307
|
+
},
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// Is the user's GitHub connected? (resolve-manifest under the hood — the
|
|
311
|
+
// raw connection list is intentionally not service-token-reachable.)
|
|
312
|
+
const github = await hub.checkConnector({ userId, connectorId: 'github' })
|
|
313
|
+
|
|
314
|
+
// Mint a scoped, short-lived capability bundle for a sandbox process.
|
|
315
|
+
const { env } = await hub.mintCapabilityBundle({
|
|
316
|
+
userId,
|
|
317
|
+
subject: { type: 'sandbox', id: sandboxId },
|
|
318
|
+
manifestId: manifest.id,
|
|
319
|
+
})
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The client speaks the platform's `/v1/integrations/*` surface and supports
|
|
323
|
+
both auth modes the route layer accepts — a `svc_*` service token
|
|
324
|
+
(`mode: 'service'`, acting user in `X-Platform-User-Id`) or a per-user
|
|
325
|
+
`sk-tan-*` key (`mode: 'user-key'`). It exposes the four service-token-reachable
|
|
326
|
+
operations — `resolveManifest`, `createGrants` / `listGrants`,
|
|
327
|
+
`mintCapabilityBundle`, `runHealthchecks` — plus the `checkConnector`
|
|
328
|
+
convenience. Provider credentials never cross this client; bundles carry only
|
|
329
|
+
short-lived capability tokens.
|
|
330
|
+
|
|
291
331
|
## Product Hub Ownership
|
|
292
332
|
|
|
293
333
|
Use a hosted hub when multiple apps intentionally share identity, billing,
|
|
@@ -4,15 +4,16 @@ import {
|
|
|
4
4
|
buildTangleCatalogRuntimePackageManifest,
|
|
5
5
|
renderTangleCatalogRuntimePnpmAddCommand,
|
|
6
6
|
startTangleCatalogRuntimeNodeServer
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-M2RFFAMB.js";
|
|
8
|
+
import "../chunk-DACSERTI.js";
|
|
9
|
+
import "../chunk-5ASL5XNX.js";
|
|
9
10
|
import "../chunk-H4XYLS7T.js";
|
|
10
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-FDZIQVK7.js";
|
|
12
|
+
import "../chunk-VVC7U7W7.js";
|
|
11
13
|
import "../chunk-376UBTNB.js";
|
|
12
|
-
import "../chunk-
|
|
14
|
+
import "../chunk-JOILC44P.js";
|
|
13
15
|
import "../chunk-2TW2QKGZ.js";
|
|
14
|
-
import "../chunk-
|
|
15
|
-
import "../chunk-ATYHZXLL.js";
|
|
16
|
+
import "../chunk-Y6O3MIBW.js";
|
|
16
17
|
|
|
17
18
|
// src/bin/tangle-catalog-runtime.ts
|
|
18
19
|
var args = new Set(process.argv.slice(2));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/tangle-catalog-runtime.ts"],"sourcesContent":["#!/usr/bin/env node\nimport {\n buildTangleCatalogRuntimePackageManifest,\n renderTangleCatalogRuntimePnpmAddCommand,\n} from '../tangle-catalog.js'\nimport { auditTangleCatalogRuntimePackages } from '../tangle-catalog-runtime.js'\nimport { startTangleCatalogRuntimeNodeServer } from '../tangle-catalog-runtime-server.js'\n\nconst args = new Set(process.argv.slice(2))\nif (args.has('--print-package-json')) {\n console.log(JSON.stringify(buildTangleCatalogRuntimePackageManifest({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }), null, 2))\n process.exit(0)\n}\n\nif (args.has('--print-pnpm-add')) {\n console.log(renderTangleCatalogRuntimePnpmAddCommand({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }))\n process.exit(0)\n}\n\nif (args.has('--audit-packages')) {\n const connectorIds = process.env.TANGLE_CATALOG_AUDIT_CONNECTORS\n ?.split(',')\n .map((id) => id.trim())\n .filter(Boolean)\n console.log(JSON.stringify(await auditTangleCatalogRuntimePackages({ connectorIds }), null, 2))\n process.exit(0)\n}\n\nconst secret = process.env.TANGLE_CATALOG_RUNTIME_SECRET\nif (!secret || secret.length < 32) {\n console.error('TANGLE_CATALOG_RUNTIME_SECRET must be set to at least 32 characters.')\n process.exit(1)\n}\n\nconst authResolverUrl = process.env.TANGLE_CATALOG_AUTH_RESOLVER_URL\nconst authResolverSecret = process.env.TANGLE_CATALOG_AUTH_RESOLVER_SECRET\nif (Boolean(authResolverUrl) !== Boolean(authResolverSecret)) {\n console.error('TANGLE_CATALOG_AUTH_RESOLVER_URL and TANGLE_CATALOG_AUTH_RESOLVER_SECRET must be set together.')\n process.exit(1)\n}\n\nconst port = Number(process.env.PORT ?? process.env.TANGLE_CATALOG_RUNTIME_PORT ?? 4109)\nif (!Number.isInteger(port) || port < 1 || port > 65_535) {\n console.error('PORT must be an integer between 1 and 65535.')\n process.exit(1)\n}\n\nconst server = await startTangleCatalogRuntimeNodeServer({\n secret,\n host: process.env.HOST ?? process.env.TANGLE_CATALOG_RUNTIME_HOST ?? '0.0.0.0',\n port,\n authResolver: authResolverUrl && authResolverSecret\n ? {\n endpoint: authResolverUrl,\n secret: authResolverSecret,\n }\n : false,\n onLog: (event) => {\n const line = JSON.stringify({\n level: event.level,\n message: event.message,\n ...event.metadata,\n })\n if (event.level === 'error') console.error(line)\n else console.log(line)\n },\n})\n\nconsole.log(JSON.stringify({\n level: 'info',\n message: 'Tangle catalog runtime listening.',\n url: server.url,\n}))\n\nfor (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, async () => {\n await server.close()\n process.exit(0)\n })\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/bin/tangle-catalog-runtime.ts"],"sourcesContent":["#!/usr/bin/env node\nimport {\n buildTangleCatalogRuntimePackageManifest,\n renderTangleCatalogRuntimePnpmAddCommand,\n} from '../tangle-catalog.js'\nimport { auditTangleCatalogRuntimePackages } from '../tangle-catalog-runtime.js'\nimport { startTangleCatalogRuntimeNodeServer } from '../tangle-catalog-runtime-server.js'\n\nconst args = new Set(process.argv.slice(2))\nif (args.has('--print-package-json')) {\n console.log(JSON.stringify(buildTangleCatalogRuntimePackageManifest({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }), null, 2))\n process.exit(0)\n}\n\nif (args.has('--print-pnpm-add')) {\n console.log(renderTangleCatalogRuntimePnpmAddCommand({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }))\n process.exit(0)\n}\n\nif (args.has('--audit-packages')) {\n const connectorIds = process.env.TANGLE_CATALOG_AUDIT_CONNECTORS\n ?.split(',')\n .map((id) => id.trim())\n .filter(Boolean)\n console.log(JSON.stringify(await auditTangleCatalogRuntimePackages({ connectorIds }), null, 2))\n process.exit(0)\n}\n\nconst secret = process.env.TANGLE_CATALOG_RUNTIME_SECRET\nif (!secret || secret.length < 32) {\n console.error('TANGLE_CATALOG_RUNTIME_SECRET must be set to at least 32 characters.')\n process.exit(1)\n}\n\nconst authResolverUrl = process.env.TANGLE_CATALOG_AUTH_RESOLVER_URL\nconst authResolverSecret = process.env.TANGLE_CATALOG_AUTH_RESOLVER_SECRET\nif (Boolean(authResolverUrl) !== Boolean(authResolverSecret)) {\n console.error('TANGLE_CATALOG_AUTH_RESOLVER_URL and TANGLE_CATALOG_AUTH_RESOLVER_SECRET must be set together.')\n process.exit(1)\n}\n\nconst port = Number(process.env.PORT ?? process.env.TANGLE_CATALOG_RUNTIME_PORT ?? 4109)\nif (!Number.isInteger(port) || port < 1 || port > 65_535) {\n console.error('PORT must be an integer between 1 and 65535.')\n process.exit(1)\n}\n\nconst server = await startTangleCatalogRuntimeNodeServer({\n secret,\n host: process.env.HOST ?? process.env.TANGLE_CATALOG_RUNTIME_HOST ?? '0.0.0.0',\n port,\n authResolver: authResolverUrl && authResolverSecret\n ? {\n endpoint: authResolverUrl,\n secret: authResolverSecret,\n }\n : false,\n onLog: (event) => {\n const line = JSON.stringify({\n level: event.level,\n message: event.message,\n ...event.metadata,\n })\n if (event.level === 'error') console.error(line)\n else console.log(line)\n },\n})\n\nconsole.log(JSON.stringify({\n level: 'info',\n message: 'Tangle catalog runtime listening.',\n url: server.url,\n}))\n\nfor (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, async () => {\n await server.close()\n process.exit(0)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAQA,IAAM,OAAO,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC1C,IAAI,KAAK,IAAI,sBAAsB,GAAG;AACpC,UAAQ,IAAI,KAAK,UAAU,yCAAyC;AAAA,IAClE,0BAA0B,QAAQ,IAAI;AAAA,EACxC,CAAC,GAAG,MAAM,CAAC,CAAC;AACZ,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAI,KAAK,IAAI,kBAAkB,GAAG;AAChC,UAAQ,IAAI,yCAAyC;AAAA,IACnD,0BAA0B,QAAQ,IAAI;AAAA,EACxC,CAAC,CAAC;AACF,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAI,KAAK,IAAI,kBAAkB,GAAG;AAChC,QAAM,eAAe,QAAQ,IAAI,iCAC7B,MAAM,GAAG,EACV,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,EACrB,OAAO,OAAO;AACjB,UAAQ,IAAI,KAAK,UAAU,MAAM,kCAAkC,EAAE,aAAa,CAAC,GAAG,MAAM,CAAC,CAAC;AAC9F,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,SAAS,QAAQ,IAAI;AAC3B,IAAI,CAAC,UAAU,OAAO,SAAS,IAAI;AACjC,UAAQ,MAAM,sEAAsE;AACpF,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,kBAAkB,QAAQ,IAAI;AACpC,IAAM,qBAAqB,QAAQ,IAAI;AACvC,IAAI,QAAQ,eAAe,MAAM,QAAQ,kBAAkB,GAAG;AAC5D,UAAQ,MAAM,gGAAgG;AAC9G,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,QAAQ,IAAI,+BAA+B,IAAI;AACvF,IAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,OAAQ;AACxD,UAAQ,MAAM,8CAA8C;AAC5D,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,SAAS,MAAM,oCAAoC;AAAA,EACvD;AAAA,EACA,MAAM,QAAQ,IAAI,QAAQ,QAAQ,IAAI,+BAA+B;AAAA,EACrE;AAAA,EACA,cAAc,mBAAmB,qBAC7B;AAAA,IACE,UAAU;AAAA,IACV,QAAQ;AAAA,EACV,IACA;AAAA,EACJ,OAAO,CAAC,UAAU;AAChB,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,GAAG,MAAM;AAAA,IACX,CAAC;AACD,QAAI,MAAM,UAAU,QAAS,SAAQ,MAAM,IAAI;AAAA,QAC1C,SAAQ,IAAI,IAAI;AAAA,EACvB;AACF,CAAC;AAED,QAAQ,IAAI,KAAK,UAAU;AAAA,EACzB,OAAO;AAAA,EACP,SAAS;AAAA,EACT,KAAK,OAAO;AACd,CAAC,CAAC;AAEF,WAAW,UAAU,CAAC,UAAU,SAAS,GAAY;AACnD,UAAQ,KAAK,QAAQ,YAAY;AAC/B,UAAM,OAAO,MAAM;AACnB,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":[]}
|
package/dist/catalog.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { I as IntegrationCatalogView, a as IntegrationToolDefinition, b as IntegrationToolSearchFilters, c as IntegrationToolSearchResult, M as McpToolDefinition, d as buildIntegrationCatalogView, e as buildIntegrationToolCatalog, i as integrationToolName, p as parseIntegrationToolName, s as searchIntegrationTools, t as toMcpTools } from './registry.js';
|
|
2
|
-
import './tangle-id-
|
|
1
|
+
export { I as IntegrationCatalogView, a as IntegrationToolDefinition, b as IntegrationToolSearchFilters, c as IntegrationToolSearchResult, M as McpToolDefinition, d as buildIntegrationCatalogView, e as buildIntegrationToolCatalog, f as describeIntegrationTool, g as flattenIntegrationToolDefinition, i as integrationToolName, p as parseIntegrationToolName, s as searchIntegrationTools, t as toMcpTools } from './registry.js';
|
|
2
|
+
import './tangle-id-C6s2NT2r.js';
|
|
3
3
|
import './errors-Bg3_rxnQ.js';
|
|
4
4
|
import './connect/index.js';
|
|
5
5
|
import './middleware/index.js';
|
package/dist/catalog.js
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildIntegrationCatalogView,
|
|
3
3
|
buildIntegrationToolCatalog,
|
|
4
|
+
describeIntegrationTool,
|
|
5
|
+
flattenIntegrationToolDefinition,
|
|
4
6
|
integrationToolName,
|
|
5
7
|
parseIntegrationToolName,
|
|
6
8
|
searchIntegrationTools,
|
|
7
9
|
toMcpTools
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-M2RFFAMB.js";
|
|
11
|
+
import "./chunk-DACSERTI.js";
|
|
12
|
+
import "./chunk-5ASL5XNX.js";
|
|
10
13
|
import "./chunk-H4XYLS7T.js";
|
|
11
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-FDZIQVK7.js";
|
|
15
|
+
import "./chunk-VVC7U7W7.js";
|
|
12
16
|
import "./chunk-376UBTNB.js";
|
|
13
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-JOILC44P.js";
|
|
14
18
|
import "./chunk-2TW2QKGZ.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-ATYHZXLL.js";
|
|
19
|
+
import "./chunk-Y6O3MIBW.js";
|
|
17
20
|
export {
|
|
18
21
|
buildIntegrationCatalogView,
|
|
19
22
|
buildIntegrationToolCatalog,
|
|
23
|
+
describeIntegrationTool,
|
|
24
|
+
flattenIntegrationToolDefinition,
|
|
20
25
|
integrationToolName,
|
|
21
26
|
parseIntegrationToolName,
|
|
22
27
|
searchIntegrationTools,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TangleIdentityUnreachableError,
|
|
3
3
|
createTangleIdentityClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Y6O3MIBW.js";
|
|
5
5
|
|
|
6
6
|
// src/middleware/index.ts
|
|
7
7
|
async function requireTangleAuth(request, opts = {}) {
|
|
@@ -126,4 +126,4 @@ export {
|
|
|
126
126
|
honoTangleAuthMiddleware,
|
|
127
127
|
expressTangleAuthMiddleware
|
|
128
128
|
};
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
129
|
+
//# sourceMappingURL=chunk-5ASL5XNX.js.map
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_TANGLE_PLATFORM_URL,
|
|
3
3
|
TangleIdentityUnreachableError,
|
|
4
4
|
createTangleIdentityClient
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Y6O3MIBW.js";
|
|
6
6
|
|
|
7
7
|
// src/connect/index.ts
|
|
8
8
|
function startConnectFlow(opts, input) {
|
|
@@ -103,4 +103,4 @@ export {
|
|
|
103
103
|
revokeConnectFlow,
|
|
104
104
|
InMemoryConnectStateStore
|
|
105
105
|
};
|
|
106
|
-
//# sourceMappingURL=chunk-
|
|
106
|
+
//# sourceMappingURL=chunk-DACSERTI.js.map
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_TANGLE_PLATFORM_URL
|
|
3
|
+
} from "./chunk-Y6O3MIBW.js";
|
|
4
|
+
|
|
5
|
+
// src/consumer.ts
|
|
6
|
+
var PLATFORM_USER_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
7
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
8
|
+
var DEFAULT_MAX_ATTEMPTS = 2;
|
|
9
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
10
|
+
var IntegrationHubRequestError = class extends Error {
|
|
11
|
+
name = "IntegrationHubRequestError";
|
|
12
|
+
/** HTTP status, or 0 for a network-level failure. */
|
|
13
|
+
status;
|
|
14
|
+
/** Platform error code (`VALIDATION_ERROR`, `scope_missing`, …) or
|
|
15
|
+
* `network_error` / `http_error` when no structured code was returned. */
|
|
16
|
+
code;
|
|
17
|
+
/** `METHOD /path` the request targeted. */
|
|
18
|
+
endpoint;
|
|
19
|
+
/** True when the failure class is transient and a retry could succeed. */
|
|
20
|
+
retryable;
|
|
21
|
+
constructor(input) {
|
|
22
|
+
super(input.message);
|
|
23
|
+
this.status = input.status;
|
|
24
|
+
this.code = input.code;
|
|
25
|
+
this.endpoint = input.endpoint;
|
|
26
|
+
this.retryable = input.retryable;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var IntegrationHubClient = class {
|
|
30
|
+
endpoint;
|
|
31
|
+
product;
|
|
32
|
+
auth;
|
|
33
|
+
fetchImpl;
|
|
34
|
+
timeoutMs;
|
|
35
|
+
maxAttempts;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
if (!options.product) {
|
|
38
|
+
throw new Error("IntegrationHubClient: product is required");
|
|
39
|
+
}
|
|
40
|
+
if (options.auth.mode === "service" && !options.auth.serviceToken) {
|
|
41
|
+
throw new Error("IntegrationHubClient: service auth requires a serviceToken");
|
|
42
|
+
}
|
|
43
|
+
if (options.auth.mode === "service" && !options.auth.serviceName) {
|
|
44
|
+
throw new Error("IntegrationHubClient: service auth requires a serviceName");
|
|
45
|
+
}
|
|
46
|
+
if (options.auth.mode === "user-key" && !options.auth.apiKey) {
|
|
47
|
+
throw new Error("IntegrationHubClient: user-key auth requires an apiKey");
|
|
48
|
+
}
|
|
49
|
+
this.endpoint = (options.endpoint ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
|
|
50
|
+
this.product = options.product;
|
|
51
|
+
this.auth = options.auth;
|
|
52
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
53
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
54
|
+
this.maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a manifest against a user's connections. The returned
|
|
58
|
+
* `ready` / `missing` split is the canonical way to ask "does this user
|
|
59
|
+
* have the connections this work needs" — the raw connection list is not
|
|
60
|
+
* reachable by a service token by design.
|
|
61
|
+
*/
|
|
62
|
+
async resolveManifest(input) {
|
|
63
|
+
return this.request("POST", "/resolve-manifest", input.userId, {
|
|
64
|
+
product: input.product ?? this.product,
|
|
65
|
+
manifest: input.manifest,
|
|
66
|
+
ownerUserId: input.userId
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Convenience over {@link resolveManifest} — probe a single connector and
|
|
71
|
+
* get back a boolean plus the satisfying connection. The Surface-A quest
|
|
72
|
+
* primitive ("is the user's GitHub linked?").
|
|
73
|
+
*/
|
|
74
|
+
async checkConnector(input) {
|
|
75
|
+
const requirementId = input.connectorId;
|
|
76
|
+
const resolution = await this.resolveManifest({
|
|
77
|
+
userId: input.userId,
|
|
78
|
+
manifest: {
|
|
79
|
+
id: `connectivity-check:${input.connectorId}`,
|
|
80
|
+
requirements: [
|
|
81
|
+
{
|
|
82
|
+
id: requirementId,
|
|
83
|
+
connectorId: input.connectorId,
|
|
84
|
+
reason: `Connectivity check for ${input.connectorId}`,
|
|
85
|
+
mode: input.mode ?? "read",
|
|
86
|
+
...input.requiredScopes ? { requiredScopes: input.requiredScopes } : {},
|
|
87
|
+
...input.requiredActions ? { requiredActions: input.requiredActions } : {}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const requirement = resolution.ready.find((r) => r.requirement.id === requirementId) ?? resolution.missing.find((r) => r.requirement.id === requirementId) ?? resolution.optionalMissing.find((r) => r.requirement.id === requirementId);
|
|
93
|
+
if (!requirement) {
|
|
94
|
+
throw new IntegrationHubRequestError({
|
|
95
|
+
status: 0,
|
|
96
|
+
code: "malformed_response",
|
|
97
|
+
message: `resolve-manifest returned no resolution for requirement ${requirementId}`,
|
|
98
|
+
endpoint: "POST /resolve-manifest",
|
|
99
|
+
retryable: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
connected: requirement.status === "ready",
|
|
104
|
+
...requirement.connection ? { connection: requirement.connection } : {},
|
|
105
|
+
resolution: requirement
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Create grants for every satisfiable requirement of a manifest. The
|
|
109
|
+
* platform rejects the call if any non-optional requirement is missing a
|
|
110
|
+
* connection. */
|
|
111
|
+
async createGrants(input) {
|
|
112
|
+
const data = await this.request(
|
|
113
|
+
"POST",
|
|
114
|
+
"/grants",
|
|
115
|
+
input.userId,
|
|
116
|
+
{
|
|
117
|
+
grantee: input.grantee,
|
|
118
|
+
manifest: input.manifest,
|
|
119
|
+
ownerUserId: input.userId,
|
|
120
|
+
...input.metadata ? { metadata: input.metadata } : {}
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
return data.grants;
|
|
124
|
+
}
|
|
125
|
+
/** List the acting user's grants, optionally filtered to one grantee. */
|
|
126
|
+
async listGrants(input) {
|
|
127
|
+
const query = input.grantee !== void 0 ? `?granteeType=${encodeURIComponent(input.grantee.type)}&granteeId=${encodeURIComponent(input.grantee.id)}` : "";
|
|
128
|
+
const data = await this.request(
|
|
129
|
+
"GET",
|
|
130
|
+
`/grants${query}`,
|
|
131
|
+
input.userId
|
|
132
|
+
);
|
|
133
|
+
return data.grants;
|
|
134
|
+
}
|
|
135
|
+
/** Mint a short-lived capability bundle for a sandbox / agent process.
|
|
136
|
+
* Provider credentials never leave the platform — the bundle carries only
|
|
137
|
+
* scoped, expiring capability tokens. */
|
|
138
|
+
async mintCapabilityBundle(input) {
|
|
139
|
+
if (!input.manifestId && !(input.grantIds && input.grantIds.length > 0)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
"IntegrationHubClient.mintCapabilityBundle: manifestId or a non-empty grantIds is required"
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return this.request("POST", "/capabilities/bundle", input.userId, {
|
|
145
|
+
subject: input.subject,
|
|
146
|
+
...input.manifestId ? { manifestId: input.manifestId } : {},
|
|
147
|
+
...input.grantIds ? { grantIds: input.grantIds } : {},
|
|
148
|
+
...input.grantee ? { grantee: input.grantee } : {},
|
|
149
|
+
...input.ttlMs !== void 0 ? { ttlMs: input.ttlMs } : {}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Run live healthchecks across all of the acting user's connections. */
|
|
153
|
+
async runHealthchecks(input) {
|
|
154
|
+
const data = await this.request(
|
|
155
|
+
"POST",
|
|
156
|
+
"/healthchecks/run",
|
|
157
|
+
input.userId,
|
|
158
|
+
{ ownerUserId: input.userId }
|
|
159
|
+
);
|
|
160
|
+
return data.healthchecks;
|
|
161
|
+
}
|
|
162
|
+
buildHeaders(userId, hasBody) {
|
|
163
|
+
const headers = new Headers({ accept: "application/json" });
|
|
164
|
+
if (hasBody) headers.set("content-type", "application/json");
|
|
165
|
+
if (this.auth.mode === "service") {
|
|
166
|
+
headers.set("authorization", `Bearer ${this.auth.serviceToken}`);
|
|
167
|
+
headers.set("x-service-name", this.auth.serviceName);
|
|
168
|
+
headers.set("x-platform-user-id", userId);
|
|
169
|
+
} else {
|
|
170
|
+
headers.set("authorization", `Bearer ${this.auth.apiKey}`);
|
|
171
|
+
}
|
|
172
|
+
return headers;
|
|
173
|
+
}
|
|
174
|
+
async request(method, path, userId, body) {
|
|
175
|
+
if (!PLATFORM_USER_ID_PATTERN.test(userId)) {
|
|
176
|
+
throw new IntegrationHubRequestError({
|
|
177
|
+
status: 0,
|
|
178
|
+
code: "invalid_user_id",
|
|
179
|
+
message: `userId ${JSON.stringify(userId)} is not a valid platform user id`,
|
|
180
|
+
endpoint: `${method} ${path}`,
|
|
181
|
+
retryable: false
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const url = `${this.endpoint}/v1/integrations${path}`;
|
|
185
|
+
const endpointLabel = `${method} /v1/integrations${path}`;
|
|
186
|
+
const headers = this.buildHeaders(userId, body !== void 0);
|
|
187
|
+
const init = {
|
|
188
|
+
method,
|
|
189
|
+
headers,
|
|
190
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
191
|
+
};
|
|
192
|
+
let lastError;
|
|
193
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
194
|
+
let response;
|
|
195
|
+
try {
|
|
196
|
+
response = await this.fetchImpl(url, {
|
|
197
|
+
...init,
|
|
198
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
199
|
+
});
|
|
200
|
+
} catch (error) {
|
|
201
|
+
lastError = new IntegrationHubRequestError({
|
|
202
|
+
status: 0,
|
|
203
|
+
code: "network_error",
|
|
204
|
+
message: `${endpointLabel} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
205
|
+
endpoint: endpointLabel,
|
|
206
|
+
retryable: true
|
|
207
|
+
});
|
|
208
|
+
if (attempt < this.maxAttempts) {
|
|
209
|
+
await delay(attempt);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
throw lastError;
|
|
213
|
+
}
|
|
214
|
+
const payload = await readJson(response);
|
|
215
|
+
if (response.ok && isSuccessEnvelope(payload)) {
|
|
216
|
+
return payload.data;
|
|
217
|
+
}
|
|
218
|
+
const retryable = RETRYABLE_STATUSES.has(response.status);
|
|
219
|
+
lastError = new IntegrationHubRequestError({
|
|
220
|
+
status: response.status,
|
|
221
|
+
code: errorCode(payload, response.status),
|
|
222
|
+
message: errorMessage(payload, endpointLabel, response.status),
|
|
223
|
+
endpoint: endpointLabel,
|
|
224
|
+
retryable
|
|
225
|
+
});
|
|
226
|
+
if (retryable && attempt < this.maxAttempts) {
|
|
227
|
+
await delay(attempt);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
throw lastError;
|
|
231
|
+
}
|
|
232
|
+
throw lastError ?? new IntegrationHubRequestError({
|
|
233
|
+
status: 0,
|
|
234
|
+
code: "unknown",
|
|
235
|
+
message: `${endpointLabel} exhausted retries without a result`,
|
|
236
|
+
endpoint: endpointLabel,
|
|
237
|
+
retryable: false
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
function createIntegrationHubClient(options) {
|
|
242
|
+
return new IntegrationHubClient(options);
|
|
243
|
+
}
|
|
244
|
+
function isSuccessEnvelope(payload) {
|
|
245
|
+
return typeof payload === "object" && payload !== null && payload.success === true && "data" in payload;
|
|
246
|
+
}
|
|
247
|
+
async function readJson(response) {
|
|
248
|
+
const text = await response.text().catch(() => "");
|
|
249
|
+
if (!text) return void 0;
|
|
250
|
+
try {
|
|
251
|
+
return JSON.parse(text);
|
|
252
|
+
} catch {
|
|
253
|
+
return { __text: text };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function errorCode(payload, status) {
|
|
257
|
+
if (typeof payload === "object" && payload !== null) {
|
|
258
|
+
const error = payload.error;
|
|
259
|
+
if (error && typeof error.code === "string") return error.code;
|
|
260
|
+
}
|
|
261
|
+
return status === 0 ? "network_error" : "http_error";
|
|
262
|
+
}
|
|
263
|
+
function errorMessage(payload, endpointLabel, status) {
|
|
264
|
+
if (typeof payload === "object" && payload !== null) {
|
|
265
|
+
const record = payload;
|
|
266
|
+
if (record.error && typeof record.error.message === "string") {
|
|
267
|
+
return `${endpointLabel} \u2192 ${status}: ${record.error.message}`;
|
|
268
|
+
}
|
|
269
|
+
if (typeof record.__text === "string" && record.__text.length > 0) {
|
|
270
|
+
return `${endpointLabel} \u2192 ${status}: ${record.__text.slice(0, 300)}`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return `${endpointLabel} \u2192 ${status}`;
|
|
274
|
+
}
|
|
275
|
+
function delay(attempt) {
|
|
276
|
+
return new Promise((resolve) => setTimeout(resolve, attempt * 200));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export {
|
|
280
|
+
IntegrationHubRequestError,
|
|
281
|
+
IntegrationHubClient,
|
|
282
|
+
createIntegrationHubClient
|
|
283
|
+
};
|
|
284
|
+
//# sourceMappingURL=chunk-FDZIQVK7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/consumer.ts"],"sourcesContent":["/**\n * @stable Integration Hub consumer client.\n *\n * The third client-shaped surface a product needs, alongside the two that\n * already ship:\n *\n * - `createTangleIntegrationsClient` (`client.ts`) — the *invoke* client.\n * Capability-token auth, runs INSIDE a sandbox / generated app, single\n * endpoint `/v1/integrations/invoke`.\n * - `startConnectFlow` / `finishConnectFlow` (`connect/index.ts`) — the\n * *user-consent* flow, mirrors `/cross-site/*`.\n * - **this** — the S2S *management* client. A product BACKEND (blueprint-\n * agent, sandbox, gtm-agent, tax-agent, legal-agent, evals, …) drives the\n * `/v1/integrations/{resolve-manifest,grants,capabilities/bundle,\n * healthchecks/run}` management surface on `id.tangle.tools` on behalf of\n * an identified user.\n *\n * Every consumer needs the identical client — the wire protocol, the\n * `{ success, data }` envelope, the auth header shape are all platform-owned.\n * Re-implementing a bespoke fetch loop per product forks the protocol and the\n * copies drift. This module is that shared implementation. It mirrors the\n * `connect/index.ts` design rule one-for-one: DO NOT invent the wire protocol\n * — speak exactly what `products/platform/api/src/routes/integrations.ts`\n * serves.\n *\n * Two auth modes — the route layer (`authMiddleware`) accepts either:\n *\n * - `service` — a `svc_*` service token + `X-Service-Name`. The acting\n * user travels in `X-Platform-User-Id`. The platform honors that header\n * only for service tokens whose `SERVICE_SCOPES` set contains\n * `impersonate:user`; a token without it is rejected (403). Reaches the\n * four management paths the platform allowlists for service tokens.\n * - `user-key` — a per-user `sk-tan-*` API key (minted via the connect\n * flow). The key identifies the user; no impersonation header. Reaches\n * every route the user themselves can.\n *\n * The capability-token `invoke` endpoint is intentionally NOT exposed here —\n * that is `createTangleIntegrationsClient`'s job and uses a different auth.\n */\n\nimport type { IntegrationActor, IntegrationConnection } from './index.js'\nimport type {\n IntegrationGrant,\n IntegrationManifest,\n IntegrationManifestResolution,\n IntegrationRequirementMode,\n IntegrationRequirementResolution,\n IntegrationSandboxBundle,\n} from './runtime.js'\nimport type { IntegrationHealthcheckResult } from './healthcheck.js'\nimport { DEFAULT_TANGLE_PLATFORM_URL } from './connectors/adapters/tangle-id.js'\n\n/** Matches the platform's `PLATFORM_USER_ID_PATTERN` (`auth.ts`). A user id\n * that fails this is rejected client-side before the request leaves. */\nconst PLATFORM_USER_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/\n\nconst DEFAULT_TIMEOUT_MS = 10_000\nconst DEFAULT_MAX_ATTEMPTS = 2\n/** HTTP statuses worth a retry — transient platform/edge failures only.\n * 4xx is deterministic and never retried. */\nconst RETRYABLE_STATUSES = new Set([502, 503, 504])\n\nexport type IntegrationHubAuth =\n | {\n mode: 'service'\n /** The `svc_*` token issued to this product. */\n serviceToken: string\n /** Registered service name — sent as `X-Service-Name`. Required\n * because one token may be shared across services, in which case the\n * platform demands the header to disambiguate. */\n serviceName: string\n }\n | {\n mode: 'user-key'\n /** A per-user `sk-tan-*` key bound to the acting user. */\n apiKey: string\n }\n\nexport interface IntegrationHubClientOptions {\n /** The product / consumer identifier (e.g. `blueprint-agent`). Sent as the\n * `product` field of resolve-manifest calls; recorded platform-side. */\n product: string\n /** Service-token or per-user-key auth. */\n auth: IntegrationHubAuth\n /** Platform base URL. Defaults to `https://id.tangle.tools`. */\n endpoint?: string\n /** Injected for tests. Defaults to the global `fetch`. */\n fetchImpl?: typeof fetch\n /** Per-request timeout in ms. Default 10_000. */\n timeoutMs?: number\n /** Max attempts on transient (network / 502 / 503 / 504) failures.\n * Default 2 — i.e. one retry. */\n maxAttempts?: number\n}\n\n/** Thrown for every non-2xx response and every transport failure. Carries the\n * HTTP status and the platform error code so callers can branch precisely\n * (`403` + `impersonate` → the service token lacks the scope; `409` /\n * `missing_connection` → prompt the user to connect). */\nexport class IntegrationHubRequestError extends Error {\n readonly name = 'IntegrationHubRequestError'\n /** HTTP status, or 0 for a network-level failure. */\n readonly status: number\n /** Platform error code (`VALIDATION_ERROR`, `scope_missing`, …) or\n * `network_error` / `http_error` when no structured code was returned. */\n readonly code: string\n /** `METHOD /path` the request targeted. */\n readonly endpoint: string\n /** True when the failure class is transient and a retry could succeed. */\n readonly retryable: boolean\n\n constructor(input: {\n status: number\n code: string\n message: string\n endpoint: string\n retryable: boolean\n }) {\n super(input.message)\n this.status = input.status\n this.code = input.code\n this.endpoint = input.endpoint\n this.retryable = input.retryable\n }\n}\n\nexport interface ResolveManifestInput {\n /** The acting user — the connection owner. */\n userId: string\n manifest: IntegrationManifest\n /** Overrides the client-level `product` for this call. */\n product?: string\n}\n\nexport interface CreateGrantsInput {\n /** The acting user — the connection owner. */\n userId: string\n /** Who the grant is FOR (the sandbox / agent / app that will invoke). */\n grantee: IntegrationActor\n manifest: IntegrationManifest\n metadata?: Record<string, unknown>\n}\n\nexport interface ListGrantsInput {\n /** The acting user — the connection owner. */\n userId: string\n /** Optional grantee filter; both fields travel together as query params. */\n grantee?: IntegrationActor\n}\n\nexport interface MintCapabilityBundleInput {\n /** The acting user — must own every connection behind the grants. */\n userId: string\n /** Who the capability bundle is issued TO (the sandbox / agent process). */\n subject: IntegrationActor\n /** Mint from every grant of a manifest … */\n manifestId?: string\n /** … or from an explicit grant id list. Exactly one of the two is required. */\n grantIds?: string[]\n grantee?: IntegrationActor\n /** Bundle TTL in ms. Platform clamps to [1s, 60m]; default 15m. */\n ttlMs?: number\n}\n\nexport interface CapabilityBundleResult {\n bundle: IntegrationSandboxBundle\n /** Bridge environment variables to inject into the sandbox process —\n * `buildIntegrationBridgeEnvironment(bundle)`, computed platform-side. */\n env: Record<string, string>\n}\n\nexport interface CheckConnectorInput {\n /** The acting user. */\n userId: string\n /** Connector to probe — `github`, `google-calendar`, `tangle-id`, … */\n connectorId: string\n /** Defaults to `read`. */\n mode?: IntegrationRequirementMode\n requiredScopes?: string[]\n requiredActions?: string[]\n}\n\nexport interface CheckConnectorResult {\n /** True when the user has an active connection satisfying the requirement. */\n connected: boolean\n /** The satisfying connection, present iff `connected`. */\n connection?: IntegrationConnection\n /** The full requirement resolution — status, missing scopes/actions, message. */\n resolution: IntegrationRequirementResolution\n}\n\n/**\n * S2S management client for the `id.tangle.tools` integration hub. One per\n * product; methods are stateless and safe to call concurrently.\n */\nexport class IntegrationHubClient {\n private readonly endpoint: string\n private readonly product: string\n private readonly auth: IntegrationHubAuth\n private readonly fetchImpl: typeof fetch\n private readonly timeoutMs: number\n private readonly maxAttempts: number\n\n constructor(options: IntegrationHubClientOptions) {\n if (!options.product) {\n throw new Error('IntegrationHubClient: product is required')\n }\n if (options.auth.mode === 'service' && !options.auth.serviceToken) {\n throw new Error('IntegrationHubClient: service auth requires a serviceToken')\n }\n if (options.auth.mode === 'service' && !options.auth.serviceName) {\n throw new Error('IntegrationHubClient: service auth requires a serviceName')\n }\n if (options.auth.mode === 'user-key' && !options.auth.apiKey) {\n throw new Error('IntegrationHubClient: user-key auth requires an apiKey')\n }\n this.endpoint = (options.endpoint ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\\/+$/, '')\n this.product = options.product\n this.auth = options.auth\n this.fetchImpl = options.fetchImpl ?? fetch\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)\n }\n\n /**\n * Resolve a manifest against a user's connections. The returned\n * `ready` / `missing` split is the canonical way to ask \"does this user\n * have the connections this work needs\" — the raw connection list is not\n * reachable by a service token by design.\n */\n async resolveManifest(input: ResolveManifestInput): Promise<IntegrationManifestResolution> {\n return this.request<IntegrationManifestResolution>('POST', '/resolve-manifest', input.userId, {\n product: input.product ?? this.product,\n manifest: input.manifest,\n ownerUserId: input.userId,\n })\n }\n\n /**\n * Convenience over {@link resolveManifest} — probe a single connector and\n * get back a boolean plus the satisfying connection. The Surface-A quest\n * primitive (\"is the user's GitHub linked?\").\n */\n async checkConnector(input: CheckConnectorInput): Promise<CheckConnectorResult> {\n const requirementId = input.connectorId\n const resolution = await this.resolveManifest({\n userId: input.userId,\n manifest: {\n id: `connectivity-check:${input.connectorId}`,\n requirements: [\n {\n id: requirementId,\n connectorId: input.connectorId,\n reason: `Connectivity check for ${input.connectorId}`,\n mode: input.mode ?? 'read',\n ...(input.requiredScopes ? { requiredScopes: input.requiredScopes } : {}),\n ...(input.requiredActions ? { requiredActions: input.requiredActions } : {}),\n },\n ],\n },\n })\n const requirement =\n resolution.ready.find((r) => r.requirement.id === requirementId) ??\n resolution.missing.find((r) => r.requirement.id === requirementId) ??\n resolution.optionalMissing.find((r) => r.requirement.id === requirementId)\n if (!requirement) {\n throw new IntegrationHubRequestError({\n status: 0,\n code: 'malformed_response',\n message: `resolve-manifest returned no resolution for requirement ${requirementId}`,\n endpoint: 'POST /resolve-manifest',\n retryable: false,\n })\n }\n return {\n connected: requirement.status === 'ready',\n ...(requirement.connection ? { connection: requirement.connection } : {}),\n resolution: requirement,\n }\n }\n\n /** Create grants for every satisfiable requirement of a manifest. The\n * platform rejects the call if any non-optional requirement is missing a\n * connection. */\n async createGrants(input: CreateGrantsInput): Promise<IntegrationGrant[]> {\n const data = await this.request<{ grants: IntegrationGrant[] }>(\n 'POST',\n '/grants',\n input.userId,\n {\n grantee: input.grantee,\n manifest: input.manifest,\n ownerUserId: input.userId,\n ...(input.metadata ? { metadata: input.metadata } : {}),\n },\n )\n return data.grants\n }\n\n /** List the acting user's grants, optionally filtered to one grantee. */\n async listGrants(input: ListGrantsInput): Promise<IntegrationGrant[]> {\n const query =\n input.grantee !== undefined\n ? `?granteeType=${encodeURIComponent(input.grantee.type)}&granteeId=${encodeURIComponent(input.grantee.id)}`\n : ''\n const data = await this.request<{ grants: IntegrationGrant[] }>(\n 'GET',\n `/grants${query}`,\n input.userId,\n )\n return data.grants\n }\n\n /** Mint a short-lived capability bundle for a sandbox / agent process.\n * Provider credentials never leave the platform — the bundle carries only\n * scoped, expiring capability tokens. */\n async mintCapabilityBundle(input: MintCapabilityBundleInput): Promise<CapabilityBundleResult> {\n if (!input.manifestId && !(input.grantIds && input.grantIds.length > 0)) {\n throw new Error(\n 'IntegrationHubClient.mintCapabilityBundle: manifestId or a non-empty grantIds is required',\n )\n }\n return this.request<CapabilityBundleResult>('POST', '/capabilities/bundle', input.userId, {\n subject: input.subject,\n ...(input.manifestId ? { manifestId: input.manifestId } : {}),\n ...(input.grantIds ? { grantIds: input.grantIds } : {}),\n ...(input.grantee ? { grantee: input.grantee } : {}),\n ...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),\n })\n }\n\n /** Run live healthchecks across all of the acting user's connections. */\n async runHealthchecks(input: { userId: string }): Promise<IntegrationHealthcheckResult[]> {\n const data = await this.request<{ healthchecks: IntegrationHealthcheckResult[] }>(\n 'POST',\n '/healthchecks/run',\n input.userId,\n { ownerUserId: input.userId },\n )\n return data.healthchecks\n }\n\n private buildHeaders(userId: string, hasBody: boolean): Headers {\n const headers = new Headers({ accept: 'application/json' })\n if (hasBody) headers.set('content-type', 'application/json')\n if (this.auth.mode === 'service') {\n headers.set('authorization', `Bearer ${this.auth.serviceToken}`)\n headers.set('x-service-name', this.auth.serviceName)\n headers.set('x-platform-user-id', userId)\n } else {\n headers.set('authorization', `Bearer ${this.auth.apiKey}`)\n }\n return headers\n }\n\n private async request<T>(\n method: 'GET' | 'POST',\n path: string,\n userId: string,\n body?: Record<string, unknown>,\n ): Promise<T> {\n if (!PLATFORM_USER_ID_PATTERN.test(userId)) {\n throw new IntegrationHubRequestError({\n status: 0,\n code: 'invalid_user_id',\n message: `userId ${JSON.stringify(userId)} is not a valid platform user id`,\n endpoint: `${method} ${path}`,\n retryable: false,\n })\n }\n const url = `${this.endpoint}/v1/integrations${path}`\n const endpointLabel = `${method} /v1/integrations${path}`\n const headers = this.buildHeaders(userId, body !== undefined)\n const init: RequestInit = {\n method,\n headers,\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n }\n\n let lastError: IntegrationHubRequestError | undefined\n for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n let response: Response\n try {\n response = await this.fetchImpl(url, {\n ...init,\n signal: AbortSignal.timeout(this.timeoutMs),\n })\n } catch (error) {\n lastError = new IntegrationHubRequestError({\n status: 0,\n code: 'network_error',\n message: `${endpointLabel} failed: ${error instanceof Error ? error.message : String(error)}`,\n endpoint: endpointLabel,\n retryable: true,\n })\n if (attempt < this.maxAttempts) {\n await delay(attempt)\n continue\n }\n throw lastError\n }\n\n const payload = await readJson(response)\n if (response.ok && isSuccessEnvelope(payload)) {\n return payload.data as T\n }\n\n const retryable = RETRYABLE_STATUSES.has(response.status)\n lastError = new IntegrationHubRequestError({\n status: response.status,\n code: errorCode(payload, response.status),\n message: errorMessage(payload, endpointLabel, response.status),\n endpoint: endpointLabel,\n retryable,\n })\n if (retryable && attempt < this.maxAttempts) {\n await delay(attempt)\n continue\n }\n throw lastError\n }\n // Unreachable — the loop always returns or throws — but satisfies the\n // type checker and fails loud if the invariant is ever broken.\n throw (\n lastError ??\n new IntegrationHubRequestError({\n status: 0,\n code: 'unknown',\n message: `${endpointLabel} exhausted retries without a result`,\n endpoint: endpointLabel,\n retryable: false,\n })\n )\n }\n}\n\nexport function createIntegrationHubClient(\n options: IntegrationHubClientOptions,\n): IntegrationHubClient {\n return new IntegrationHubClient(options)\n}\n\ninterface SuccessEnvelope {\n success: true\n data: unknown\n}\n\nfunction isSuccessEnvelope(payload: unknown): payload is SuccessEnvelope {\n return (\n typeof payload === 'object' &&\n payload !== null &&\n (payload as { success?: unknown }).success === true &&\n 'data' in payload\n )\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => '')\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n // Hono `HTTPException` (auth-middleware rejections) renders the message\n // as a plain-text body, not the `{ success, error }` envelope. Preserve\n // it so the error message stays actionable.\n return { __text: text }\n }\n}\n\nfunction errorCode(payload: unknown, status: number): string {\n if (typeof payload === 'object' && payload !== null) {\n const error = (payload as { error?: { code?: unknown } }).error\n if (error && typeof error.code === 'string') return error.code\n }\n return status === 0 ? 'network_error' : 'http_error'\n}\n\nfunction errorMessage(payload: unknown, endpointLabel: string, status: number): string {\n if (typeof payload === 'object' && payload !== null) {\n const record = payload as { error?: { message?: unknown }; __text?: unknown }\n if (record.error && typeof record.error.message === 'string') {\n return `${endpointLabel} → ${status}: ${record.error.message}`\n }\n if (typeof record.__text === 'string' && record.__text.length > 0) {\n return `${endpointLabel} → ${status}: ${record.__text.slice(0, 300)}`\n }\n }\n return `${endpointLabel} → ${status}`\n}\n\n/** Linear backoff — 200ms, 400ms, … — capped implicitly by `maxAttempts`. */\nfunction delay(attempt: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, attempt * 200))\n}\n"],"mappings":";;;;;AAsDA,IAAM,2BAA2B;AAEjC,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAG7B,IAAM,qBAAqB,oBAAI,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC;AAuC3C,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAC3C,OAAO;AAAA;AAAA,EAEP;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,OAMT;AACD,UAAM,MAAM,OAAO;AACnB,SAAK,SAAS,MAAM;AACpB,SAAK,OAAO,MAAM;AAClB,SAAK,WAAW,MAAM;AACtB,SAAK,YAAY,MAAM;AAAA,EACzB;AACF;AAuEO,IAAM,uBAAN,MAA2B;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAAsC;AAChD,QAAI,CAAC,QAAQ,SAAS;AACpB,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC7D;AACA,QAAI,QAAQ,KAAK,SAAS,aAAa,CAAC,QAAQ,KAAK,cAAc;AACjE,YAAM,IAAI,MAAM,4DAA4D;AAAA,IAC9E;AACA,QAAI,QAAQ,KAAK,SAAS,aAAa,CAAC,QAAQ,KAAK,aAAa;AAChE,YAAM,IAAI,MAAM,2DAA2D;AAAA,IAC7E;AACA,QAAI,QAAQ,KAAK,SAAS,cAAc,CAAC,QAAQ,KAAK,QAAQ;AAC5D,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AACA,SAAK,YAAY,QAAQ,YAAY,6BAA6B,QAAQ,QAAQ,EAAE;AACpF,SAAK,UAAU,QAAQ;AACvB,SAAK,OAAO,QAAQ;AACpB,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,cAAc,KAAK,IAAI,GAAG,QAAQ,eAAe,oBAAoB;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,OAAqE;AACzF,WAAO,KAAK,QAAuC,QAAQ,qBAAqB,MAAM,QAAQ;AAAA,MAC5F,SAAS,MAAM,WAAW,KAAK;AAAA,MAC/B,UAAU,MAAM;AAAA,MAChB,aAAa,MAAM;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,OAA2D;AAC9E,UAAM,gBAAgB,MAAM;AAC5B,UAAM,aAAa,MAAM,KAAK,gBAAgB;AAAA,MAC5C,QAAQ,MAAM;AAAA,MACd,UAAU;AAAA,QACR,IAAI,sBAAsB,MAAM,WAAW;AAAA,QAC3C,cAAc;AAAA,UACZ;AAAA,YACE,IAAI;AAAA,YACJ,aAAa,MAAM;AAAA,YACnB,QAAQ,0BAA0B,MAAM,WAAW;AAAA,YACnD,MAAM,MAAM,QAAQ;AAAA,YACpB,GAAI,MAAM,iBAAiB,EAAE,gBAAgB,MAAM,eAAe,IAAI,CAAC;AAAA,YACvE,GAAI,MAAM,kBAAkB,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,UAC5E;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AACD,UAAM,cACJ,WAAW,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,aAAa,KAC/D,WAAW,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,aAAa,KACjE,WAAW,gBAAgB,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,aAAa;AAC3E,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,2BAA2B;AAAA,QACnC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS,2DAA2D,aAAa;AAAA,QACjF,UAAU;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AACA,WAAO;AAAA,MACL,WAAW,YAAY,WAAW;AAAA,MAClC,GAAI,YAAY,aAAa,EAAE,YAAY,YAAY,WAAW,IAAI,CAAC;AAAA,MACvE,YAAY;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,OAAuD;AACxE,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,QACE,SAAS,MAAM;AAAA,QACf,UAAU,MAAM;AAAA,QAChB,aAAa,MAAM;AAAA,QACnB,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,MACvD;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,WAAW,OAAqD;AACpE,UAAM,QACJ,MAAM,YAAY,SACd,gBAAgB,mBAAmB,MAAM,QAAQ,IAAI,CAAC,cAAc,mBAAmB,MAAM,QAAQ,EAAE,CAAC,KACxG;AACN,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA,UAAU,KAAK;AAAA,MACf,MAAM;AAAA,IACR;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAqB,OAAmE;AAC5F,QAAI,CAAC,MAAM,cAAc,EAAE,MAAM,YAAY,MAAM,SAAS,SAAS,IAAI;AACvE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,QAAgC,QAAQ,wBAAwB,MAAM,QAAQ;AAAA,MACxF,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,aAAa,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,MAC3D,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,MACrD,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MAClD,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,IAC5D,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,gBAAgB,OAAoE;AACxF,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,EAAE,aAAa,MAAM,OAAO;AAAA,IAC9B;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,aAAa,QAAgB,SAA2B;AAC9D,UAAM,UAAU,IAAI,QAAQ,EAAE,QAAQ,mBAAmB,CAAC;AAC1D,QAAI,QAAS,SAAQ,IAAI,gBAAgB,kBAAkB;AAC3D,QAAI,KAAK,KAAK,SAAS,WAAW;AAChC,cAAQ,IAAI,iBAAiB,UAAU,KAAK,KAAK,YAAY,EAAE;AAC/D,cAAQ,IAAI,kBAAkB,KAAK,KAAK,WAAW;AACnD,cAAQ,IAAI,sBAAsB,MAAM;AAAA,IAC1C,OAAO;AACL,cAAQ,IAAI,iBAAiB,UAAU,KAAK,KAAK,MAAM,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,QACZ,QACA,MACA,QACA,MACY;AACZ,QAAI,CAAC,yBAAyB,KAAK,MAAM,GAAG;AAC1C,YAAM,IAAI,2BAA2B;AAAA,QACnC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS,UAAU,KAAK,UAAU,MAAM,CAAC;AAAA,QACzC,UAAU,GAAG,MAAM,IAAI,IAAI;AAAA,QAC3B,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AACA,UAAM,MAAM,GAAG,KAAK,QAAQ,mBAAmB,IAAI;AACnD,UAAM,gBAAgB,GAAG,MAAM,oBAAoB,IAAI;AACvD,UAAM,UAAU,KAAK,aAAa,QAAQ,SAAS,MAAS;AAC5D,UAAM,OAAoB;AAAA,MACxB;AAAA,MACA;AAAA,MACA,GAAI,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,IAC7D;AAEA,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,KAAK,aAAa,WAAW;AAC5D,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,KAAK,UAAU,KAAK;AAAA,UACnC,GAAG;AAAA,UACH,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,QAC5C,CAAC;AAAA,MACH,SAAS,OAAO;AACd,oBAAY,IAAI,2BAA2B;AAAA,UACzC,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS,GAAG,aAAa,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,UAC3F,UAAU;AAAA,UACV,WAAW;AAAA,QACb,CAAC;AACD,YAAI,UAAU,KAAK,aAAa;AAC9B,gBAAM,MAAM,OAAO;AACnB;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAEA,YAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,UAAI,SAAS,MAAM,kBAAkB,OAAO,GAAG;AAC7C,eAAO,QAAQ;AAAA,MACjB;AAEA,YAAM,YAAY,mBAAmB,IAAI,SAAS,MAAM;AACxD,kBAAY,IAAI,2BAA2B;AAAA,QACzC,QAAQ,SAAS;AAAA,QACjB,MAAM,UAAU,SAAS,SAAS,MAAM;AAAA,QACxC,SAAS,aAAa,SAAS,eAAe,SAAS,MAAM;AAAA,QAC7D,UAAU;AAAA,QACV;AAAA,MACF,CAAC;AACD,UAAI,aAAa,UAAU,KAAK,aAAa;AAC3C,cAAM,MAAM,OAAO;AACnB;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAGA,UACE,aACA,IAAI,2BAA2B;AAAA,MAC7B,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS,GAAG,aAAa;AAAA,MACzB,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAAA,EAEL;AACF;AAEO,SAAS,2BACd,SACsB;AACtB,SAAO,IAAI,qBAAqB,OAAO;AACzC;AAOA,SAAS,kBAAkB,SAA8C;AACvE,SACE,OAAO,YAAY,YACnB,YAAY,QACX,QAAkC,YAAY,QAC/C,UAAU;AAEd;AAEA,eAAe,SAAS,UAAsC;AAC5D,QAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AAIN,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AACF;AAEA,SAAS,UAAU,SAAkB,QAAwB;AAC3D,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,QAAS,QAA2C;AAC1D,QAAI,SAAS,OAAO,MAAM,SAAS,SAAU,QAAO,MAAM;AAAA,EAC5D;AACA,SAAO,WAAW,IAAI,kBAAkB;AAC1C;AAEA,SAAS,aAAa,SAAkB,eAAuB,QAAwB;AACrF,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,SAAS;AACf,QAAI,OAAO,SAAS,OAAO,OAAO,MAAM,YAAY,UAAU;AAC5D,aAAO,GAAG,aAAa,WAAM,MAAM,KAAK,OAAO,MAAM,OAAO;AAAA,IAC9D;AACA,QAAI,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,GAAG;AACjE,aAAO,GAAG,aAAa,WAAM,MAAM,KAAK,OAAO,OAAO,MAAM,GAAG,GAAG,CAAC;AAAA,IACrE;AAAA,EACF;AACA,SAAO,GAAG,aAAa,WAAM,MAAM;AACrC;AAGA,SAAS,MAAM,SAAgC;AAC7C,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,UAAU,GAAG,CAAC;AACpE;","names":[]}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
CredentialsExpired,
|
|
8
8
|
ResourceContention
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-Y6O3MIBW.js";
|
|
10
10
|
|
|
11
11
|
// src/connectors/oauth.ts
|
|
12
12
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -1147,13 +1147,13 @@ function gmail(opts) {
|
|
|
1147
1147
|
]
|
|
1148
1148
|
},
|
|
1149
1149
|
async executeRead(inv) {
|
|
1150
|
-
const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
|
|
1150
|
+
const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret, inv.onCredentialsRotated);
|
|
1151
1151
|
if (inv.capabilityName === "list_messages") return listMessages(inv, accessToken, timeoutMs);
|
|
1152
1152
|
if (inv.capabilityName === "read_message") return readMessage(inv, accessToken, timeoutMs);
|
|
1153
1153
|
throw new Error(`gmail: unknown read capability ${inv.capabilityName}`);
|
|
1154
1154
|
},
|
|
1155
1155
|
async executeMutation(inv) {
|
|
1156
|
-
const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
|
|
1156
|
+
const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret, inv.onCredentialsRotated);
|
|
1157
1157
|
if (inv.capabilityName === "send_reply") return sendReply(inv, accessToken, timeoutMs);
|
|
1158
1158
|
if (inv.capabilityName === "watch_label") return watchLabel(inv, accessToken, timeoutMs);
|
|
1159
1159
|
throw new Error(`gmail: unknown mutation capability ${inv.capabilityName}`);
|
|
@@ -1426,7 +1426,7 @@ async function watchLabel(inv, accessToken, timeoutMs) {
|
|
|
1426
1426
|
idempotentReplay: false
|
|
1427
1427
|
};
|
|
1428
1428
|
}
|
|
1429
|
-
async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
|
|
1429
|
+
async function ensureFreshAccessToken4(creds, clientId, clientSecret, onCredentialsRotated) {
|
|
1430
1430
|
if (creds.kind !== "oauth2") {
|
|
1431
1431
|
throw new Error("gmail: expected oauth2 credentials");
|
|
1432
1432
|
}
|
|
@@ -1445,6 +1445,12 @@ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
|
|
|
1445
1445
|
creds.accessToken = refreshed.accessToken;
|
|
1446
1446
|
creds.expiresAt = refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0;
|
|
1447
1447
|
if (refreshed.refreshToken) creds.refreshToken = refreshed.refreshToken;
|
|
1448
|
+
onCredentialsRotated?.({
|
|
1449
|
+
kind: "oauth2",
|
|
1450
|
+
accessToken: creds.accessToken,
|
|
1451
|
+
refreshToken: creds.refreshToken,
|
|
1452
|
+
expiresAt: creds.expiresAt
|
|
1453
|
+
});
|
|
1448
1454
|
return creds.accessToken;
|
|
1449
1455
|
}
|
|
1450
1456
|
|
|
@@ -4257,4 +4263,4 @@ export {
|
|
|
4257
4263
|
asanaConnector,
|
|
4258
4264
|
salesforceConnector
|
|
4259
4265
|
};
|
|
4260
|
-
//# sourceMappingURL=chunk-
|
|
4266
|
+
//# sourceMappingURL=chunk-JOILC44P.js.map
|