@aithos/sdk 0.1.0-alpha.3 → 0.1.0-alpha.5
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/dist/src/auth-api.d.ts +41 -0
- package/dist/src/auth-api.js +82 -0
- package/dist/src/auth.d.ts +114 -75
- package/dist/src/auth.js +553 -73
- package/dist/src/compute.d.ts +8 -6
- package/dist/src/compute.js +19 -11
- package/dist/src/ethos.d.ts +117 -1
- package/dist/src/ethos.js +417 -16
- package/dist/src/index.d.ts +8 -4
- package/dist/src/index.js +26 -8
- package/dist/src/internal/delegate-bundle.d.ts +18 -0
- package/dist/src/internal/delegate-bundle.js +89 -0
- package/dist/src/internal/delegate-state.d.ts +45 -0
- package/dist/src/internal/delegate-state.js +120 -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 +88 -1
- package/dist/src/mandates.js +185 -8
- package/dist/src/sdk.d.ts +36 -3
- package/dist/src/sdk.js +27 -23
- package/dist/src/session-store.d.ts +58 -0
- package/dist/src/session-store.js +158 -0
- package/dist/src/wallet.d.ts +4 -6
- package/dist/src/wallet.js +18 -8
- package/dist/test/auth-j3.test.d.ts +2 -0
- package/dist/test/auth-j3.test.js +360 -0
- package/dist/test/compute.test.js +22 -11
- 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.test.d.ts +2 -0
- package/dist/test/mandates.test.js +93 -0
- package/dist/test/sdk.test.js +64 -30
- package/dist/test/signer.test.d.ts +2 -0
- package/dist/test/signer.test.js +117 -0
- package/dist/test/wallet.test.js +20 -9
- package/package.json +4 -3
package/dist/src/mandates.d.ts
CHANGED
|
@@ -1,2 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AithosAuth } from "./auth.js";
|
|
2
|
+
import type { AithosSdkEndpoints } from "./endpoints.js";
|
|
3
|
+
/** Capability scope the SDK accepts. Server-side ultimately decides. */
|
|
4
|
+
export type Scope = "ethos.read.public" | "ethos.read.circle" | "ethos.read.self" | "ethos.write.public" | "ethos.write.circle" | "ethos.write.self";
|
|
5
|
+
/**
|
|
6
|
+
* Which sphere of the owner signs the mandate. Bounds the upper-most
|
|
7
|
+
* scope set the mandate can carry.
|
|
8
|
+
*/
|
|
9
|
+
export type ActorSphere = "public" | "circle" | "self";
|
|
10
|
+
export interface CreateMandateInput {
|
|
11
|
+
/** Grantee URN — usually `urn:aithos:agent:<extension-id>` or similar. */
|
|
12
|
+
readonly granteeId: string;
|
|
13
|
+
/** Optional human-readable label for the grantee. */
|
|
14
|
+
readonly granteeLabel?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Sphere of the owner that issues the mandate. Defaults to the
|
|
17
|
+
* highest-numbered sphere covered by `scopes` (most permissive
|
|
18
|
+
* common ancestor): `"self"` if any scope ends in `.self`, else
|
|
19
|
+
* `"circle"` if any ends in `.circle`, else `"public"`.
|
|
20
|
+
*/
|
|
21
|
+
readonly actorSphere?: ActorSphere;
|
|
22
|
+
/** Capability set granted by the mandate. */
|
|
23
|
+
readonly scopes: readonly Scope[];
|
|
24
|
+
/** Lifetime in seconds. */
|
|
25
|
+
readonly ttlSeconds: number;
|
|
26
|
+
}
|
|
27
|
+
export interface MintedMandate {
|
|
28
|
+
/** Unique mandate id (matches `mandate.id` inside the bundle). */
|
|
29
|
+
readonly mandateId: string;
|
|
30
|
+
/** Subject DID — the owner who issued it. */
|
|
31
|
+
readonly subjectDid: string;
|
|
32
|
+
/** Grantee URN. */
|
|
33
|
+
readonly granteeId: string;
|
|
34
|
+
readonly scopes: readonly Scope[];
|
|
35
|
+
/** ISO-8601 (UTC) — `null` if the mandate has no `not_after`. */
|
|
36
|
+
readonly expiresAt: string | null;
|
|
37
|
+
/** Shareable `.aithos-delegate.json` Blob. Hand this to the grantee. */
|
|
38
|
+
readonly bundle: Blob;
|
|
39
|
+
/** Suggested filename for the bundle. */
|
|
40
|
+
readonly filename: string;
|
|
41
|
+
}
|
|
42
|
+
export interface OwnedMandate {
|
|
43
|
+
readonly mandateId: string;
|
|
44
|
+
readonly issuerDid: string;
|
|
45
|
+
readonly actorDid: string;
|
|
46
|
+
readonly scopes: readonly Scope[];
|
|
47
|
+
readonly notBefore: number | null;
|
|
48
|
+
readonly notAfter: number | null;
|
|
49
|
+
readonly createdAt: number;
|
|
50
|
+
}
|
|
51
|
+
export interface MandatesNamespaceDeps {
|
|
52
|
+
readonly auth: AithosAuth;
|
|
53
|
+
readonly endpoints: AithosSdkEndpoints;
|
|
54
|
+
readonly fetch: typeof fetch;
|
|
55
|
+
}
|
|
56
|
+
export declare class MandatesNamespace {
|
|
57
|
+
#private;
|
|
58
|
+
constructor(deps: MandatesNamespaceDeps);
|
|
59
|
+
/**
|
|
60
|
+
* Mint, sign, publish, and package a fresh delegate bundle. The
|
|
61
|
+
* grantee's keypair is generated inside this call and never
|
|
62
|
+
* persisted on the owner's machine — the seed flows out via the
|
|
63
|
+
* returned Blob and only via that Blob.
|
|
64
|
+
*/
|
|
65
|
+
create(input: CreateMandateInput): Promise<MintedMandate>;
|
|
66
|
+
/**
|
|
67
|
+
* List mandates issued by the signed-in owner. Pages through
|
|
68
|
+
* `aithos.list_mandates` until exhausted (or until 5 pages have
|
|
69
|
+
* been crawled — an owner with more than 1000 active mandates is
|
|
70
|
+
* out of scope today).
|
|
71
|
+
*/
|
|
72
|
+
list(): Promise<readonly OwnedMandate[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Publish a §4.2 revocation for `mandateId`. The mandate stops
|
|
75
|
+
* authorizing future actions (artifacts dated before `revoked_at`
|
|
76
|
+
* remain valid — revocation is not retroactive).
|
|
77
|
+
*
|
|
78
|
+
* Server-side: handled by `aithos.publish_revocation`. The envelope
|
|
79
|
+
* is signed by the owner's `#public` sphere — the spec also accepts
|
|
80
|
+
* `#root`, but `#public` is what the existing app does.
|
|
81
|
+
*
|
|
82
|
+
* Throws {@link AithosSDKError} on backend errors. Note that
|
|
83
|
+
* server-side support is in-flight; this method may surface a
|
|
84
|
+
* `mandates_-32601` (method not found) until the auth platform
|
|
85
|
+
* lands the corresponding write handler.
|
|
86
|
+
*/
|
|
87
|
+
revoke(mandateId: string): Promise<void>;
|
|
88
|
+
}
|
|
2
89
|
//# sourceMappingURL=mandates.d.ts.map
|
package/dist/src/mandates.js
CHANGED
|
@@ -1,13 +1,190 @@
|
|
|
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
|
+
export class MandatesNamespace {
|
|
20
|
+
#deps;
|
|
21
|
+
constructor(deps) {
|
|
22
|
+
this.#deps = deps;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Mint, sign, publish, and package a fresh delegate bundle. The
|
|
26
|
+
* grantee's keypair is generated inside this call and never
|
|
27
|
+
* persisted on the owner's machine — the seed flows out via the
|
|
28
|
+
* returned Blob and only via that Blob.
|
|
29
|
+
*/
|
|
30
|
+
async create(input) {
|
|
31
|
+
const owner = this.#requireOwner();
|
|
32
|
+
if (input.scopes.length === 0) {
|
|
33
|
+
throw new AithosSDKError("mandates_invalid_scopes", "scopes must be a non-empty list");
|
|
34
|
+
}
|
|
35
|
+
if (input.ttlSeconds <= 0) {
|
|
36
|
+
throw new AithosSDKError("mandates_invalid_ttl", "ttlSeconds must be > 0");
|
|
37
|
+
}
|
|
38
|
+
const actorSphere = input.actorSphere ?? defaultSphereFromScopes(input.scopes);
|
|
39
|
+
const ownerStored = owner._unsafeStoredIdentity();
|
|
40
|
+
const result = await mintDelegateBundle({
|
|
41
|
+
owner: ownerStored,
|
|
42
|
+
granteeId: input.granteeId,
|
|
43
|
+
...(input.granteeLabel ? { granteeLabel: input.granteeLabel } : {}),
|
|
44
|
+
actorSphere,
|
|
45
|
+
scopes: [...input.scopes],
|
|
46
|
+
ttlSeconds: input.ttlSeconds,
|
|
47
|
+
});
|
|
48
|
+
const mandate = result.mandate;
|
|
49
|
+
return {
|
|
50
|
+
mandateId: mandate.id,
|
|
51
|
+
subjectDid: mandate.subject_did,
|
|
52
|
+
granteeId: input.granteeId,
|
|
53
|
+
scopes: input.scopes,
|
|
54
|
+
expiresAt: mandate.not_after ?? null,
|
|
55
|
+
bundle: result.bundleBlob,
|
|
56
|
+
filename: `aithos-delegate-${mandate.id}.json`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* List mandates issued by the signed-in owner. Pages through
|
|
61
|
+
* `aithos.list_mandates` until exhausted (or until 5 pages have
|
|
62
|
+
* been crawled — an owner with more than 1000 active mandates is
|
|
63
|
+
* out of scope today).
|
|
64
|
+
*/
|
|
65
|
+
async list() {
|
|
66
|
+
const owner = this.#requireOwner();
|
|
67
|
+
const out = [];
|
|
68
|
+
let cursor;
|
|
69
|
+
for (let i = 0; i < 5; i++) {
|
|
70
|
+
const page = await readRpc("aithos.list_mandates", {
|
|
71
|
+
issuer_did: owner.did,
|
|
72
|
+
limit: 200,
|
|
73
|
+
...(cursor ? { cursor } : {}),
|
|
74
|
+
});
|
|
75
|
+
for (const it of page.items) {
|
|
76
|
+
out.push(toOwnedMandate(it));
|
|
77
|
+
}
|
|
78
|
+
if (!page.next_cursor)
|
|
79
|
+
break;
|
|
80
|
+
cursor = page.next_cursor;
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Publish a §4.2 revocation for `mandateId`. The mandate stops
|
|
86
|
+
* authorizing future actions (artifacts dated before `revoked_at`
|
|
87
|
+
* remain valid — revocation is not retroactive).
|
|
88
|
+
*
|
|
89
|
+
* Server-side: handled by `aithos.publish_revocation`. The envelope
|
|
90
|
+
* is signed by the owner's `#public` sphere — the spec also accepts
|
|
91
|
+
* `#root`, but `#public` is what the existing app does.
|
|
92
|
+
*
|
|
93
|
+
* Throws {@link AithosSDKError} on backend errors. Note that
|
|
94
|
+
* server-side support is in-flight; this method may surface a
|
|
95
|
+
* `mandates_-32601` (method not found) until the auth platform
|
|
96
|
+
* lands the corresponding write handler.
|
|
97
|
+
*/
|
|
98
|
+
async revoke(mandateId) {
|
|
99
|
+
const owner = this.#requireOwner();
|
|
100
|
+
const ownerStored = owner._unsafeStoredIdentity();
|
|
101
|
+
// TODO(post-alpha): once @aithos/protocol-client exposes a public
|
|
102
|
+
// configuration API for the `api` endpoint, route this through the
|
|
103
|
+
// same channel as `readRpc` / `publishZoneEdit` so self-hosters
|
|
104
|
+
// can override. For now, target the production write surface.
|
|
105
|
+
const url = "https://api.aithos.be/mcp/primitives/write";
|
|
106
|
+
const params = {
|
|
107
|
+
mandate_id: mandateId,
|
|
108
|
+
revoked_at: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
// Reach into the owner's #public signer for envelope signing via
|
|
111
|
+
// the centralized migration bridge (will go away when
|
|
112
|
+
// protocol-client accepts Signer-shaped objects).
|
|
113
|
+
const publicKp = ownerKeyPair(owner, "public");
|
|
114
|
+
const envelope = buildSignedEnvelope({
|
|
115
|
+
iss: ownerStored.did,
|
|
116
|
+
aud: url,
|
|
117
|
+
method: "aithos.publish_revocation",
|
|
118
|
+
verificationMethod: `${ownerStored.did}#public`,
|
|
119
|
+
params,
|
|
120
|
+
signer: publicKp,
|
|
121
|
+
});
|
|
122
|
+
let res;
|
|
123
|
+
try {
|
|
124
|
+
res = await this.#deps.fetch(url, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "content-type": "application/json" },
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
jsonrpc: "2.0",
|
|
129
|
+
id: "aithos.publish_revocation",
|
|
130
|
+
method: "aithos.publish_revocation",
|
|
131
|
+
params: { ...params, _envelope: envelope },
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
throw new AithosSDKError("network", e.message);
|
|
137
|
+
}
|
|
138
|
+
const body = (await res.json().catch(() => null));
|
|
139
|
+
if (!body) {
|
|
140
|
+
throw new AithosSDKError("http", `HTTP ${res.status} ${res.statusText} — non-JSON response`, { status: res.status });
|
|
141
|
+
}
|
|
142
|
+
if (body.error) {
|
|
143
|
+
throw new AithosSDKError(`mandates_${body.error.code}`, body.error.message, body.error.data ? { data: body.error.data } : undefined);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/* ------------------------------------------------------------------------ */
|
|
147
|
+
/* Internals */
|
|
148
|
+
/* ------------------------------------------------------------------------ */
|
|
149
|
+
#requireOwner() {
|
|
150
|
+
const owner = this.#deps.auth._getOwnerSigners();
|
|
151
|
+
if (!owner || owner.destroyed) {
|
|
152
|
+
throw new AithosSDKError("mandates_no_owner", "no owner signed in; sign in first");
|
|
153
|
+
}
|
|
154
|
+
return owner;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/* -------------------------------------------------------------------------- */
|
|
158
|
+
/* Helpers */
|
|
159
|
+
/* -------------------------------------------------------------------------- */
|
|
160
|
+
function defaultSphereFromScopes(scopes) {
|
|
161
|
+
if (scopes.some((s) => s.endsWith(".self")))
|
|
162
|
+
return "self";
|
|
163
|
+
if (scopes.some((s) => s.endsWith(".circle")))
|
|
164
|
+
return "circle";
|
|
165
|
+
return "public";
|
|
166
|
+
}
|
|
167
|
+
function toOwnedMandate(it) {
|
|
168
|
+
return {
|
|
169
|
+
mandateId: requireString(it, "mandate_id"),
|
|
170
|
+
issuerDid: requireString(it, "issuer_did"),
|
|
171
|
+
actorDid: requireString(it, "actor_did"),
|
|
172
|
+
scopes: filterScopeArray(it["scopes"]),
|
|
173
|
+
notBefore: typeof it["not_before"] === "number" ? it["not_before"] : null,
|
|
174
|
+
notAfter: typeof it["not_after"] === "number" ? it["not_after"] : null,
|
|
175
|
+
createdAt: typeof it["created_at"] === "number" ? it["created_at"] : 0,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function requireString(o, key) {
|
|
179
|
+
const v = o[key];
|
|
180
|
+
if (typeof v !== "string") {
|
|
181
|
+
throw new AithosSDKError("mandates_response_malformed", `list_mandates row missing string field ${key}`);
|
|
182
|
+
}
|
|
183
|
+
return v;
|
|
184
|
+
}
|
|
185
|
+
function filterScopeArray(v) {
|
|
186
|
+
if (!Array.isArray(v))
|
|
187
|
+
return [];
|
|
188
|
+
return v.filter((s) => typeof s === "string");
|
|
189
|
+
}
|
|
13
190
|
//# sourceMappingURL=mandates.js.map
|
package/dist/src/sdk.d.ts
CHANGED
|
@@ -1,18 +1,51 @@
|
|
|
1
|
+
import type { AithosAuth } from "./auth.js";
|
|
1
2
|
import { ComputeNamespace } from "./compute.js";
|
|
2
3
|
import { type AithosSdkEndpoints } from "./endpoints.js";
|
|
4
|
+
import { EthosNamespace } from "./ethos.js";
|
|
5
|
+
import { MandatesNamespace } from "./mandates.js";
|
|
3
6
|
import { WalletNamespace } from "./wallet.js";
|
|
4
|
-
|
|
7
|
+
export interface AithosSDKConfig {
|
|
8
|
+
/**
|
|
9
|
+
* The {@link AithosAuth} instance the SDK reads sign-in state from.
|
|
10
|
+
* Constructed and managed by the app — typically a singleton at the
|
|
11
|
+
* top of the application tree.
|
|
12
|
+
*/
|
|
13
|
+
readonly auth: AithosAuth;
|
|
14
|
+
/**
|
|
15
|
+
* Application DID — identifies the calling app in mandates, audit
|
|
16
|
+
* logs, billing splits. Issued at developer onboarding (will be
|
|
17
|
+
* self-service before 0.1.0 stable).
|
|
18
|
+
*/
|
|
19
|
+
readonly appDid: string;
|
|
20
|
+
/**
|
|
21
|
+
* Optional endpoint overrides. Production defaults point at the
|
|
22
|
+
* Aithos infrastructure; pass overrides for staging, self-hosting,
|
|
23
|
+
* tests.
|
|
24
|
+
*/
|
|
25
|
+
readonly endpoints?: Partial<AithosSdkEndpoints>;
|
|
26
|
+
/**
|
|
27
|
+
* Optional `fetch` implementation. Defaults to the global `fetch`.
|
|
28
|
+
* Used by tests to inject a mock without monkeypatching globals.
|
|
29
|
+
*/
|
|
30
|
+
readonly fetch?: typeof fetch;
|
|
31
|
+
}
|
|
5
32
|
export declare class AithosSDK {
|
|
6
33
|
/** Resolved endpoint configuration (defaults + caller overrides). */
|
|
7
34
|
readonly endpoints: AithosSdkEndpoints;
|
|
8
35
|
/** Application DID (as declared in mandates). */
|
|
9
36
|
readonly appDid: string;
|
|
10
|
-
/**
|
|
11
|
-
readonly
|
|
37
|
+
/** The same auth instance the app constructed and passed in. */
|
|
38
|
+
readonly auth: AithosAuth;
|
|
12
39
|
/** Compute proxy namespace — Bedrock invocation. */
|
|
13
40
|
readonly compute: ComputeNamespace;
|
|
14
41
|
/** Wallet namespace — Stripe top-up. */
|
|
15
42
|
readonly wallet: WalletNamespace;
|
|
43
|
+
/** Ethos editing namespace — load, mutate (staged), publish. */
|
|
44
|
+
readonly ethos: EthosNamespace;
|
|
45
|
+
/** Mandate lifecycle namespace — create / list / revoke. */
|
|
46
|
+
readonly mandates: MandatesNamespace;
|
|
16
47
|
constructor(config: AithosSDKConfig);
|
|
48
|
+
/** DID of the currently signed-in owner, or null if no owner is loaded. */
|
|
49
|
+
get userDid(): string | null;
|
|
17
50
|
}
|
|
18
51
|
//# sourceMappingURL=sdk.d.ts.map
|
package/dist/src/sdk.js
CHANGED
|
@@ -1,58 +1,62 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright 2026 Mathieu Colla
|
|
3
|
-
// AithosSDK — top-level developer surface.
|
|
4
|
-
//
|
|
5
|
-
// One construction, namespaced methods. The SDK takes the user's identity
|
|
6
|
-
// (a `BrowserIdentity` from `@aithos/protocol-client`) and the calling
|
|
7
|
-
// app's DID, then exposes:
|
|
8
|
-
//
|
|
9
|
-
// sdk.compute — Bedrock invocation through the compute proxy.
|
|
10
|
-
// sdk.wallet — Stripe Checkout for credit-pack top-ups.
|
|
11
|
-
// sdk.ethos — re-exports from protocol-client (zone editor, signers).
|
|
12
|
-
// sdk.onboarding / sdk.mandates — likewise.
|
|
13
|
-
//
|
|
14
|
-
// The class is intentionally thin: the heavy lifting lives in dedicated
|
|
15
|
-
// namespace classes (`ComputeNamespace`, `WalletNamespace`) so each can be
|
|
16
|
-
// tested in isolation with a mock fetch and so an advanced caller could
|
|
17
|
-
// instantiate just one of them if needed.
|
|
18
3
|
import { ComputeNamespace } from "./compute.js";
|
|
19
4
|
import { resolveEndpoints } from "./endpoints.js";
|
|
5
|
+
import { EthosNamespace } from "./ethos.js";
|
|
6
|
+
import { MandatesNamespace } from "./mandates.js";
|
|
20
7
|
import { WalletNamespace } from "./wallet.js";
|
|
21
8
|
export class AithosSDK {
|
|
22
9
|
/** Resolved endpoint configuration (defaults + caller overrides). */
|
|
23
10
|
endpoints;
|
|
24
11
|
/** Application DID (as declared in mandates). */
|
|
25
12
|
appDid;
|
|
26
|
-
/**
|
|
27
|
-
|
|
13
|
+
/** The same auth instance the app constructed and passed in. */
|
|
14
|
+
auth;
|
|
28
15
|
/** Compute proxy namespace — Bedrock invocation. */
|
|
29
16
|
compute;
|
|
30
17
|
/** Wallet namespace — Stripe top-up. */
|
|
31
18
|
wallet;
|
|
19
|
+
/** Ethos editing namespace — load, mutate (staged), publish. */
|
|
20
|
+
ethos;
|
|
21
|
+
/** Mandate lifecycle namespace — create / list / revoke. */
|
|
22
|
+
mandates;
|
|
32
23
|
constructor(config) {
|
|
33
|
-
if (!config.
|
|
34
|
-
throw new TypeError("AithosSDK: config.
|
|
24
|
+
if (!config.auth) {
|
|
25
|
+
throw new TypeError("AithosSDK: config.auth is required");
|
|
35
26
|
}
|
|
36
27
|
if (!config.appDid || typeof config.appDid !== "string") {
|
|
37
28
|
throw new TypeError("AithosSDK: config.appDid is required (string)");
|
|
38
29
|
}
|
|
39
30
|
this.endpoints = resolveEndpoints(config.endpoints);
|
|
40
31
|
this.appDid = config.appDid;
|
|
41
|
-
this.
|
|
32
|
+
this.auth = config.auth;
|
|
42
33
|
const fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
43
34
|
this.compute = new ComputeNamespace({
|
|
44
|
-
|
|
35
|
+
auth: config.auth,
|
|
45
36
|
appDid: config.appDid,
|
|
46
37
|
endpoints: this.endpoints,
|
|
47
38
|
fetch: fetchImpl,
|
|
48
39
|
});
|
|
49
40
|
this.wallet = new WalletNamespace({
|
|
50
|
-
|
|
41
|
+
auth: config.auth,
|
|
51
42
|
appDid: config.appDid,
|
|
52
|
-
userDid: config.identity.did,
|
|
53
43
|
endpoints: this.endpoints,
|
|
54
44
|
fetch: fetchImpl,
|
|
55
45
|
});
|
|
46
|
+
this.ethos = new EthosNamespace({
|
|
47
|
+
auth: config.auth,
|
|
48
|
+
endpoints: this.endpoints,
|
|
49
|
+
fetch: fetchImpl,
|
|
50
|
+
});
|
|
51
|
+
this.mandates = new MandatesNamespace({
|
|
52
|
+
auth: config.auth,
|
|
53
|
+
endpoints: this.endpoints,
|
|
54
|
+
fetch: fetchImpl,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** DID of the currently signed-in owner, or null if no owner is loaded. */
|
|
58
|
+
get userDid() {
|
|
59
|
+
return this.auth.getOwnerInfo()?.did ?? null;
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
//# sourceMappingURL=sdk.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AithosSession } from "./auth.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pluggable storage backend for the active session.
|
|
4
|
+
*
|
|
5
|
+
* Implementations should be **synchronous** : the SDK reads the store on
|
|
6
|
+
* boot to surface `getCurrentSession()` and async storage would force a
|
|
7
|
+
* Promise everywhere it's not warranted. Async backends should pre-warm
|
|
8
|
+
* the cache during the app's bootstrap and then implement the methods
|
|
9
|
+
* over that cache.
|
|
10
|
+
*
|
|
11
|
+
* `set` is called after every successful sign-in / sign-up / Google
|
|
12
|
+
* callback exchange. `clear` is called on `signOut`. `get` is called by
|
|
13
|
+
* `getCurrentSession()` and may also be called by the app on boot.
|
|
14
|
+
*/
|
|
15
|
+
export interface AithosSessionStore {
|
|
16
|
+
/** Read the persisted session. Return null if none, or if it's expired. */
|
|
17
|
+
get(): AithosSession | null;
|
|
18
|
+
/** Persist the session. Implementations should not throw — at worst
|
|
19
|
+
* they should warn and silently no-op (e.g. quota exceeded). */
|
|
20
|
+
set(session: AithosSession): void;
|
|
21
|
+
/** Wipe the persisted session. */
|
|
22
|
+
clear(): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Storage key used by the bundled stores. Apps that want to coexist with
|
|
26
|
+
* other Aithos-aware libs (or that want to scope sessions per-tenant) can
|
|
27
|
+
* pass a custom key via {@link sessionStorageStore} or
|
|
28
|
+
* {@link localStorageStore}.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_SESSION_STORAGE_KEY = "aithos.session.v1";
|
|
31
|
+
interface WebStorageStoreOptions {
|
|
32
|
+
/** Storage key. Defaults to {@link DEFAULT_SESSION_STORAGE_KEY}. */
|
|
33
|
+
readonly key?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Default web store : `sessionStorage`. The session lives until the tab
|
|
37
|
+
* is closed. Cleared on `signOut()`. Use this when reauthenticating each
|
|
38
|
+
* day is acceptable and reduces blast radius after an XSS.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sessionStorageStore(opts?: WebStorageStoreOptions): AithosSessionStore;
|
|
41
|
+
/**
|
|
42
|
+
* `localStorage` store. The session persists until the JWT expires or the
|
|
43
|
+
* user explicitly signs out. Higher convenience, larger XSS blast radius.
|
|
44
|
+
*/
|
|
45
|
+
export declare function localStorageStore(opts?: WebStorageStoreOptions): AithosSessionStore;
|
|
46
|
+
/**
|
|
47
|
+
* No-op store. `set` and `clear` discard their input ; `get` always
|
|
48
|
+
* returns null. The default in non-browser contexts (Node, edge runtimes)
|
|
49
|
+
* — apps running there should pass their own store explicitly.
|
|
50
|
+
*/
|
|
51
|
+
export declare function noopStore(): AithosSessionStore;
|
|
52
|
+
/**
|
|
53
|
+
* Pick a sensible default : `sessionStorage` if the browser environment
|
|
54
|
+
* is available, {@link noopStore} otherwise.
|
|
55
|
+
*/
|
|
56
|
+
export declare function defaultSessionStore(): AithosSessionStore;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
/* -------------------------------------------------------------------------- */
|
|
4
|
+
/* Storage key & expiration */
|
|
5
|
+
/* -------------------------------------------------------------------------- */
|
|
6
|
+
/**
|
|
7
|
+
* Storage key used by the bundled stores. Apps that want to coexist with
|
|
8
|
+
* other Aithos-aware libs (or that want to scope sessions per-tenant) can
|
|
9
|
+
* pass a custom key via {@link sessionStorageStore} or
|
|
10
|
+
* {@link localStorageStore}.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_SESSION_STORAGE_KEY = "aithos.session.v1";
|
|
13
|
+
/** Conservative buffer — drop the session 30 s before its `exp` so we
|
|
14
|
+
* don't hand out a token the server is about to reject. */
|
|
15
|
+
const SESSION_EXPIRY_BUFFER_S = 30;
|
|
16
|
+
function isExpired(session, nowSec) {
|
|
17
|
+
return session.exp <= nowSec + SESSION_EXPIRY_BUFFER_S;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validate at runtime that an opaque object looks like an `AithosSession`.
|
|
21
|
+
* Storage values come from JSON.parse over user-controlled data — we can't
|
|
22
|
+
* trust them blind. This isn't a security check (the server validates the
|
|
23
|
+
* JWT ; persistence layers don't authenticate themselves) ; it just
|
|
24
|
+
* prevents weird crashes when the storage was tampered with.
|
|
25
|
+
*/
|
|
26
|
+
function isSessionShaped(v) {
|
|
27
|
+
if (typeof v !== "object" || v === null)
|
|
28
|
+
return false;
|
|
29
|
+
const o = v;
|
|
30
|
+
return (typeof o["session"] === "string" &&
|
|
31
|
+
typeof o["exp"] === "number" &&
|
|
32
|
+
typeof o["did"] === "string" &&
|
|
33
|
+
typeof o["handle"] === "string");
|
|
34
|
+
}
|
|
35
|
+
function browserStorageStore(storageRef, opts = {}) {
|
|
36
|
+
const key = opts.key ?? DEFAULT_SESSION_STORAGE_KEY;
|
|
37
|
+
return {
|
|
38
|
+
get() {
|
|
39
|
+
const s = storageRef();
|
|
40
|
+
if (!s)
|
|
41
|
+
return null;
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = s.getItem(key);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (!raw)
|
|
50
|
+
return null;
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Corrupted entry — wipe to recover.
|
|
57
|
+
try {
|
|
58
|
+
s.removeItem(key);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* ignore */
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (!isSessionShaped(parsed))
|
|
66
|
+
return null;
|
|
67
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
68
|
+
if (isExpired(parsed, nowSec)) {
|
|
69
|
+
// Auto-evict — let the caller see "no session" and re-auth.
|
|
70
|
+
try {
|
|
71
|
+
s.removeItem(key);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* ignore */
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
},
|
|
80
|
+
set(session) {
|
|
81
|
+
const s = storageRef();
|
|
82
|
+
if (!s)
|
|
83
|
+
return;
|
|
84
|
+
try {
|
|
85
|
+
s.setItem(key, JSON.stringify(session));
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
// Quota exceeded, private mode, etc. — log but don't throw : the
|
|
89
|
+
// sign-in returned successfully, the in-memory session is still
|
|
90
|
+
// usable for this tab.
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.warn("[AithosAuth] failed to persist session:", e.message);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
clear() {
|
|
96
|
+
const s = storageRef();
|
|
97
|
+
if (!s)
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
s.removeItem(key);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* ignore */
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function safeStorage(getter) {
|
|
109
|
+
return () => {
|
|
110
|
+
try {
|
|
111
|
+
const s = getter();
|
|
112
|
+
return s ?? null;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Some restricted contexts (sandboxed iframes, file:// URLs) throw
|
|
116
|
+
// on access. Treat them as "no storage available".
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Default web store : `sessionStorage`. The session lives until the tab
|
|
123
|
+
* is closed. Cleared on `signOut()`. Use this when reauthenticating each
|
|
124
|
+
* day is acceptable and reduces blast radius after an XSS.
|
|
125
|
+
*/
|
|
126
|
+
export function sessionStorageStore(opts) {
|
|
127
|
+
return browserStorageStore(safeStorage(() => (typeof sessionStorage !== "undefined" ? sessionStorage : undefined)), opts);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* `localStorage` store. The session persists until the JWT expires or the
|
|
131
|
+
* user explicitly signs out. Higher convenience, larger XSS blast radius.
|
|
132
|
+
*/
|
|
133
|
+
export function localStorageStore(opts) {
|
|
134
|
+
return browserStorageStore(safeStorage(() => (typeof localStorage !== "undefined" ? localStorage : undefined)), opts);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* No-op store. `set` and `clear` discard their input ; `get` always
|
|
138
|
+
* returns null. The default in non-browser contexts (Node, edge runtimes)
|
|
139
|
+
* — apps running there should pass their own store explicitly.
|
|
140
|
+
*/
|
|
141
|
+
export function noopStore() {
|
|
142
|
+
return {
|
|
143
|
+
get: () => null,
|
|
144
|
+
set: () => { },
|
|
145
|
+
clear: () => { },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Pick a sensible default : `sessionStorage` if the browser environment
|
|
150
|
+
* is available, {@link noopStore} otherwise.
|
|
151
|
+
*/
|
|
152
|
+
export function defaultSessionStore() {
|
|
153
|
+
if (typeof sessionStorage !== "undefined") {
|
|
154
|
+
return sessionStorageStore();
|
|
155
|
+
}
|
|
156
|
+
return noopStore();
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=session-store.js.map
|
package/dist/src/wallet.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AithosAuth } from "./auth.js";
|
|
2
2
|
import { type AithosSdkEndpoints } from "./endpoints.js";
|
|
3
3
|
/**
|
|
4
4
|
* Canonical credit-pack identifiers. Pricing and microcredit amounts are
|
|
@@ -44,12 +44,10 @@ export interface GetBalanceResult {
|
|
|
44
44
|
readonly exists: boolean;
|
|
45
45
|
}
|
|
46
46
|
export interface WalletNamespaceDeps {
|
|
47
|
-
/**
|
|
48
|
-
readonly
|
|
47
|
+
/** Auth instance — the wallet reads the active owner DID + signing key from here. */
|
|
48
|
+
readonly auth: AithosAuth;
|
|
49
49
|
/** App DID — sent as audit attribution alongside the balance request. */
|
|
50
50
|
readonly appDid: string;
|
|
51
|
-
/** Pre-resolved DID convenience accessor (mirrors identity.did). */
|
|
52
|
-
readonly userDid: string;
|
|
53
51
|
readonly endpoints: AithosSdkEndpoints;
|
|
54
52
|
readonly fetch: typeof fetch;
|
|
55
53
|
}
|
|
@@ -64,7 +62,7 @@ export declare class WalletNamespace {
|
|
|
64
62
|
* hosted URL — the caller is responsible for redirecting the user (e.g.
|
|
65
63
|
* `window.location.href = result.checkoutUrl`).
|
|
66
64
|
*
|
|
67
|
-
* On success, the Stripe webhook will credit
|
|
65
|
+
* On success, the Stripe webhook will credit the user's wallet once the
|
|
68
66
|
* payment clears. Wallet balances are shared across all Aithos apps that
|
|
69
67
|
* use the same DID.
|
|
70
68
|
*/
|