@tangle-network/hub-sdk 0.28.0-develop.20260819054947.83fb85f → 0.28.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 +191 -6
- package/dist/index.d.ts +832 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +621 -253
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -30,9 +30,7 @@ SaaS vs on-prem flips via env only — there is no per-client default URL.
|
|
|
30
30
|
| `TANGLE_API_KEY` | One of | Long-lived API key (Bearer). User / API key principal. |
|
|
31
31
|
| `TANGLE_HUB_CAPABILITY_TOKEN` | One of | Short-lived capability token (Bearer). Sandbox-runtime principal. Auto-minted server-side per action. |
|
|
32
32
|
|
|
33
|
-
The two auth credentials are mutually exclusive — set exactly one.
|
|
34
|
-
mirrors the platform-api `hubSandboxEnvironmentSchema` rule
|
|
35
|
-
(`exactly-one-api-key-or-capability-token`).
|
|
33
|
+
The two auth credentials are mutually exclusive — set exactly one.
|
|
36
34
|
|
|
37
35
|
### Examples
|
|
38
36
|
|
|
@@ -126,6 +124,29 @@ const hub = HubClient.fromEnv({
|
|
|
126
124
|
});
|
|
127
125
|
```
|
|
128
126
|
|
|
127
|
+
## Tool approvals
|
|
128
|
+
|
|
129
|
+
`tools.invoke()` preserves an existing `HUB_APPROVAL_REQUIRED` error instead of requesting another token.
|
|
130
|
+
The error retains the original approval ID and the server's action details.
|
|
131
|
+
Present that approval through the product's decision flow.
|
|
132
|
+
Set `approve: true` only after the caller has authorized that action.
|
|
133
|
+
Automatic capability-token minting remains available when the server returns no concrete approval.
|
|
134
|
+
|
|
135
|
+
## GitHub OAuth scopes
|
|
136
|
+
|
|
137
|
+
Pass the exact scope set when the caller needs GitHub repository selection.
|
|
138
|
+
Platform accepts `read:user`, `repo`, and `read:org` together for this request.
|
|
139
|
+
It rejects a callback if GitHub grants a different set.
|
|
140
|
+
Omitting `requestedScopes` retains the existing default GitHub flow.
|
|
141
|
+
The SDK refuses a start response with different scopes, including responses from an older Platform deployment.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const hub = HubClient.fromEnv();
|
|
145
|
+
await hub.connections.start("github", {
|
|
146
|
+
requestedScopes: ["read:user", "repo", "read:org"],
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
129
150
|
## Workflows
|
|
130
151
|
|
|
131
152
|
`hub.workflows` covers the `/v1/workflows` surface — the same resource the
|
|
@@ -218,19 +239,183 @@ product does not need a second workflow-run request.
|
|
|
218
239
|
Callbacks may be retried; use `delivery.runId` as the durable idempotency key
|
|
219
240
|
before starting product work.
|
|
220
241
|
|
|
242
|
+
## Allowances and the paywall decision
|
|
243
|
+
|
|
244
|
+
A product that bills its end users sets a free allowance, and optionally a paid one, on a meter.
|
|
245
|
+
Hub admits every turn against it, counts turns per member and per line, and reads each payer's dollars from the ledger.
|
|
246
|
+
The product keeps only its checkout: after a payment it records the member as paid until the period ends.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
await hub.allowances.setPlan(`agent:${agentId}`, {
|
|
250
|
+
free: { turnsPerDay: 20, usdPerDay: 2 },
|
|
251
|
+
paid: { turnsPerDay: 200, usdPerDay: 5, priceUsdMonthly: 10 },
|
|
252
|
+
spendResourceType: "hosted_enrollment",
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const turn = await hub.allowances.admit(`agent:${agentId}`, {
|
|
256
|
+
member: enrollmentId,
|
|
257
|
+
turnId: messageId,
|
|
258
|
+
line: lineId,
|
|
259
|
+
});
|
|
260
|
+
if (turn.decision === "paywall") return sendCheckoutLink();
|
|
261
|
+
if (turn.decision === "allowance_reached") return sendComeBackTomorrow();
|
|
262
|
+
|
|
263
|
+
// From the checkout webhook:
|
|
264
|
+
await hub.allowances.grant(`agent:${agentId}`, enrollmentId, {
|
|
265
|
+
expiresAt: periodEnd.toISOString(),
|
|
266
|
+
reference: subscriptionId,
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`admit` is idempotent by `turnId`, so a retried turn is never counted twice.
|
|
271
|
+
A member's dollars are the sum of today's ledger charges on keys bound to `(spendResourceType, member)` and on every key delegated below them, so delegate each member's key with that resource.
|
|
272
|
+
A paid member also has a funded capacity for the period: the price less Tangle's 10% fee, plus the free daily dollars for the period's 31 days.
|
|
273
|
+
With the plan above, that is $9 + $62 = $71 against the $155 that $5 a day would allow.
|
|
274
|
+
Past it, `admit` answers `allowance_reached`, and `turn.period` shows `spent`, `funded` and `remaining`.
|
|
275
|
+
Only the meter's owner may call these, signed in or with a root key of their own.
|
|
276
|
+
A delegated or resource-bound key is refused, so an end user's sandbox cannot grant itself the paid tier.
|
|
277
|
+
|
|
278
|
+
## Shared-Line application dispatch
|
|
279
|
+
|
|
280
|
+
Before admitting a shared-Line callback, check its current Hub authority:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const current = await hub.lines.applicationDispatchCurrent(
|
|
284
|
+
lineId,
|
|
285
|
+
messageId,
|
|
286
|
+
{ attachmentId, memberId, memberRevision, senderAddress, textSha256, binding, dispatchFence, dispatchDeadline },
|
|
287
|
+
callbackBearer,
|
|
288
|
+
);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`textSha256` is the lowercase SHA-256 hex digest of the exact callback `text` UTF-8 bytes, with no normalization.
|
|
292
|
+
Compute it from the received callback text, not from a caller-supplied digest.
|
|
293
|
+
Hub compares it with the persisted inbound message text before returning `current: true`.
|
|
294
|
+
This uses the callback bearer alone; account credentials, cookies, and redirects are excluded.
|
|
295
|
+
It returns a boolean only from a successful, valid Hub response and makes no retry.
|
|
296
|
+
HTTP failures retain their status and code without response bodies or credential details.
|
|
297
|
+
Configure the client's injected `fetch` to enforce the caller's request deadline.
|
|
298
|
+
A current result authorizes this observation; native admission must still enforce its own expiry and deduplication contract.
|
|
299
|
+
|
|
221
300
|
## Exports
|
|
222
301
|
|
|
223
302
|
- `HubClient`, `HubClient.fromEnv(options?)`
|
|
224
303
|
- `HubConnectionsClient`, `HubPermissionsClient`, `HubTokensClient`,
|
|
225
304
|
`HubChannelsClient`, `HubEventSubscriptionsClient`, `HubToolsClient`,
|
|
226
|
-
`HubApprovalsClient`, `HubAuditClient`, `HubWorkflowsClient
|
|
305
|
+
`HubApprovalsClient`, `HubAuditClient`, `HubWorkflowsClient`,
|
|
306
|
+
`HubAllowancesClient`
|
|
227
307
|
- `deriveHubEventCallbackSecret`, `authenticateHubEventRequest`,
|
|
228
308
|
`verifyHubEventSignature`, `parseHubEventDelivery`, `HubEventDeliveryError`
|
|
229
309
|
- `HubSdkError` — typed `code: HubErrorCode`, redacted `details`, optional
|
|
230
310
|
HTTP `status`
|
|
231
311
|
- `HUB_URL_ENV_VAR`, `HUB_API_KEY_ENV_VAR`, `HUB_CAPABILITY_TOKEN_ENV_VAR` —
|
|
232
|
-
string constants for the env-var names
|
|
233
|
-
`hub-sandbox-contract.ts` `z.literal(...)`)
|
|
312
|
+
string constants for the env-var names
|
|
234
313
|
- `resolveHubBaseUrl(env?)`, `resolveHubAuth(env?)` — standalone resolvers
|
|
235
314
|
with the same fail-loud contract as `fromEnv`
|
|
236
315
|
- All request/response types from the `/v1/hub/*` contract
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## Link application login to explicit Hub permissions
|
|
319
|
+
|
|
320
|
+
Use the same `HubClient` for application management; do not add a product-local
|
|
321
|
+
HTTP client. These operations require the authenticated developer account, not
|
|
322
|
+
an app's OIDC access token. Customer grants are created only by the existing
|
|
323
|
+
session-authenticated `/cross-site/app-consent` flow.
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { HubClient } from "@tangle-network/hub-sdk";
|
|
327
|
+
|
|
328
|
+
const hub = HubClient.fromEnv(); // developer account for app administration
|
|
329
|
+
const { app, clientSecret } = await hub.apps.create({
|
|
330
|
+
name: "Support assistant",
|
|
331
|
+
redirectUris: ["https://assistant.example/hub/callback"],
|
|
332
|
+
allowedScopes: ["github.issues.create"],
|
|
333
|
+
});
|
|
334
|
+
// Save clientSecret once in the application's server-side secret store.
|
|
335
|
+
await hub.apps.linkOAuthClient(app.id, oidcClientId);
|
|
336
|
+
const apps = await hub.apps.list(); // includes oauthClientId; never the secret
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`oidcClientId` is the application's already-registered Sign in with Tangle client.
|
|
340
|
+
Linking requires the same owner on both records and is immutable and one-to-one.
|
|
341
|
+
It does not reuse the two clients' secrets, change their IDs, create a user grant,
|
|
342
|
+
or authorize spending. Use separate app/client pairs for separate deployments.
|
|
343
|
+
Existing unlinked broker apps remain compatible. There is no automatic link by
|
|
344
|
+
name, callback domain, email, or ownership alone.
|
|
345
|
+
|
|
346
|
+
With a customer's authenticated Hub client, `hub.apps.grants.list()` returns only
|
|
347
|
+
that customer's active resource grants and `hub.apps.grants.revoke(grantId)`
|
|
348
|
+
revokes one idempotently. App administrators use `hub.apps.revoke(appId)` to
|
|
349
|
+
suspend the app, revoke all its customer grants, and disable its owned linked
|
|
350
|
+
OIDC client. An app's broker credential cannot call these management methods.
|
|
351
|
+
|
|
352
|
+
An app broker can create `new HubLineGrantsClient(options)` for a line approved by its owner.
|
|
353
|
+
Pass an `authHeaders` provider that mints a fresh scoped broker token for each call.
|
|
354
|
+
Pass the exact `workspaceId` and `workspaceAgentId` binding to every method.
|
|
355
|
+
The `current` response includes `intakeMode`, `priorAttachmentId`, and `priorAttachmentActive`.
|
|
356
|
+
An `owner_only` grant attaches one declared owner sender and rejects unknown senders.
|
|
357
|
+
The app cannot choose its own sender or change the approved intake mode in `attach`.
|
|
358
|
+
|
|
359
|
+
An owner can instead consent with `execution=application` (and `owner_test=1`).
|
|
360
|
+
Then `current().execution` is `"application"` and `attach` is refused.
|
|
361
|
+
The owner's existing attachment stays on the Line.
|
|
362
|
+
`memberApplication()` reads the declared owner member, its `memberRevision`, `eligible`, and the Hub-derived `binding`.
|
|
363
|
+
`selectApplication()` routes that member's texts to an HTTPS callback on the approved origin, with the app's own bearer `secret`.
|
|
364
|
+
`clearApplication()` returns them to the incumbent agent.
|
|
365
|
+
When another app answers, `selection.own` is false and `selection.app.name` and `selection.selectedAt` say which app and since when; its callback URL stays private.
|
|
366
|
+
Show that to the owner with an explicit choice to answer here instead.
|
|
367
|
+
Only on that choice, pass the selection id you read as both `replaceSelectionId` and `expectedSelectionId` to `selectApplication()`.
|
|
368
|
+
Without `replaceSelectionId`, or with a stale id, Hub refuses with 409 and the other app keeps answering.
|
|
369
|
+
The app cannot clear another app's selection, and revoking the grant revokes its selection.
|
|
370
|
+
Each callback carries `binding`; verify currentness with `lines.applicationDispatchCurrent`.
|
|
371
|
+
|
|
372
|
+
Broker execution keeps the one approved connection even when the invocation
|
|
373
|
+
omits `connectionId`; it never substitutes another account. Each one-shot broker
|
|
374
|
+
token is checked against current app policy and the exact active grant when
|
|
375
|
+
minted and consumed. Changing consent revokes the old binding, tokens and codes;
|
|
376
|
+
the fresh consent response supplies a new grant ID. Login disconnect revokes the
|
|
377
|
+
linked app's existing grants for that customer, not other customers or apps.
|
|
378
|
+
An operation already admitted to a provider may finish; revocation blocks new
|
|
379
|
+
admissions and minting, not external side effects already underway.
|
|
380
|
+
|
|
381
|
+
This is identity and resource delegation, **not** generalized spending delegation.
|
|
382
|
+
Existing product billing checks and action approvals continue to apply.
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
### Select an application for one verified line member
|
|
386
|
+
|
|
387
|
+
The account owner can select an app without changing the Line attachment.
|
|
388
|
+
Unselected members keep the existing default agent.
|
|
389
|
+
The member must already be active, declared, verified, and consented.
|
|
390
|
+
An owner without a customer subject can select only their own Platform subject.
|
|
391
|
+
Other members must retain their existing customer subject.
|
|
392
|
+
The app authorizes and funds its own agent execution.
|
|
393
|
+
|
|
394
|
+
Use `hub.lines.memberApplications.get(lineId, memberId)` to read the current selection.
|
|
395
|
+
Its `app.name` is Hub's name for the app that answers: the registered app of a consented grant, or the trusted app that serves the callback origin, else the callback host.
|
|
396
|
+
Bindings starting with `line-grant:` belong to consented app grants and are refused here.
|
|
397
|
+
Use `select(lineId, memberId, input)` with `expectedSelectionId`, `expectedMemberRevision`, `subjectId`, and `application`.
|
|
398
|
+
The application supplies its approved HTTPS callback, binding, and callback secret.
|
|
399
|
+
Use `clear(lineId, memberId, { expectedSelectionId, expectedMemberRevision })` to restore the default for future messages.
|
|
400
|
+
Selection changes use compare-and-swap and refuse while this member has an active callback or provider delivery.
|
|
401
|
+
Each inbound message pins its selected target.
|
|
402
|
+
Revoked selections refuse pending work instead of redirecting it.
|
|
403
|
+
Existing messages and execution receipts retain their original identity.
|
|
404
|
+
This path uses the existing shared-application flag and remains disabled by default.
|
|
405
|
+
Account-owner channel authority is separate from the app's agent workspace key.
|
|
406
|
+
|
|
407
|
+
### Customer grants with separate sponsorship
|
|
408
|
+
|
|
409
|
+
A server registers an application with `accessPayer: "app-owner"` when its owner sponsors platform eligibility. This is immutable: Hub uses the authenticated stored application owner, never a payer ID from the customer. Connections and grants still belong to the customer. The customer's native consent names the exact connection and actions. Existing explicit deny or approval policies still take precedence.
|
|
410
|
+
|
|
411
|
+
Keep the application secret on your server. After same-session callback and state validation, use the maintained broker client:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { HubAppBrokerClient } from "@tangle-network/hub-sdk";
|
|
415
|
+
|
|
416
|
+
const broker = new HubAppBrokerClient({ baseUrl, clientId, clientSecret });
|
|
417
|
+
const grant = await broker.exchange({ code, redirectUri, connectionId });
|
|
418
|
+
const result = await broker.invoke(grant.grantId, connectionId, "github.issues.list", { owner: "example", repo: "project" });
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
`invoke` mints a fresh one-use, 60-second broker token and checks its selected scope before execution. The host application must recheck its own current customer enrollment on every call and revoke the app grant when access ends. Do not return broker tokens or application secrets to a browser or persist them in agent profiles.
|