@aithos/sdk 0.1.0-alpha.4 → 0.1.0-alpha.41
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 +211 -7
- package/dist/src/apps.d.ts +155 -0
- package/dist/src/apps.js +288 -0
- package/dist/src/assets.d.ts +207 -0
- package/dist/src/assets.js +533 -0
- package/dist/src/auth-api.d.ts +138 -0
- package/dist/src/auth-api.js +168 -0
- package/dist/src/auth.d.ts +536 -119
- package/dist/src/auth.js +1207 -152
- package/dist/src/compute.d.ts +251 -9
- package/dist/src/compute.js +293 -16
- package/dist/src/data-schema-contacts-v1.d.ts +14 -0
- package/dist/src/data-schema-contacts-v1.js +28 -0
- package/dist/src/data.d.ts +153 -0
- package/dist/src/data.js +670 -0
- package/dist/src/endpoints.d.ts +9 -0
- package/dist/src/endpoints.js +5 -0
- package/dist/src/ethos.d.ts +202 -1
- package/dist/src/ethos.js +821 -16
- package/dist/src/index.d.ts +18 -6
- package/dist/src/index.js +39 -6
- package/dist/src/internal/delegate-bundle.d.ts +18 -0
- package/dist/src/internal/delegate-bundle.js +94 -0
- package/dist/src/internal/delegate-state.d.ts +45 -0
- package/dist/src/internal/delegate-state.js +120 -0
- package/dist/src/internal/envelope.d.ts +77 -0
- package/dist/src/internal/envelope.js +154 -0
- package/dist/src/internal/owner-signers.d.ts +78 -0
- package/dist/src/internal/owner-signers.js +179 -0
- package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
- package/dist/src/internal/protocol-client-bridge.js +20 -0
- package/dist/src/internal/recovery-file.d.ts +29 -0
- package/dist/src/internal/recovery-file.js +98 -0
- package/dist/src/internal/signer.d.ts +59 -0
- package/dist/src/internal/signer.js +86 -0
- package/dist/src/key-store.d.ts +128 -0
- package/dist/src/key-store.js +244 -0
- package/dist/src/mandates.d.ts +163 -1
- package/dist/src/mandates.js +286 -8
- package/dist/src/react/AithosAsset.d.ts +66 -0
- package/dist/src/react/AithosAsset.js +67 -0
- package/dist/src/react/context.d.ts +29 -0
- package/dist/src/react/context.js +31 -0
- package/dist/src/react/index.d.ts +28 -0
- package/dist/src/react/index.js +30 -0
- package/dist/src/react/use-aithos-asset.d.ts +39 -0
- package/dist/src/react/use-aithos-asset.js +118 -0
- package/dist/src/sdk.d.ts +46 -3
- package/dist/src/sdk.js +49 -23
- package/dist/src/wallet.d.ts +4 -6
- package/dist/src/wallet.js +18 -8
- package/dist/src/web.d.ts +279 -0
- package/dist/src/web.js +186 -0
- package/dist/test/auth-j3.test.d.ts +2 -0
- package/dist/test/auth-j3.test.js +391 -0
- package/dist/test/compute-delegate-path.test.d.ts +2 -0
- package/dist/test/compute-delegate-path.test.js +183 -0
- package/dist/test/compute.test.js +26 -11
- package/dist/test/endpoints.test.js +20 -1
- package/dist/test/envelope.test.d.ts +2 -0
- package/dist/test/envelope.test.js +318 -0
- package/dist/test/ethos-first-edition.test.d.ts +2 -0
- package/dist/test/ethos-first-edition.test.js +248 -0
- package/dist/test/ethos.test.d.ts +2 -0
- package/dist/test/ethos.test.js +219 -0
- package/dist/test/key-store.test.d.ts +2 -0
- package/dist/test/key-store.test.js +161 -0
- package/dist/test/mandates-compute.test.d.ts +2 -0
- package/dist/test/mandates-compute.test.js +256 -0
- package/dist/test/mandates.test.d.ts +2 -0
- package/dist/test/mandates.test.js +93 -0
- package/dist/test/sdk.test.js +70 -30
- package/dist/test/signer.test.d.ts +2 -0
- package/dist/test/signer.test.js +117 -0
- package/dist/test/signup-bootstrap.test.d.ts +2 -0
- package/dist/test/signup-bootstrap.test.js +311 -0
- package/dist/test/wallet.test.js +20 -9
- package/dist/test/web.test.d.ts +2 -0
- package/dist/test/web.test.js +270 -0
- package/package.json +18 -3
package/dist/src/mandates.js
CHANGED
|
@@ -1,13 +1,291 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright 2026 Mathieu Colla
|
|
3
|
-
//
|
|
3
|
+
// `sdk.mandates` namespace — owner-side mandate lifecycle.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// Three verbs:
|
|
6
|
+
// create(input) → mints + publishes + returns the
|
|
7
|
+
// shareable .aithos-delegate.json bundle
|
|
8
|
+
// list() → mandates the owner has issued
|
|
9
|
+
// revoke(mandateId) → publishes a §4.2 revocation
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// Capability boundary: every method requires an owner signed in. We
|
|
12
|
+
// reach the OwnerSigners through `auth._getOwnerSigners()` and project
|
|
13
|
+
// to a `StoredIdentity` for protocol-client interop. When
|
|
14
|
+
// protocol-client gains a Signer-shaped API the projection step
|
|
15
|
+
// disappears.
|
|
16
|
+
import { buildSignedEnvelope, mintDelegateBundle, readRpc, } from "@aithos/protocol-client";
|
|
17
|
+
import { ownerKeyPair } from "./internal/protocol-client-bridge.js";
|
|
18
|
+
import { AithosSDKError } from "./types.js";
|
|
19
|
+
/**
|
|
20
|
+
* The opt-in scope that authorizes a delegate to spend the subject's
|
|
21
|
+
* compute credits via the Aithos compute proxy. Mirror of
|
|
22
|
+
* `COMPUTE_INVOKE_SCOPE` in `@aithos/protocol-core` v0.4.0.
|
|
23
|
+
*
|
|
24
|
+
* The SDK's `mandates.create()` injects this scope automatically when
|
|
25
|
+
* the caller passes a `compute` namespace, and refuses to mint a
|
|
26
|
+
* mandate where the caller put it directly into `scopes` — this is
|
|
27
|
+
* what makes "compute is a separate, conscious decision" hold at the
|
|
28
|
+
* API surface.
|
|
29
|
+
*/
|
|
30
|
+
export const COMPUTE_INVOKE_SCOPE = "compute.invoke";
|
|
31
|
+
export class MandatesNamespace {
|
|
32
|
+
#deps;
|
|
33
|
+
constructor(deps) {
|
|
34
|
+
this.#deps = deps;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Mint, sign, publish, and package a fresh delegate bundle. The
|
|
38
|
+
* grantee's keypair is generated inside this call and never
|
|
39
|
+
* persisted on the owner's machine — the seed flows out via the
|
|
40
|
+
* returned Blob and only via that Blob.
|
|
41
|
+
*/
|
|
42
|
+
async create(input) {
|
|
43
|
+
const owner = this.#requireOwner();
|
|
44
|
+
// A mandate must carry at least one capability — either ethos
|
|
45
|
+
// scopes, the compute namespace, or both. A "compute-only" mandate
|
|
46
|
+
// (`scopes: []` + `compute: { ... }`) is legitimate: it gives the
|
|
47
|
+
// grantee no access to the subject's ethos data, only the right
|
|
48
|
+
// to spend a bounded amount of compute credits in their name.
|
|
49
|
+
// Useful for creative assistants, brainstorming agents, etc.
|
|
50
|
+
if (input.scopes.length === 0 && input.compute === undefined) {
|
|
51
|
+
throw new AithosSDKError("mandates_invalid_scopes", "scopes must be a non-empty list (or pass `compute` for a compute-only mandate)");
|
|
52
|
+
}
|
|
53
|
+
if (input.ttlSeconds <= 0) {
|
|
54
|
+
throw new AithosSDKError("mandates_invalid_ttl", "ttlSeconds must be > 0");
|
|
55
|
+
}
|
|
56
|
+
// Forbid `compute.invoke` smuggled in via `scopes[]`. The whole
|
|
57
|
+
// point of the dedicated `compute` namespace is that adding token-
|
|
58
|
+
// spending capability requires a typed, named, reviewable input —
|
|
59
|
+
// not a string lost in a generic list. Type-checking can't catch
|
|
60
|
+
// this (the union doesn't include the literal, but callers can
|
|
61
|
+
// up-cast); the runtime check is what holds.
|
|
62
|
+
if (input.scopes.some((s) => s === COMPUTE_INVOKE_SCOPE)) {
|
|
63
|
+
throw new AithosSDKError("mandates_invalid_scopes", `Pass token-spending capability via the dedicated 'compute' namespace, ` +
|
|
64
|
+
`not by adding "${COMPUTE_INVOKE_SCOPE}" to scopes[]. The namespace forces ` +
|
|
65
|
+
`an explicit budget and is what a consent UI reviews.`);
|
|
66
|
+
}
|
|
67
|
+
// Validate + project the compute namespace if present, then derive
|
|
68
|
+
// the final scopes/constraints to send to the protocol layer.
|
|
69
|
+
const computeProjection = projectCompute(input.compute);
|
|
70
|
+
const projectedScopes = [...input.scopes];
|
|
71
|
+
if (computeProjection) {
|
|
72
|
+
projectedScopes.push(COMPUTE_INVOKE_SCOPE);
|
|
73
|
+
}
|
|
74
|
+
const actorSphere = input.actorSphere ?? defaultSphereFromScopes(input.scopes);
|
|
75
|
+
const ownerStored = owner._unsafeStoredIdentity();
|
|
76
|
+
const result = await mintDelegateBundle({
|
|
77
|
+
owner: ownerStored,
|
|
78
|
+
granteeId: input.granteeId,
|
|
79
|
+
...(input.granteeLabel ? { granteeLabel: input.granteeLabel } : {}),
|
|
80
|
+
actorSphere,
|
|
81
|
+
scopes: projectedScopes,
|
|
82
|
+
ttlSeconds: input.ttlSeconds,
|
|
83
|
+
// protocol-client v0.1.0-alpha.11 ships MandateConstraints without
|
|
84
|
+
// the `compute` field; the wire format accepts it though (the
|
|
85
|
+
// canonicalizer just serializes whatever's in the object). We
|
|
86
|
+
// up-cast through `unknown` to bypass the structural check until
|
|
87
|
+
// protocol-client picks up protocol-core 0.4.0 types.
|
|
88
|
+
...(computeProjection
|
|
89
|
+
? {
|
|
90
|
+
constraints: {
|
|
91
|
+
compute: computeProjection,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
: {}),
|
|
95
|
+
...(input.notBefore ? { notBefore: input.notBefore } : {}),
|
|
96
|
+
});
|
|
97
|
+
const mandate = result.mandate;
|
|
98
|
+
return {
|
|
99
|
+
mandateId: mandate.id,
|
|
100
|
+
subjectDid: mandate.subject_did,
|
|
101
|
+
granteeId: input.granteeId,
|
|
102
|
+
scopes: projectedScopes,
|
|
103
|
+
expiresAt: mandate.not_after ?? null,
|
|
104
|
+
bundle: result.bundleBlob,
|
|
105
|
+
filename: `aithos-delegate-${mandate.id}.json`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* List mandates issued by the signed-in owner. Pages through
|
|
110
|
+
* `aithos.list_mandates` until exhausted (or until 5 pages have
|
|
111
|
+
* been crawled — an owner with more than 1000 active mandates is
|
|
112
|
+
* out of scope today).
|
|
113
|
+
*/
|
|
114
|
+
async list() {
|
|
115
|
+
const owner = this.#requireOwner();
|
|
116
|
+
const out = [];
|
|
117
|
+
let cursor;
|
|
118
|
+
for (let i = 0; i < 5; i++) {
|
|
119
|
+
const page = await readRpc("aithos.list_mandates", {
|
|
120
|
+
issuer_did: owner.did,
|
|
121
|
+
limit: 200,
|
|
122
|
+
...(cursor ? { cursor } : {}),
|
|
123
|
+
});
|
|
124
|
+
for (const it of page.items) {
|
|
125
|
+
out.push(toOwnedMandate(it));
|
|
126
|
+
}
|
|
127
|
+
if (!page.next_cursor)
|
|
128
|
+
break;
|
|
129
|
+
cursor = page.next_cursor;
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Publish a §4.2 revocation for `mandateId`. The mandate stops
|
|
135
|
+
* authorizing future actions (artifacts dated before `revoked_at`
|
|
136
|
+
* remain valid — revocation is not retroactive).
|
|
137
|
+
*
|
|
138
|
+
* Server-side: handled by `aithos.publish_revocation`. The envelope
|
|
139
|
+
* is signed by the owner's `#public` sphere — the spec also accepts
|
|
140
|
+
* `#root`, but `#public` is what the existing app does.
|
|
141
|
+
*
|
|
142
|
+
* Throws {@link AithosSDKError} on backend errors. Note that
|
|
143
|
+
* server-side support is in-flight; this method may surface a
|
|
144
|
+
* `mandates_-32601` (method not found) until the auth platform
|
|
145
|
+
* lands the corresponding write handler.
|
|
146
|
+
*/
|
|
147
|
+
async revoke(mandateId) {
|
|
148
|
+
const owner = this.#requireOwner();
|
|
149
|
+
const ownerStored = owner._unsafeStoredIdentity();
|
|
150
|
+
// TODO(post-alpha): once @aithos/protocol-client exposes a public
|
|
151
|
+
// configuration API for the `api` endpoint, route this through the
|
|
152
|
+
// same channel as `readRpc` / `publishZoneEdit` so self-hosters
|
|
153
|
+
// can override. For now, target the production write surface.
|
|
154
|
+
const url = "https://api.aithos.be/mcp/primitives/write";
|
|
155
|
+
const params = {
|
|
156
|
+
mandate_id: mandateId,
|
|
157
|
+
revoked_at: new Date().toISOString(),
|
|
158
|
+
};
|
|
159
|
+
// Reach into the owner's #public signer for envelope signing via
|
|
160
|
+
// the centralized migration bridge (will go away when
|
|
161
|
+
// protocol-client accepts Signer-shaped objects).
|
|
162
|
+
const publicKp = ownerKeyPair(owner, "public");
|
|
163
|
+
const envelope = buildSignedEnvelope({
|
|
164
|
+
iss: ownerStored.did,
|
|
165
|
+
aud: url,
|
|
166
|
+
method: "aithos.publish_revocation",
|
|
167
|
+
verificationMethod: `${ownerStored.did}#public`,
|
|
168
|
+
params,
|
|
169
|
+
signer: publicKp,
|
|
170
|
+
});
|
|
171
|
+
let res;
|
|
172
|
+
try {
|
|
173
|
+
res = await this.#deps.fetch(url, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { "content-type": "application/json" },
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
jsonrpc: "2.0",
|
|
178
|
+
id: "aithos.publish_revocation",
|
|
179
|
+
method: "aithos.publish_revocation",
|
|
180
|
+
params: { ...params, _envelope: envelope },
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
throw new AithosSDKError("network", e.message);
|
|
186
|
+
}
|
|
187
|
+
const body = (await res.json().catch(() => null));
|
|
188
|
+
if (!body) {
|
|
189
|
+
throw new AithosSDKError("http", `HTTP ${res.status} ${res.statusText} — non-JSON response`, { status: res.status });
|
|
190
|
+
}
|
|
191
|
+
if (body.error) {
|
|
192
|
+
throw new AithosSDKError(`mandates_${body.error.code}`, body.error.message, body.error.data ? { data: body.error.data } : undefined);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/* ------------------------------------------------------------------------ */
|
|
196
|
+
/* Internals */
|
|
197
|
+
/* ------------------------------------------------------------------------ */
|
|
198
|
+
#requireOwner() {
|
|
199
|
+
const owner = this.#deps.auth._getOwnerSigners();
|
|
200
|
+
if (!owner || owner.destroyed) {
|
|
201
|
+
throw new AithosSDKError("mandates_no_owner", "no owner signed in; sign in first");
|
|
202
|
+
}
|
|
203
|
+
return owner;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/* -------------------------------------------------------------------------- */
|
|
207
|
+
/* Helpers */
|
|
208
|
+
/* -------------------------------------------------------------------------- */
|
|
209
|
+
function defaultSphereFromScopes(scopes) {
|
|
210
|
+
if (scopes.some((s) => s.endsWith(".self")))
|
|
211
|
+
return "self";
|
|
212
|
+
if (scopes.some((s) => s.endsWith(".circle")))
|
|
213
|
+
return "circle";
|
|
214
|
+
return "public";
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Validate the SDK-side `compute` namespace and project it onto the
|
|
218
|
+
* snake_case shape the protocol layer canonicalizes into the mandate.
|
|
219
|
+
*
|
|
220
|
+
* Returns `null` if the caller passed nothing — that's the "no compute
|
|
221
|
+
* authorization" path. Throws {@link AithosSDKError} on any structural
|
|
222
|
+
* problem (camelCase mirror of the rules `validateComputeAuthorization`
|
|
223
|
+
* enforces server-side; we duplicate them here so a misuse fails at the
|
|
224
|
+
* SDK boundary with a precise error rather than only blowing up at mint).
|
|
225
|
+
*/
|
|
226
|
+
function projectCompute(c) {
|
|
227
|
+
if (c === undefined)
|
|
228
|
+
return null;
|
|
229
|
+
const hasDaily = typeof c.dailyCapMicrocredits === "number";
|
|
230
|
+
const hasTotal = typeof c.totalCapMicrocredits === "number";
|
|
231
|
+
if (!hasDaily && !hasTotal) {
|
|
232
|
+
throw new AithosSDKError("mandates_invalid_compute", "compute namespace requires at least one of dailyCapMicrocredits " +
|
|
233
|
+
"or totalCapMicrocredits — an unbounded compute mandate is a bearer " +
|
|
234
|
+
"token to drain the subject's wallet.");
|
|
235
|
+
}
|
|
236
|
+
for (const [field, value] of [
|
|
237
|
+
["dailyCapMicrocredits", c.dailyCapMicrocredits],
|
|
238
|
+
["totalCapMicrocredits", c.totalCapMicrocredits],
|
|
239
|
+
["maxCreditsPerCall", c.maxCreditsPerCall],
|
|
240
|
+
]) {
|
|
241
|
+
if (value === undefined)
|
|
242
|
+
continue;
|
|
243
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
244
|
+
throw new AithosSDKError("mandates_invalid_compute", `compute.${field} must be a positive integer (got ${value}).`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (c.allowedModels !== undefined) {
|
|
248
|
+
if (!Array.isArray(c.allowedModels)) {
|
|
249
|
+
throw new AithosSDKError("mandates_invalid_compute", "compute.allowedModels must be an array of strings.");
|
|
250
|
+
}
|
|
251
|
+
for (const m of c.allowedModels) {
|
|
252
|
+
if (typeof m !== "string" || m.length === 0) {
|
|
253
|
+
throw new AithosSDKError("mandates_invalid_compute", `compute.allowedModels entries must be non-empty strings (got ${JSON.stringify(m)}).`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const wire = {};
|
|
258
|
+
if (hasDaily)
|
|
259
|
+
wire.daily_cap_microcredits = c.dailyCapMicrocredits;
|
|
260
|
+
if (hasTotal)
|
|
261
|
+
wire.total_cap_microcredits = c.totalCapMicrocredits;
|
|
262
|
+
if (c.maxCreditsPerCall !== undefined)
|
|
263
|
+
wire.max_credits_per_call = c.maxCreditsPerCall;
|
|
264
|
+
if (c.allowedModels !== undefined)
|
|
265
|
+
wire.allowed_models = [...c.allowedModels];
|
|
266
|
+
return wire;
|
|
267
|
+
}
|
|
268
|
+
function toOwnedMandate(it) {
|
|
269
|
+
return {
|
|
270
|
+
mandateId: requireString(it, "mandate_id"),
|
|
271
|
+
issuerDid: requireString(it, "issuer_did"),
|
|
272
|
+
actorDid: requireString(it, "actor_did"),
|
|
273
|
+
scopes: filterScopeArray(it["scopes"]),
|
|
274
|
+
notBefore: typeof it["not_before"] === "number" ? it["not_before"] : null,
|
|
275
|
+
notAfter: typeof it["not_after"] === "number" ? it["not_after"] : null,
|
|
276
|
+
createdAt: typeof it["created_at"] === "number" ? it["created_at"] : 0,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function requireString(o, key) {
|
|
280
|
+
const v = o[key];
|
|
281
|
+
if (typeof v !== "string") {
|
|
282
|
+
throw new AithosSDKError("mandates_response_malformed", `list_mandates row missing string field ${key}`);
|
|
283
|
+
}
|
|
284
|
+
return v;
|
|
285
|
+
}
|
|
286
|
+
function filterScopeArray(v) {
|
|
287
|
+
if (!Array.isArray(v))
|
|
288
|
+
return [];
|
|
289
|
+
return v.filter((s) => typeof s === "string");
|
|
290
|
+
}
|
|
13
291
|
//# sourceMappingURL=mandates.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<AithosAsset>` — drop-in component for displaying Aithos-hosted
|
|
3
|
+
* binary content with the simplest possible API:
|
|
4
|
+
*
|
|
5
|
+
* <AithosAsset urn={cv.urn} alt="CV" className="w-full" />
|
|
6
|
+
* <AithosAsset urn={video.urn} as="video" controls />
|
|
7
|
+
* <AithosAsset urn={song.urn} as="audio" controls />
|
|
8
|
+
* <AithosAsset urn={cv.urn} as="download" filename="cv.pdf">
|
|
9
|
+
* Download my CV (PDF)
|
|
10
|
+
* </AithosAsset>
|
|
11
|
+
*
|
|
12
|
+
* The component handles the full lifecycle:
|
|
13
|
+
* - Fetch + decrypt via the assets client (from context or `client` prop).
|
|
14
|
+
* - Show `fallback` while loading.
|
|
15
|
+
* - Show `errorFallback` (or alt text in img-mode) on failure.
|
|
16
|
+
* - Revoke the `blob:` URL on unmount or URN change.
|
|
17
|
+
*
|
|
18
|
+
* For public assets attached to the Ethos public zone, you can also
|
|
19
|
+
* just use the stable CloudFront URL directly (`<img src={asset.url} />`).
|
|
20
|
+
* This component is meant for private regime assets that require
|
|
21
|
+
* client-side decryption.
|
|
22
|
+
*/
|
|
23
|
+
import type { ReactNode, ImgHTMLAttributes, VideoHTMLAttributes, AudioHTMLAttributes, AnchorHTMLAttributes } from "react";
|
|
24
|
+
import type { AssetsClient } from "../assets.js";
|
|
25
|
+
interface AithosAssetBaseProps {
|
|
26
|
+
/** Asset URN, e.g. `urn:aithos:asset:did:aithos:z6Mkr…:asset_01J…`. */
|
|
27
|
+
readonly urn: string;
|
|
28
|
+
/** Override the assets client from context. */
|
|
29
|
+
readonly client?: AssetsClient | null;
|
|
30
|
+
/** Rendered while the fetch+decrypt is in flight. */
|
|
31
|
+
readonly fallback?: ReactNode;
|
|
32
|
+
/** Rendered on fetch/decrypt failure. `as="img"` defaults to showing alt instead. */
|
|
33
|
+
readonly errorFallback?: ReactNode;
|
|
34
|
+
/** Called once the asset is decrypted and ready to display. */
|
|
35
|
+
readonly onLoad?: () => void;
|
|
36
|
+
/** Called when the fetch/decrypt fails. */
|
|
37
|
+
readonly onError?: (err: Error) => void;
|
|
38
|
+
/**
|
|
39
|
+
* If `true`, keep the previous asset visible while a new URN is
|
|
40
|
+
* being fetched, instead of flashing the fallback. Default `false`.
|
|
41
|
+
*/
|
|
42
|
+
readonly keepPreviousOnUrnChange?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface AithosImageProps extends AithosAssetBaseProps, Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "onLoad" | "onError"> {
|
|
45
|
+
readonly as?: "img";
|
|
46
|
+
}
|
|
47
|
+
export interface AithosVideoProps extends AithosAssetBaseProps, Omit<VideoHTMLAttributes<HTMLVideoElement>, "src" | "onLoad" | "onError"> {
|
|
48
|
+
readonly as: "video";
|
|
49
|
+
}
|
|
50
|
+
export interface AithosAudioProps extends AithosAssetBaseProps, Omit<AudioHTMLAttributes<HTMLAudioElement>, "src" | "onLoad" | "onError"> {
|
|
51
|
+
readonly as: "audio";
|
|
52
|
+
}
|
|
53
|
+
export interface AithosDownloadProps extends AithosAssetBaseProps, Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "onLoad" | "onError"> {
|
|
54
|
+
readonly as: "download";
|
|
55
|
+
/** Suggested filename in the browser's download dialog. */
|
|
56
|
+
readonly filename?: string;
|
|
57
|
+
readonly children?: ReactNode;
|
|
58
|
+
}
|
|
59
|
+
export type AithosAssetProps = AithosImageProps | AithosVideoProps | AithosAudioProps | AithosDownloadProps;
|
|
60
|
+
/**
|
|
61
|
+
* One component, four media kinds. The `as` prop selects between
|
|
62
|
+
* `<img>` (default), `<video>`, `<audio>`, and a styled `<a download>`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function AithosAsset(props: AithosAssetProps): ReactNode;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=AithosAsset.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { useAithosAsset } from "./use-aithos-asset.js";
|
|
4
|
+
/* -------------------------------------------------------------------------- */
|
|
5
|
+
/* Component */
|
|
6
|
+
/* -------------------------------------------------------------------------- */
|
|
7
|
+
/**
|
|
8
|
+
* One component, four media kinds. The `as` prop selects between
|
|
9
|
+
* `<img>` (default), `<video>`, `<audio>`, and a styled `<a download>`.
|
|
10
|
+
*/
|
|
11
|
+
export function AithosAsset(props) {
|
|
12
|
+
const { urn, client, fallback, errorFallback, onLoad, onError, keepPreviousOnUrnChange, ...rest } = props;
|
|
13
|
+
const state = useAithosAsset(urn, {
|
|
14
|
+
client: client ?? undefined,
|
|
15
|
+
keepPreviousOnUrnChange,
|
|
16
|
+
});
|
|
17
|
+
// onLoad / onError side-effects
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (state.url && onLoad)
|
|
20
|
+
onLoad();
|
|
21
|
+
}, [state.url, onLoad]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (state.error && onError)
|
|
24
|
+
onError(state.error);
|
|
25
|
+
}, [state.error, onError]);
|
|
26
|
+
// Loading
|
|
27
|
+
if (state.loading) {
|
|
28
|
+
return (fallback ?? null);
|
|
29
|
+
}
|
|
30
|
+
// Error
|
|
31
|
+
if (state.error) {
|
|
32
|
+
if (errorFallback !== undefined) {
|
|
33
|
+
return errorFallback;
|
|
34
|
+
}
|
|
35
|
+
// For img-mode, the alt attribute is a sensible default error
|
|
36
|
+
// surface (the browser renders the alt text when src is broken).
|
|
37
|
+
if (rest.as === undefined || rest.as === "img") {
|
|
38
|
+
const imgProps = rest;
|
|
39
|
+
// eslint-disable-next-line jsx-a11y/alt-text
|
|
40
|
+
return _jsx("img", { ...imgProps, src: undefined });
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
// No URL yet (no URN supplied → idle state)
|
|
45
|
+
if (!state.url) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
// Dispatch on `as`
|
|
49
|
+
const as = rest.as ?? "img";
|
|
50
|
+
const { as: _consumed, ...domProps } = rest;
|
|
51
|
+
void _consumed;
|
|
52
|
+
if (as === "img") {
|
|
53
|
+
return (_jsx("img", { ...domProps, src: state.url }));
|
|
54
|
+
}
|
|
55
|
+
if (as === "video") {
|
|
56
|
+
return (_jsx("video", { ...domProps, src: state.url }));
|
|
57
|
+
}
|
|
58
|
+
if (as === "audio") {
|
|
59
|
+
return (_jsx("audio", { ...domProps, src: state.url }));
|
|
60
|
+
}
|
|
61
|
+
if (as === "download") {
|
|
62
|
+
const { filename, children, ...anchorRest } = domProps;
|
|
63
|
+
return (_jsx("a", { ...anchorRest, href: state.url, download: filename ?? true, children: children }));
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=AithosAsset.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React context for the Aithos assets client.
|
|
3
|
+
*
|
|
4
|
+
* App roots wrap their tree with `<AssetsClientProvider client={sdk.assets}>`
|
|
5
|
+
* once, then child components use `<AithosAsset urn="..." />` (or the
|
|
6
|
+
* `useAithosAsset` hook) without having to thread the client through
|
|
7
|
+
* props.
|
|
8
|
+
*/
|
|
9
|
+
import { type ReactNode } from "react";
|
|
10
|
+
import type { AssetsClient } from "../assets.js";
|
|
11
|
+
export interface AssetsClientProviderProps {
|
|
12
|
+
/** The SDK's assets client — typically `sdk.assets`. */
|
|
13
|
+
readonly client: AssetsClient;
|
|
14
|
+
readonly children?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Provider that exposes an {@link AssetsClient} to descendants.
|
|
18
|
+
*
|
|
19
|
+
* Typical placement: as high in the React tree as the authenticated
|
|
20
|
+
* session lives (e.g. inside the auth boundary).
|
|
21
|
+
*/
|
|
22
|
+
export declare function AssetsClientProvider(props: AssetsClientProviderProps): any;
|
|
23
|
+
/**
|
|
24
|
+
* Return the {@link AssetsClient} from the nearest provider, or `null`
|
|
25
|
+
* if no provider is present. Components SHOULD accept an override via
|
|
26
|
+
* an explicit `client` prop and fall back to this hook when omitted.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useAssetsClient(): AssetsClient | null;
|
|
29
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Copyright 2026 Mathieu Colla
|
|
4
|
+
/**
|
|
5
|
+
* React context for the Aithos assets client.
|
|
6
|
+
*
|
|
7
|
+
* App roots wrap their tree with `<AssetsClientProvider client={sdk.assets}>`
|
|
8
|
+
* once, then child components use `<AithosAsset urn="..." />` (or the
|
|
9
|
+
* `useAithosAsset` hook) without having to thread the client through
|
|
10
|
+
* props.
|
|
11
|
+
*/
|
|
12
|
+
import { createContext, useContext } from "react";
|
|
13
|
+
const AssetsClientContext = createContext(null);
|
|
14
|
+
/**
|
|
15
|
+
* Provider that exposes an {@link AssetsClient} to descendants.
|
|
16
|
+
*
|
|
17
|
+
* Typical placement: as high in the React tree as the authenticated
|
|
18
|
+
* session lives (e.g. inside the auth boundary).
|
|
19
|
+
*/
|
|
20
|
+
export function AssetsClientProvider(props) {
|
|
21
|
+
return (_jsx(AssetsClientContext.Provider, { value: props.client, children: props.children }));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Return the {@link AssetsClient} from the nearest provider, or `null`
|
|
25
|
+
* if no provider is present. Components SHOULD accept an override via
|
|
26
|
+
* an explicit `client` prop and fall back to this hook when omitted.
|
|
27
|
+
*/
|
|
28
|
+
export function useAssetsClient() {
|
|
29
|
+
return useContext(AssetsClientContext);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@aithos/sdk/react` — React bindings for the Aithos SDK.
|
|
3
|
+
*
|
|
4
|
+
* Drop-in components and hooks for displaying Aithos-hosted assets in
|
|
5
|
+
* React applications. The package's only peer dependency beyond
|
|
6
|
+
* `@aithos/sdk` itself is `react` (^18 || ^19).
|
|
7
|
+
*
|
|
8
|
+
* Quick start:
|
|
9
|
+
*
|
|
10
|
+
* // 1. Wrap your tree once
|
|
11
|
+
* <AssetsClientProvider client={sdk.assets}>
|
|
12
|
+
* <App />
|
|
13
|
+
* </AssetsClientProvider>
|
|
14
|
+
*
|
|
15
|
+
* // 2. Use anywhere
|
|
16
|
+
* <AithosAsset urn={cv.urn} alt="CV" className="w-full" />
|
|
17
|
+
* <AithosAsset urn={video.urn} as="video" controls />
|
|
18
|
+
* <AithosAsset urn={pdf.urn} as="download" filename="cv.pdf">
|
|
19
|
+
* Download my CV
|
|
20
|
+
* </AithosAsset>
|
|
21
|
+
*
|
|
22
|
+
* // 3. Or use the hook directly for custom rendering
|
|
23
|
+
* const { url, bytes, loading, error } = useAithosAsset(urn);
|
|
24
|
+
*/
|
|
25
|
+
export { AssetsClientProvider, useAssetsClient, type AssetsClientProviderProps, } from "./context.js";
|
|
26
|
+
export { useAithosAsset, type UseAithosAssetState, type UseAithosAssetOptions, } from "./use-aithos-asset.js";
|
|
27
|
+
export { AithosAsset, type AithosAssetProps, type AithosImageProps, type AithosVideoProps, type AithosAudioProps, type AithosDownloadProps, } from "./AithosAsset.js";
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
/**
|
|
4
|
+
* `@aithos/sdk/react` — React bindings for the Aithos SDK.
|
|
5
|
+
*
|
|
6
|
+
* Drop-in components and hooks for displaying Aithos-hosted assets in
|
|
7
|
+
* React applications. The package's only peer dependency beyond
|
|
8
|
+
* `@aithos/sdk` itself is `react` (^18 || ^19).
|
|
9
|
+
*
|
|
10
|
+
* Quick start:
|
|
11
|
+
*
|
|
12
|
+
* // 1. Wrap your tree once
|
|
13
|
+
* <AssetsClientProvider client={sdk.assets}>
|
|
14
|
+
* <App />
|
|
15
|
+
* </AssetsClientProvider>
|
|
16
|
+
*
|
|
17
|
+
* // 2. Use anywhere
|
|
18
|
+
* <AithosAsset urn={cv.urn} alt="CV" className="w-full" />
|
|
19
|
+
* <AithosAsset urn={video.urn} as="video" controls />
|
|
20
|
+
* <AithosAsset urn={pdf.urn} as="download" filename="cv.pdf">
|
|
21
|
+
* Download my CV
|
|
22
|
+
* </AithosAsset>
|
|
23
|
+
*
|
|
24
|
+
* // 3. Or use the hook directly for custom rendering
|
|
25
|
+
* const { url, bytes, loading, error } = useAithosAsset(urn);
|
|
26
|
+
*/
|
|
27
|
+
export { AssetsClientProvider, useAssetsClient, } from "./context.js";
|
|
28
|
+
export { useAithosAsset, } from "./use-aithos-asset.js";
|
|
29
|
+
export { AithosAsset, } from "./AithosAsset.js";
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AssetsClient } from "../assets.js";
|
|
2
|
+
export interface UseAithosAssetState {
|
|
3
|
+
/** A `blob:` URL pointing at the decrypted plaintext. Use in <img src=…> etc. */
|
|
4
|
+
readonly url: string | null;
|
|
5
|
+
/** Raw decrypted bytes. Use when you need the binary itself (e.g. PDF viewer). */
|
|
6
|
+
readonly bytes: Uint8Array | null;
|
|
7
|
+
/** IANA media type — useful for routing rendering. */
|
|
8
|
+
readonly mediaType: string | null;
|
|
9
|
+
/** Plaintext size in bytes. */
|
|
10
|
+
readonly sizeBytes: number | null;
|
|
11
|
+
/** True while the fetch+decrypt is in flight. */
|
|
12
|
+
readonly loading: boolean;
|
|
13
|
+
/** Non-null when the fetch/decrypt failed. */
|
|
14
|
+
readonly error: Error | null;
|
|
15
|
+
}
|
|
16
|
+
export interface UseAithosAssetOptions {
|
|
17
|
+
/** Override the client from context. Useful for tests. */
|
|
18
|
+
readonly client?: AssetsClient | null;
|
|
19
|
+
/**
|
|
20
|
+
* If `true`, the hook keeps the previous blob URL visible while a
|
|
21
|
+
* new URN is being fetched, instead of flashing `null`. Default
|
|
22
|
+
* `false` (more predictable, less visual stutter avoidance).
|
|
23
|
+
*/
|
|
24
|
+
readonly keepPreviousOnUrnChange?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fetch + decrypt an Aithos asset and return a stable `blob:` URL.
|
|
28
|
+
*
|
|
29
|
+
* Lifecycle:
|
|
30
|
+
* - On mount or when `urn` changes, runs `client.fetch(urn)`.
|
|
31
|
+
* - On success, creates an object URL from the plaintext bytes.
|
|
32
|
+
* - On unmount or before refetching, revokes the URL to free memory.
|
|
33
|
+
* - Cancels in-flight fetches if the URN changes mid-flight.
|
|
34
|
+
*
|
|
35
|
+
* Throws (via `error`) when no client is available — supply one via
|
|
36
|
+
* `<AssetsClientProvider>` or the `client` option.
|
|
37
|
+
*/
|
|
38
|
+
export declare function useAithosAsset(urn: string | null | undefined, options?: UseAithosAssetOptions): UseAithosAssetState;
|
|
39
|
+
//# sourceMappingURL=use-aithos-asset.d.ts.map
|