@tinycloud/node-sdk 0.0.0-beta-20260401001229
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/LICENSE.md +320 -0
- package/dist/core.cjs +2445 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +1119 -0
- package/dist/core.d.ts +1119 -0
- package/dist/core.js +2429 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +19571 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +19597 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/core.js
ADDED
|
@@ -0,0 +1,2429 @@
|
|
|
1
|
+
// src/core.ts
|
|
2
|
+
import { TinyCloud as TinyCloud2 } from "@tinycloud/sdk-core";
|
|
3
|
+
import {
|
|
4
|
+
SilentNotificationHandler as SilentNotificationHandler2,
|
|
5
|
+
AutoApproveSpaceCreationHandler as AutoApproveSpaceCreationHandler2,
|
|
6
|
+
defaultSpaceCreationHandler
|
|
7
|
+
} from "@tinycloud/sdk-core";
|
|
8
|
+
|
|
9
|
+
// src/storage/MemorySessionStorage.ts
|
|
10
|
+
var MemorySessionStorage = class {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Save a session for an address.
|
|
16
|
+
*/
|
|
17
|
+
async save(address, session) {
|
|
18
|
+
const normalizedAddress = address.toLowerCase();
|
|
19
|
+
this.sessions.set(normalizedAddress, session);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load a session for an address.
|
|
23
|
+
*/
|
|
24
|
+
async load(address) {
|
|
25
|
+
const normalizedAddress = address.toLowerCase();
|
|
26
|
+
const session = this.sessions.get(normalizedAddress);
|
|
27
|
+
if (!session) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const expiresAt = new Date(session.expiresAt);
|
|
31
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
32
|
+
this.sessions.delete(normalizedAddress);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Clear a session for an address.
|
|
39
|
+
*/
|
|
40
|
+
async clear(address) {
|
|
41
|
+
const normalizedAddress = address.toLowerCase();
|
|
42
|
+
this.sessions.delete(normalizedAddress);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if a session exists for an address.
|
|
46
|
+
*/
|
|
47
|
+
exists(address) {
|
|
48
|
+
const normalizedAddress = address.toLowerCase();
|
|
49
|
+
const session = this.sessions.get(normalizedAddress);
|
|
50
|
+
if (!session) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const expiresAt = new Date(session.expiresAt);
|
|
54
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
55
|
+
this.sessions.delete(normalizedAddress);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Memory storage is always available.
|
|
62
|
+
*/
|
|
63
|
+
isAvailable() {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Clear all sessions.
|
|
68
|
+
*/
|
|
69
|
+
clearAll() {
|
|
70
|
+
this.sessions.clear();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the number of stored sessions.
|
|
74
|
+
*/
|
|
75
|
+
size() {
|
|
76
|
+
return this.sessions.size;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/storage/FileSessionStorage.ts
|
|
81
|
+
import { validatePersistedSessionData } from "@tinycloud/sdk-core";
|
|
82
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "fs";
|
|
83
|
+
import { join } from "path";
|
|
84
|
+
var FileSessionStorage = class {
|
|
85
|
+
/**
|
|
86
|
+
* Create a new FileSessionStorage.
|
|
87
|
+
*
|
|
88
|
+
* @param baseDir - Directory to store session files (default: ~/.tinycloud/sessions)
|
|
89
|
+
*/
|
|
90
|
+
constructor(baseDir) {
|
|
91
|
+
this.baseDir = baseDir || this.getDefaultDir();
|
|
92
|
+
this.ensureDirectoryExists();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get the default session storage directory.
|
|
96
|
+
*/
|
|
97
|
+
getDefaultDir() {
|
|
98
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
99
|
+
return join(home, ".tinycloud", "sessions");
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Ensure the storage directory exists.
|
|
103
|
+
*/
|
|
104
|
+
ensureDirectoryExists() {
|
|
105
|
+
if (!existsSync(this.baseDir)) {
|
|
106
|
+
mkdirSync(this.baseDir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the file path for an address.
|
|
111
|
+
*/
|
|
112
|
+
getFilePath(address) {
|
|
113
|
+
const normalizedAddress = address.toLowerCase();
|
|
114
|
+
const filename = `${normalizedAddress.replace("0x", "")}.json`;
|
|
115
|
+
return join(this.baseDir, filename);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Save a session for an address.
|
|
119
|
+
*/
|
|
120
|
+
async save(address, session) {
|
|
121
|
+
const filePath = this.getFilePath(address);
|
|
122
|
+
const data = JSON.stringify(session, null, 2);
|
|
123
|
+
writeFileSync(filePath, data, "utf-8");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Load a session for an address.
|
|
127
|
+
*/
|
|
128
|
+
async load(address) {
|
|
129
|
+
const filePath = this.getFilePath(address);
|
|
130
|
+
if (!existsSync(filePath)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const data = readFileSync(filePath, "utf-8");
|
|
135
|
+
const parsed = JSON.parse(data);
|
|
136
|
+
const validation = validatePersistedSessionData(parsed);
|
|
137
|
+
if (!validation.ok) {
|
|
138
|
+
console.warn(`Invalid session data for ${address}:`, validation.error.message);
|
|
139
|
+
unlinkSync(filePath);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const session = validation.data;
|
|
143
|
+
const expiresAt = new Date(session.expiresAt);
|
|
144
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
145
|
+
unlinkSync(filePath);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return session;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
try {
|
|
151
|
+
unlinkSync(filePath);
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Clear a session for an address.
|
|
159
|
+
*/
|
|
160
|
+
async clear(address) {
|
|
161
|
+
const filePath = this.getFilePath(address);
|
|
162
|
+
if (existsSync(filePath)) {
|
|
163
|
+
unlinkSync(filePath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if a session exists for an address.
|
|
168
|
+
*/
|
|
169
|
+
exists(address) {
|
|
170
|
+
const filePath = this.getFilePath(address);
|
|
171
|
+
if (!existsSync(filePath)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const data = readFileSync(filePath, "utf-8");
|
|
176
|
+
const session = JSON.parse(data);
|
|
177
|
+
const expiresAt = new Date(session.expiresAt);
|
|
178
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
179
|
+
unlinkSync(filePath);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if file system storage is available.
|
|
189
|
+
*/
|
|
190
|
+
isAvailable() {
|
|
191
|
+
try {
|
|
192
|
+
this.ensureDirectoryExists();
|
|
193
|
+
return existsSync(this.baseDir);
|
|
194
|
+
} catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/authorization/NodeUserAuthorization.ts
|
|
201
|
+
import {
|
|
202
|
+
fetchPeerId,
|
|
203
|
+
submitHostDelegation,
|
|
204
|
+
activateSessionWithHost,
|
|
205
|
+
checkNodeInfo,
|
|
206
|
+
AutoApproveSpaceCreationHandler
|
|
207
|
+
} from "@tinycloud/sdk-core";
|
|
208
|
+
|
|
209
|
+
// src/authorization/strategies.ts
|
|
210
|
+
var defaultSignStrategy = { type: "auto-sign" };
|
|
211
|
+
|
|
212
|
+
// src/authorization/NodeUserAuthorization.ts
|
|
213
|
+
var NodeUserAuthorization = class {
|
|
214
|
+
constructor(config) {
|
|
215
|
+
this.extensions = [];
|
|
216
|
+
this._nodeFeatures = [];
|
|
217
|
+
this.wasm = config.wasmBindings;
|
|
218
|
+
this.signer = config.signer;
|
|
219
|
+
this.signStrategy = config.signStrategy ?? defaultSignStrategy;
|
|
220
|
+
this.sessionStorage = config.sessionStorage ?? new MemorySessionStorage();
|
|
221
|
+
this.domain = config.domain;
|
|
222
|
+
this.uri = config.uri ?? `https://${config.domain}`;
|
|
223
|
+
this.statement = config.statement;
|
|
224
|
+
this.spacePrefix = config.spacePrefix ?? "default";
|
|
225
|
+
this.defaultActions = config.defaultActions ?? {
|
|
226
|
+
kv: {
|
|
227
|
+
"": [
|
|
228
|
+
"tinycloud.kv/put",
|
|
229
|
+
"tinycloud.kv/get",
|
|
230
|
+
"tinycloud.kv/del",
|
|
231
|
+
"tinycloud.kv/list",
|
|
232
|
+
"tinycloud.kv/metadata"
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
sql: {
|
|
236
|
+
"": [
|
|
237
|
+
"tinycloud.sql/read",
|
|
238
|
+
"tinycloud.sql/write",
|
|
239
|
+
"tinycloud.sql/admin",
|
|
240
|
+
"tinycloud.sql/export"
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
duckdb: {
|
|
244
|
+
"": [
|
|
245
|
+
"tinycloud.duckdb/read",
|
|
246
|
+
"tinycloud.duckdb/write",
|
|
247
|
+
"tinycloud.duckdb/admin",
|
|
248
|
+
"tinycloud.duckdb/describe",
|
|
249
|
+
"tinycloud.duckdb/export",
|
|
250
|
+
"tinycloud.duckdb/import",
|
|
251
|
+
"tinycloud.duckdb/execute"
|
|
252
|
+
]
|
|
253
|
+
},
|
|
254
|
+
capabilities: {
|
|
255
|
+
"": ["tinycloud.capabilities/read"]
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
this.sessionExpirationMs = config.sessionExpirationMs ?? 60 * 60 * 1e3;
|
|
259
|
+
this.autoCreateSpace = config.autoCreateSpace ?? false;
|
|
260
|
+
this.spaceCreationHandler = config.spaceCreationHandler;
|
|
261
|
+
this.tinycloudHosts = config.tinycloudHosts ?? ["https://node.tinycloud.xyz"];
|
|
262
|
+
this.enablePublicSpace = config.enablePublicSpace ?? true;
|
|
263
|
+
this.siweConfig = config.siweConfig;
|
|
264
|
+
this.sessionManager = this.wasm.createSessionManager();
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The current active session (web-core compatible).
|
|
268
|
+
*/
|
|
269
|
+
get session() {
|
|
270
|
+
return this._session;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The current TinyCloud session with full delegation data.
|
|
274
|
+
* Includes spaceId, delegationHeader, and delegationCid.
|
|
275
|
+
*/
|
|
276
|
+
get tinyCloudSession() {
|
|
277
|
+
return this._tinyCloudSession;
|
|
278
|
+
}
|
|
279
|
+
get nodeFeatures() {
|
|
280
|
+
return this._nodeFeatures;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Add an extension to the authorization flow.
|
|
284
|
+
*/
|
|
285
|
+
extend(extension) {
|
|
286
|
+
this.extensions.push(extension);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get the space ID for the current session.
|
|
290
|
+
*/
|
|
291
|
+
getSpaceId() {
|
|
292
|
+
return this._tinyCloudSession?.spaceId;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Create the space on the TinyCloud server (host delegation).
|
|
296
|
+
* This registers the user as the owner of the space.
|
|
297
|
+
*/
|
|
298
|
+
async hostSpace(targetSpaceId) {
|
|
299
|
+
if (!this._tinyCloudSession || !this._address || !this._chainId) {
|
|
300
|
+
throw new Error("Must be signed in to host space");
|
|
301
|
+
}
|
|
302
|
+
const host = this.tinycloudHosts[0];
|
|
303
|
+
const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
|
|
304
|
+
const peerId = await fetchPeerId(host, spaceId);
|
|
305
|
+
const siwe = this.wasm.generateHostSIWEMessage({
|
|
306
|
+
address: this._address,
|
|
307
|
+
chainId: this._chainId,
|
|
308
|
+
domain: this.domain,
|
|
309
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
310
|
+
spaceId,
|
|
311
|
+
peerId
|
|
312
|
+
});
|
|
313
|
+
const signature = await this.signMessage(siwe);
|
|
314
|
+
const headers = this.wasm.siweToDelegationHeaders({ siwe, signature });
|
|
315
|
+
const result = await submitHostDelegation(host, headers);
|
|
316
|
+
return result.success;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Create a specific space on the server via host delegation.
|
|
320
|
+
* Used for lazy creation of additional spaces (e.g., public).
|
|
321
|
+
*/
|
|
322
|
+
async hostPublicSpace(spaceId) {
|
|
323
|
+
return this.hostSpace(spaceId);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Ensure the user's space exists on the TinyCloud server.
|
|
327
|
+
* Creates the space if it doesn't exist and autoCreateSpace is enabled.
|
|
328
|
+
* If autoCreateSpace is false and space doesn't exist, silently returns
|
|
329
|
+
* (user may be using delegations to access other spaces).
|
|
330
|
+
*
|
|
331
|
+
* @throws Error if space creation fails
|
|
332
|
+
*/
|
|
333
|
+
async ensureSpaceExists() {
|
|
334
|
+
if (!this._tinyCloudSession) {
|
|
335
|
+
throw new Error("Must be signed in to ensure space exists");
|
|
336
|
+
}
|
|
337
|
+
const host = this.tinycloudHosts[0];
|
|
338
|
+
const primarySpaceId = this._tinyCloudSession.spaceId;
|
|
339
|
+
const result = await activateSessionWithHost(
|
|
340
|
+
host,
|
|
341
|
+
this._tinyCloudSession.delegationHeader
|
|
342
|
+
);
|
|
343
|
+
const handler = this.spaceCreationHandler ?? (this.autoCreateSpace ? new AutoApproveSpaceCreationHandler() : void 0);
|
|
344
|
+
const creationContext = {
|
|
345
|
+
spaceId: primarySpaceId,
|
|
346
|
+
address: this._address,
|
|
347
|
+
chainId: this._chainId,
|
|
348
|
+
host
|
|
349
|
+
};
|
|
350
|
+
if (result.success) {
|
|
351
|
+
const primarySkipped = result.skipped?.includes(primarySpaceId);
|
|
352
|
+
if (!primarySkipped) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (!handler) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const confirmed = await handler.confirmSpaceCreation(creationContext);
|
|
359
|
+
if (!confirmed) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const created = await this.hostSpace();
|
|
364
|
+
if (!created) {
|
|
365
|
+
const err = new Error(`Failed to create space: ${primarySpaceId}`);
|
|
366
|
+
handler.onSpaceCreationFailed?.(creationContext, err);
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
handler.onSpaceCreationFailed?.(creationContext, error instanceof Error ? error : new Error(String(error)));
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
374
|
+
const retryResult = await activateSessionWithHost(
|
|
375
|
+
host,
|
|
376
|
+
this._tinyCloudSession.delegationHeader
|
|
377
|
+
);
|
|
378
|
+
if (!retryResult.success) {
|
|
379
|
+
const err = new Error(
|
|
380
|
+
`Failed to activate session after creating space: ${retryResult.error}`
|
|
381
|
+
);
|
|
382
|
+
handler.onSpaceCreationFailed?.(creationContext, err);
|
|
383
|
+
throw err;
|
|
384
|
+
}
|
|
385
|
+
handler.onSpaceCreated?.(creationContext);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (result.status === 404) {
|
|
389
|
+
if (!handler) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const confirmed = await handler.confirmSpaceCreation(creationContext);
|
|
393
|
+
if (!confirmed) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const created = await this.hostSpace();
|
|
398
|
+
if (!created) {
|
|
399
|
+
const err = new Error(`Failed to create space: ${primarySpaceId}`);
|
|
400
|
+
handler.onSpaceCreationFailed?.(creationContext, err);
|
|
401
|
+
throw err;
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
handler.onSpaceCreationFailed?.(creationContext, error instanceof Error ? error : new Error(String(error)));
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
408
|
+
const retryResult = await activateSessionWithHost(
|
|
409
|
+
host,
|
|
410
|
+
this._tinyCloudSession.delegationHeader
|
|
411
|
+
);
|
|
412
|
+
if (!retryResult.success) {
|
|
413
|
+
const err = new Error(
|
|
414
|
+
`Failed to activate session after creating space: ${retryResult.error}`
|
|
415
|
+
);
|
|
416
|
+
handler.onSpaceCreationFailed?.(creationContext, err);
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
handler.onSpaceCreated?.(creationContext);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
throw new Error(`Failed to activate session: ${result.error}`);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Sign in and create a new session.
|
|
426
|
+
*
|
|
427
|
+
* This follows the correct SIWE-ReCap flow:
|
|
428
|
+
* 1. Create session key and get JWK
|
|
429
|
+
* 2. Call prepareSession() which generates the SIWE with ReCap capabilities
|
|
430
|
+
* 3. Sign the SIWE string from prepareSession
|
|
431
|
+
* 4. Call completeSessionSetup() with the prepared session + signature
|
|
432
|
+
*/
|
|
433
|
+
async signIn() {
|
|
434
|
+
this._address = await this.signer.getAddress();
|
|
435
|
+
this._chainId = await this.signer.getChainId();
|
|
436
|
+
const address = this.wasm.ensureEip55(this._address);
|
|
437
|
+
const chainId = this._chainId;
|
|
438
|
+
const keyId = `session-${Date.now()}`;
|
|
439
|
+
this.sessionManager.renameSessionKeyId("default", keyId);
|
|
440
|
+
const jwkString = this.sessionManager.jwk(keyId);
|
|
441
|
+
if (!jwkString) {
|
|
442
|
+
throw new Error("Failed to create session key");
|
|
443
|
+
}
|
|
444
|
+
const jwk = JSON.parse(jwkString);
|
|
445
|
+
const spaceId = this.wasm.makeSpaceId(address, chainId, this.spacePrefix);
|
|
446
|
+
const now = /* @__PURE__ */ new Date();
|
|
447
|
+
const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
|
|
448
|
+
const prepared = this.wasm.prepareSession({
|
|
449
|
+
abilities: this.defaultActions,
|
|
450
|
+
address,
|
|
451
|
+
chainId,
|
|
452
|
+
domain: this.domain,
|
|
453
|
+
issuedAt: now.toISOString(),
|
|
454
|
+
expirationTime: expirationTime.toISOString(),
|
|
455
|
+
spaceId,
|
|
456
|
+
jwk,
|
|
457
|
+
nonce: this.siweConfig?.nonce
|
|
458
|
+
});
|
|
459
|
+
const signature = await this.requestSignature({
|
|
460
|
+
address,
|
|
461
|
+
chainId,
|
|
462
|
+
message: prepared.siwe,
|
|
463
|
+
type: "siwe"
|
|
464
|
+
});
|
|
465
|
+
const session = this.wasm.completeSessionSetup({
|
|
466
|
+
...prepared,
|
|
467
|
+
signature
|
|
468
|
+
});
|
|
469
|
+
const clientSession = {
|
|
470
|
+
address,
|
|
471
|
+
walletAddress: address,
|
|
472
|
+
chainId,
|
|
473
|
+
sessionKey: keyId,
|
|
474
|
+
siwe: prepared.siwe,
|
|
475
|
+
signature
|
|
476
|
+
};
|
|
477
|
+
const spacesMetadata = this.enablePublicSpace ? { public: this.wasm.makeSpaceId(address, chainId, "public") } : void 0;
|
|
478
|
+
const tinyCloudSession = {
|
|
479
|
+
address,
|
|
480
|
+
chainId,
|
|
481
|
+
sessionKey: keyId,
|
|
482
|
+
spaceId,
|
|
483
|
+
spaces: spacesMetadata,
|
|
484
|
+
delegationCid: session.delegationCid,
|
|
485
|
+
delegationHeader: session.delegationHeader,
|
|
486
|
+
verificationMethod: this.sessionManager.getDID(keyId),
|
|
487
|
+
jwk,
|
|
488
|
+
siwe: prepared.siwe,
|
|
489
|
+
signature
|
|
490
|
+
};
|
|
491
|
+
const persistedData = {
|
|
492
|
+
address,
|
|
493
|
+
chainId,
|
|
494
|
+
sessionKey: JSON.stringify(jwk),
|
|
495
|
+
siwe: prepared.siwe,
|
|
496
|
+
signature,
|
|
497
|
+
tinycloudSession: {
|
|
498
|
+
delegationHeader: session.delegationHeader,
|
|
499
|
+
delegationCid: session.delegationCid,
|
|
500
|
+
spaceId,
|
|
501
|
+
spaces: spacesMetadata,
|
|
502
|
+
verificationMethod: this.sessionManager.getDID(keyId)
|
|
503
|
+
},
|
|
504
|
+
expiresAt: expirationTime.toISOString(),
|
|
505
|
+
createdAt: now.toISOString(),
|
|
506
|
+
version: "1.0"
|
|
507
|
+
};
|
|
508
|
+
await this.sessionStorage.save(address, persistedData);
|
|
509
|
+
this._session = clientSession;
|
|
510
|
+
this._tinyCloudSession = tinyCloudSession;
|
|
511
|
+
this._address = address;
|
|
512
|
+
this._chainId = chainId;
|
|
513
|
+
const nodeInfo = await checkNodeInfo(this.tinycloudHosts[0], this.wasm.protocolVersion());
|
|
514
|
+
this._nodeFeatures = nodeInfo.features;
|
|
515
|
+
for (const ext of this.extensions) {
|
|
516
|
+
if (ext.afterSignIn) {
|
|
517
|
+
await ext.afterSignIn(clientSession);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
await this.ensureSpaceExists();
|
|
521
|
+
return clientSession;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Sign out and clear the current session.
|
|
525
|
+
*/
|
|
526
|
+
async signOut() {
|
|
527
|
+
if (this._address) {
|
|
528
|
+
await this.clearPersistedSession(this._address);
|
|
529
|
+
}
|
|
530
|
+
this._session = void 0;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Get the current wallet/signer address.
|
|
534
|
+
*/
|
|
535
|
+
address() {
|
|
536
|
+
return this._address;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get the current chain ID.
|
|
540
|
+
*/
|
|
541
|
+
chainId() {
|
|
542
|
+
return this._chainId;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Sign a message with the connected signer.
|
|
546
|
+
*/
|
|
547
|
+
async signMessage(message) {
|
|
548
|
+
if (!this._address) {
|
|
549
|
+
this._address = await this.signer.getAddress();
|
|
550
|
+
}
|
|
551
|
+
if (!this._chainId) {
|
|
552
|
+
this._chainId = await this.signer.getChainId();
|
|
553
|
+
}
|
|
554
|
+
return this.requestSignature({
|
|
555
|
+
address: this._address,
|
|
556
|
+
chainId: this._chainId,
|
|
557
|
+
message,
|
|
558
|
+
type: "message"
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Prepare a session for external signing.
|
|
563
|
+
*
|
|
564
|
+
* Use this method when you need to sign the SIWE message externally (e.g., via
|
|
565
|
+
* a hardware wallet, multi-sig, or external service). After obtaining the signature,
|
|
566
|
+
* call `signInWithPreparedSession()` to complete the sign-in.
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```typescript
|
|
570
|
+
* const { prepared, keyId, jwk } = await auth.prepareSessionForSigning();
|
|
571
|
+
* const signature = await externalSigner.signMessage(prepared.siwe);
|
|
572
|
+
* const session = await auth.signInWithPreparedSession(prepared, signature, keyId, jwk);
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
async prepareSessionForSigning() {
|
|
576
|
+
const address = this.wasm.ensureEip55(await this.signer.getAddress());
|
|
577
|
+
const chainId = await this.signer.getChainId();
|
|
578
|
+
const keyId = `session-${Date.now()}`;
|
|
579
|
+
this.sessionManager.renameSessionKeyId("default", keyId);
|
|
580
|
+
const jwkString = this.sessionManager.jwk(keyId);
|
|
581
|
+
if (!jwkString) {
|
|
582
|
+
throw new Error("Failed to create session key");
|
|
583
|
+
}
|
|
584
|
+
const jwk = JSON.parse(jwkString);
|
|
585
|
+
const spaceId = this.wasm.makeSpaceId(address, chainId, this.spacePrefix);
|
|
586
|
+
const now = /* @__PURE__ */ new Date();
|
|
587
|
+
const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
|
|
588
|
+
const prepared = this.wasm.prepareSession({
|
|
589
|
+
abilities: this.defaultActions,
|
|
590
|
+
address,
|
|
591
|
+
chainId,
|
|
592
|
+
domain: this.domain,
|
|
593
|
+
issuedAt: now.toISOString(),
|
|
594
|
+
expirationTime: expirationTime.toISOString(),
|
|
595
|
+
spaceId,
|
|
596
|
+
jwk,
|
|
597
|
+
nonce: this.siweConfig?.nonce
|
|
598
|
+
});
|
|
599
|
+
return {
|
|
600
|
+
prepared,
|
|
601
|
+
keyId,
|
|
602
|
+
jwk,
|
|
603
|
+
address,
|
|
604
|
+
chainId
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Complete sign-in with a prepared session and signature.
|
|
609
|
+
*
|
|
610
|
+
* Use this method after obtaining a signature for the SIWE message from
|
|
611
|
+
* `prepareSessionForSigning()`. The signature MUST be over `prepared.siwe`.
|
|
612
|
+
*
|
|
613
|
+
* @param prepared - The prepared session from `prepareSessionForSigning()`
|
|
614
|
+
* @param signature - The signature over `prepared.siwe`
|
|
615
|
+
* @param keyId - The session key ID from `prepareSessionForSigning()`
|
|
616
|
+
* @param jwk - The JWK from `prepareSessionForSigning()`
|
|
617
|
+
*/
|
|
618
|
+
async signInWithPreparedSession(prepared, signature, keyId, jwk) {
|
|
619
|
+
const session = this.wasm.completeSessionSetup({
|
|
620
|
+
...prepared,
|
|
621
|
+
signature
|
|
622
|
+
});
|
|
623
|
+
const address = this.wasm.ensureEip55(await this.signer.getAddress());
|
|
624
|
+
const chainId = await this.signer.getChainId();
|
|
625
|
+
const clientSession = {
|
|
626
|
+
address,
|
|
627
|
+
walletAddress: address,
|
|
628
|
+
chainId,
|
|
629
|
+
sessionKey: keyId,
|
|
630
|
+
siwe: prepared.siwe,
|
|
631
|
+
signature
|
|
632
|
+
};
|
|
633
|
+
const spacesMetadata = this.enablePublicSpace ? { public: this.wasm.makeSpaceId(address, chainId, "public") } : void 0;
|
|
634
|
+
const tinyCloudSession = {
|
|
635
|
+
address,
|
|
636
|
+
chainId,
|
|
637
|
+
sessionKey: keyId,
|
|
638
|
+
spaceId: prepared.spaceId,
|
|
639
|
+
spaces: spacesMetadata,
|
|
640
|
+
delegationCid: session.delegationCid,
|
|
641
|
+
delegationHeader: session.delegationHeader,
|
|
642
|
+
verificationMethod: this.sessionManager.getDID(keyId),
|
|
643
|
+
jwk,
|
|
644
|
+
siwe: prepared.siwe,
|
|
645
|
+
signature
|
|
646
|
+
};
|
|
647
|
+
const expirationMatch = prepared.siwe.match(/Expiration Time: (.+)/);
|
|
648
|
+
const issuedAtMatch = prepared.siwe.match(/Issued At: (.+)/);
|
|
649
|
+
const expiresAt = expirationMatch?.[1] ?? new Date(Date.now() + this.sessionExpirationMs).toISOString();
|
|
650
|
+
const createdAt = issuedAtMatch?.[1] ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
651
|
+
const persistedData = {
|
|
652
|
+
address,
|
|
653
|
+
chainId,
|
|
654
|
+
sessionKey: JSON.stringify(jwk),
|
|
655
|
+
siwe: prepared.siwe,
|
|
656
|
+
signature,
|
|
657
|
+
tinycloudSession: {
|
|
658
|
+
delegationHeader: session.delegationHeader,
|
|
659
|
+
delegationCid: session.delegationCid,
|
|
660
|
+
spaceId: prepared.spaceId,
|
|
661
|
+
spaces: spacesMetadata,
|
|
662
|
+
verificationMethod: this.sessionManager.getDID(keyId)
|
|
663
|
+
},
|
|
664
|
+
expiresAt,
|
|
665
|
+
createdAt,
|
|
666
|
+
version: "1.0"
|
|
667
|
+
};
|
|
668
|
+
await this.sessionStorage.save(address, persistedData);
|
|
669
|
+
this._session = clientSession;
|
|
670
|
+
this._tinyCloudSession = tinyCloudSession;
|
|
671
|
+
this._address = address;
|
|
672
|
+
this._chainId = chainId;
|
|
673
|
+
const nodeInfo = await checkNodeInfo(this.tinycloudHosts[0], this.wasm.protocolVersion());
|
|
674
|
+
this._nodeFeatures = nodeInfo.features;
|
|
675
|
+
for (const ext of this.extensions) {
|
|
676
|
+
if (ext.afterSignIn) {
|
|
677
|
+
await ext.afterSignIn(clientSession);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
await this.ensureSpaceExists();
|
|
681
|
+
return clientSession;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Clear persisted session data.
|
|
685
|
+
*/
|
|
686
|
+
async clearPersistedSession(address) {
|
|
687
|
+
const targetAddress = address ?? this._address;
|
|
688
|
+
if (targetAddress) {
|
|
689
|
+
await this.sessionStorage.clear(targetAddress);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Check if a session is persisted for an address.
|
|
694
|
+
*/
|
|
695
|
+
isSessionPersisted(address) {
|
|
696
|
+
return this.sessionStorage.exists(address);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Request a signature based on the configured strategy.
|
|
700
|
+
*/
|
|
701
|
+
async requestSignature(request) {
|
|
702
|
+
switch (this.signStrategy.type) {
|
|
703
|
+
case "auto-sign":
|
|
704
|
+
return this.signer.signMessage(request.message);
|
|
705
|
+
case "auto-reject":
|
|
706
|
+
throw new Error("Sign request rejected by auto-reject strategy");
|
|
707
|
+
case "callback": {
|
|
708
|
+
const response = await this.signStrategy.handler(request);
|
|
709
|
+
if (!response.approved) {
|
|
710
|
+
throw new Error(
|
|
711
|
+
response.reason ?? "Sign request rejected by callback"
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
return response.signature ?? await this.signer.signMessage(request.message);
|
|
715
|
+
}
|
|
716
|
+
case "event-emitter": {
|
|
717
|
+
return this.requestSignatureViaEmitter(
|
|
718
|
+
request,
|
|
719
|
+
this.signStrategy.emitter,
|
|
720
|
+
this.signStrategy.timeout ?? 6e4
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
default:
|
|
724
|
+
throw new Error(`Unknown sign strategy: ${this.signStrategy.type}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Request signature via event emitter with timeout.
|
|
729
|
+
*/
|
|
730
|
+
requestSignatureViaEmitter(request, emitter, timeout) {
|
|
731
|
+
return new Promise((resolve, reject) => {
|
|
732
|
+
const timeoutId = setTimeout(() => {
|
|
733
|
+
reject(new Error("Sign request timed out"));
|
|
734
|
+
}, timeout);
|
|
735
|
+
const respond = async (response) => {
|
|
736
|
+
clearTimeout(timeoutId);
|
|
737
|
+
if (!response.approved) {
|
|
738
|
+
reject(
|
|
739
|
+
new Error(response.reason ?? "Sign request rejected via emitter")
|
|
740
|
+
);
|
|
741
|
+
} else {
|
|
742
|
+
const signature = response.signature ?? await this.signer.signMessage(request.message);
|
|
743
|
+
resolve(signature);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
emitter.emit("sign-request", request, respond);
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// src/TinyCloudNode.ts
|
|
752
|
+
import {
|
|
753
|
+
TinyCloud,
|
|
754
|
+
activateSessionWithHost as activateSessionWithHost2,
|
|
755
|
+
KVService as KVService2,
|
|
756
|
+
SQLService as SQLService2,
|
|
757
|
+
DuckDbService as DuckDbService2,
|
|
758
|
+
DataVaultService,
|
|
759
|
+
createVaultCrypto,
|
|
760
|
+
ServiceContext as ServiceContext2,
|
|
761
|
+
SilentNotificationHandler,
|
|
762
|
+
DelegationManager,
|
|
763
|
+
SpaceService,
|
|
764
|
+
CapabilityKeyRegistry,
|
|
765
|
+
SharingService,
|
|
766
|
+
UnsupportedFeatureError,
|
|
767
|
+
makePublicSpaceId
|
|
768
|
+
} from "@tinycloud/sdk-core";
|
|
769
|
+
|
|
770
|
+
// src/DelegatedAccess.ts
|
|
771
|
+
import {
|
|
772
|
+
KVService,
|
|
773
|
+
SQLService,
|
|
774
|
+
DuckDbService,
|
|
775
|
+
ServiceContext
|
|
776
|
+
} from "@tinycloud/sdk-core";
|
|
777
|
+
var DelegatedAccess = class {
|
|
778
|
+
constructor(session, delegation, host, invoke) {
|
|
779
|
+
this.session = session;
|
|
780
|
+
this._delegation = delegation;
|
|
781
|
+
this.host = host;
|
|
782
|
+
this._serviceContext = new ServiceContext({
|
|
783
|
+
invoke,
|
|
784
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
785
|
+
hosts: [host]
|
|
786
|
+
});
|
|
787
|
+
const prefix = this._delegation.path.replace(/\/$/, "");
|
|
788
|
+
this._kv = new KVService({ prefix });
|
|
789
|
+
this._kv.initialize(this._serviceContext);
|
|
790
|
+
this._serviceContext.registerService("kv", this._kv);
|
|
791
|
+
this._sql = new SQLService({});
|
|
792
|
+
this._sql.initialize(this._serviceContext);
|
|
793
|
+
this._serviceContext.registerService("sql", this._sql);
|
|
794
|
+
this._duckdb = new DuckDbService({});
|
|
795
|
+
this._duckdb.initialize(this._serviceContext);
|
|
796
|
+
this._serviceContext.registerService("duckdb", this._duckdb);
|
|
797
|
+
const serviceSession = {
|
|
798
|
+
delegationHeader: session.delegationHeader,
|
|
799
|
+
delegationCid: session.delegationCid,
|
|
800
|
+
spaceId: session.spaceId,
|
|
801
|
+
verificationMethod: session.verificationMethod,
|
|
802
|
+
jwk: session.jwk
|
|
803
|
+
};
|
|
804
|
+
this._serviceContext.setSession(serviceSession);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Get the delegation this access was created from.
|
|
808
|
+
*/
|
|
809
|
+
get delegation() {
|
|
810
|
+
return this._delegation;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* The space ID this access is for.
|
|
814
|
+
*/
|
|
815
|
+
get spaceId() {
|
|
816
|
+
return this._delegation.spaceId;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* The path this access is scoped to.
|
|
820
|
+
*/
|
|
821
|
+
get path() {
|
|
822
|
+
return this._delegation.path;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* KV operations on the delegated space.
|
|
826
|
+
*/
|
|
827
|
+
get kv() {
|
|
828
|
+
return this._kv;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* SQL operations on the delegated space.
|
|
832
|
+
*/
|
|
833
|
+
get sql() {
|
|
834
|
+
return this._sql;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* DuckDB operations on the delegated space.
|
|
838
|
+
*/
|
|
839
|
+
get duckdb() {
|
|
840
|
+
return this._duckdb;
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
// src/keys/WasmKeyProvider.ts
|
|
845
|
+
var WasmKeyProvider = class {
|
|
846
|
+
/**
|
|
847
|
+
* Create a new WasmKeyProvider.
|
|
848
|
+
*
|
|
849
|
+
* @param config - Configuration with the WASM session manager
|
|
850
|
+
*/
|
|
851
|
+
constructor(config) {
|
|
852
|
+
this.sessionManager = config.sessionManager;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Generate a new session key with the given name.
|
|
856
|
+
*
|
|
857
|
+
* This creates a new Ed25519 key pair in the WASM session manager.
|
|
858
|
+
* The key can then be used for signing delegations in sharing links.
|
|
859
|
+
*
|
|
860
|
+
* @param name - A unique name/ID for the key (e.g., "share:timestamp:random")
|
|
861
|
+
* @returns The key ID (same as the name provided)
|
|
862
|
+
*/
|
|
863
|
+
async createSessionKey(name) {
|
|
864
|
+
return this.sessionManager.createSessionKey(name);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Get the JWK (JSON Web Key) for a key.
|
|
868
|
+
*
|
|
869
|
+
* Returns the full JWK including the private key (d parameter),
|
|
870
|
+
* which is required for signing and for embedding in sharing links.
|
|
871
|
+
*
|
|
872
|
+
* @param keyId - The key ID to retrieve
|
|
873
|
+
* @returns The JWK object with public and private key components
|
|
874
|
+
* @throws Error if the key is not found
|
|
875
|
+
*/
|
|
876
|
+
getJWK(keyId) {
|
|
877
|
+
const jwkJson = this.sessionManager.jwk(keyId);
|
|
878
|
+
if (!jwkJson) {
|
|
879
|
+
throw new Error(`Key not found: ${keyId}`);
|
|
880
|
+
}
|
|
881
|
+
return JSON.parse(jwkJson);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Get the DID (Decentralized Identifier) for a key.
|
|
885
|
+
*
|
|
886
|
+
* Returns the did:key format DID derived from the key's public key.
|
|
887
|
+
* This DID can be used as the delegatee in delegations.
|
|
888
|
+
*
|
|
889
|
+
* @param keyId - The key ID to retrieve
|
|
890
|
+
* @returns The DID in did:key format (e.g., "did:key:z6Mk...")
|
|
891
|
+
*/
|
|
892
|
+
async getDID(keyId) {
|
|
893
|
+
return this.sessionManager.getDID(keyId);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* List all session keys currently held by the provider.
|
|
897
|
+
*
|
|
898
|
+
* @returns Array of key IDs
|
|
899
|
+
*/
|
|
900
|
+
listKeys() {
|
|
901
|
+
const keys = this.sessionManager.listSessionKeys?.();
|
|
902
|
+
return Array.isArray(keys) ? keys : [];
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Check if a key exists in the provider.
|
|
906
|
+
*
|
|
907
|
+
* @param keyId - The key ID to check
|
|
908
|
+
* @returns True if the key exists
|
|
909
|
+
*/
|
|
910
|
+
hasKey(keyId) {
|
|
911
|
+
const jwk = this.sessionManager.jwk(keyId);
|
|
912
|
+
return jwk !== void 0;
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
function createWasmKeyProvider(sessionManager) {
|
|
916
|
+
return new WasmKeyProvider({ sessionManager });
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/TinyCloudNode.ts
|
|
920
|
+
var DEFAULT_HOST = "https://node.tinycloud.xyz";
|
|
921
|
+
var TinyCloudNode = class _TinyCloudNode {
|
|
922
|
+
/**
|
|
923
|
+
* Create a new TinyCloudNode instance.
|
|
924
|
+
*
|
|
925
|
+
* All configuration is optional. Without a privateKey, the instance operates
|
|
926
|
+
* in "session-only" mode where it can receive delegations but cannot create
|
|
927
|
+
* its own space via signIn().
|
|
928
|
+
*
|
|
929
|
+
* @param config - Configuration options (all optional)
|
|
930
|
+
*
|
|
931
|
+
* @example
|
|
932
|
+
* ```typescript
|
|
933
|
+
* // Session-only mode - can receive delegations
|
|
934
|
+
* const bob = new TinyCloudNode();
|
|
935
|
+
* console.log(bob.did); // did:key:z6Mk... - available immediately
|
|
936
|
+
*
|
|
937
|
+
* // Wallet mode - can create own space
|
|
938
|
+
* const alice = new TinyCloudNode({
|
|
939
|
+
* privateKey: process.env.ALICE_PRIVATE_KEY,
|
|
940
|
+
* prefix: "myapp",
|
|
941
|
+
* });
|
|
942
|
+
* await alice.signIn();
|
|
943
|
+
* ```
|
|
944
|
+
*/
|
|
945
|
+
constructor(config = {}) {
|
|
946
|
+
this.signer = null;
|
|
947
|
+
this.auth = null;
|
|
948
|
+
this.tc = null;
|
|
949
|
+
this._chainId = 1;
|
|
950
|
+
this.config = {
|
|
951
|
+
...config,
|
|
952
|
+
host: config.host ?? DEFAULT_HOST
|
|
953
|
+
};
|
|
954
|
+
if (config.wasmBindings) {
|
|
955
|
+
this.wasmBindings = config.wasmBindings;
|
|
956
|
+
} else if (_TinyCloudNode.nodeDefaults) {
|
|
957
|
+
this.wasmBindings = _TinyCloudNode.nodeDefaults.createWasmBindings();
|
|
958
|
+
} else {
|
|
959
|
+
throw new Error(
|
|
960
|
+
"wasmBindings must be provided in config. Import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
this.sessionManager = this.wasmBindings.createSessionManager();
|
|
964
|
+
const defaultKeyId = "default";
|
|
965
|
+
let jwkStr = this.sessionManager.jwk(defaultKeyId);
|
|
966
|
+
if (jwkStr) {
|
|
967
|
+
this.sessionKeyId = defaultKeyId;
|
|
968
|
+
} else {
|
|
969
|
+
this.sessionKeyId = this.sessionManager.createSessionKey(defaultKeyId);
|
|
970
|
+
jwkStr = this.sessionManager.jwk(this.sessionKeyId);
|
|
971
|
+
}
|
|
972
|
+
if (!jwkStr) {
|
|
973
|
+
throw new Error("Failed to get session key JWK");
|
|
974
|
+
}
|
|
975
|
+
this.sessionKeyJwk = JSON.parse(jwkStr);
|
|
976
|
+
this._capabilityRegistry = new CapabilityKeyRegistry();
|
|
977
|
+
this._keyProvider = new WasmKeyProvider({
|
|
978
|
+
sessionManager: this.sessionManager
|
|
979
|
+
});
|
|
980
|
+
this.notificationHandler = config.notificationHandler ?? new SilentNotificationHandler();
|
|
981
|
+
this._sharingService = new SharingService({
|
|
982
|
+
hosts: [this.config.host],
|
|
983
|
+
// session: undefined - not needed for receive()
|
|
984
|
+
invoke: this.wasmBindings.invoke,
|
|
985
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
986
|
+
keyProvider: this._keyProvider,
|
|
987
|
+
registry: this._capabilityRegistry,
|
|
988
|
+
// delegationManager: undefined - not needed for receive()
|
|
989
|
+
createKVService: (config2) => {
|
|
990
|
+
const prefix = config2.pathPrefix?.replace(/\/$/, "");
|
|
991
|
+
const kvService = new KVService2({ prefix });
|
|
992
|
+
const kvContext = new ServiceContext2({
|
|
993
|
+
invoke: config2.invoke,
|
|
994
|
+
fetch: config2.fetch ?? globalThis.fetch.bind(globalThis),
|
|
995
|
+
hosts: config2.hosts
|
|
996
|
+
});
|
|
997
|
+
kvContext.setSession(config2.session);
|
|
998
|
+
kvService.initialize(kvContext);
|
|
999
|
+
return kvService;
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
if (config.signer) {
|
|
1003
|
+
this.signer = config.signer;
|
|
1004
|
+
this.setupAuth(config);
|
|
1005
|
+
} else if (config.privateKey) {
|
|
1006
|
+
if (!_TinyCloudNode.nodeDefaults) {
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
"privateKey requires PrivateKeySigner. Either provide a signer in config, or import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
this.signer = _TinyCloudNode.nodeDefaults.createSigner(config.privateKey, this._chainId);
|
|
1012
|
+
this.setupAuth(config);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
/** @internal Register Node.js-specific defaults (NodeWasmBindings, PrivateKeySigner) */
|
|
1016
|
+
static registerNodeDefaults(defaults) {
|
|
1017
|
+
_TinyCloudNode.nodeDefaults = defaults;
|
|
1018
|
+
}
|
|
1019
|
+
get nodeFeatures() {
|
|
1020
|
+
return this.auth?.nodeFeatures ?? [];
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Set up authorization handler and TinyCloud instance.
|
|
1024
|
+
* @internal
|
|
1025
|
+
*/
|
|
1026
|
+
setupAuth(config) {
|
|
1027
|
+
const host = this.config.host;
|
|
1028
|
+
const domain = config.domain ?? new URL(host).hostname;
|
|
1029
|
+
this.auth = new NodeUserAuthorization({
|
|
1030
|
+
signer: this.signer,
|
|
1031
|
+
signStrategy: { type: "auto-sign" },
|
|
1032
|
+
wasmBindings: this.wasmBindings,
|
|
1033
|
+
sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
|
|
1034
|
+
domain,
|
|
1035
|
+
spacePrefix: config.prefix,
|
|
1036
|
+
sessionExpirationMs: config.sessionExpirationMs ?? 60 * 60 * 1e3,
|
|
1037
|
+
tinycloudHosts: [host],
|
|
1038
|
+
autoCreateSpace: config.autoCreateSpace,
|
|
1039
|
+
enablePublicSpace: config.enablePublicSpace ?? true,
|
|
1040
|
+
spaceCreationHandler: config.spaceCreationHandler,
|
|
1041
|
+
siweConfig: config.siweConfig
|
|
1042
|
+
});
|
|
1043
|
+
this.tc = new TinyCloud(this.auth);
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Get the primary identity DID for this user.
|
|
1047
|
+
* - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
|
|
1048
|
+
* - If session-only mode: returns session key DID (did:key:z6Mk...)
|
|
1049
|
+
*
|
|
1050
|
+
* Use this for delegations - it always returns the appropriate identity.
|
|
1051
|
+
*/
|
|
1052
|
+
get did() {
|
|
1053
|
+
if (this._address) {
|
|
1054
|
+
return `did:pkh:eip155:${this._chainId}:${this._address}`;
|
|
1055
|
+
}
|
|
1056
|
+
return this.sessionManager.getDID(this.sessionKeyId);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Get the session key DID. Always available.
|
|
1060
|
+
* Format: did:key:z6Mk...#z6Mk...
|
|
1061
|
+
*
|
|
1062
|
+
* Use this when you specifically need the session key, not the user identity.
|
|
1063
|
+
*/
|
|
1064
|
+
get sessionDid() {
|
|
1065
|
+
return this.sessionManager.getDID(this.sessionKeyId);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Get the Ethereum address for this user.
|
|
1069
|
+
*/
|
|
1070
|
+
get address() {
|
|
1071
|
+
return this._address;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Check if this instance is in session-only mode (no wallet).
|
|
1075
|
+
* In session-only mode, the instance can receive delegations but cannot
|
|
1076
|
+
* create its own space via signIn().
|
|
1077
|
+
*/
|
|
1078
|
+
get isSessionOnly() {
|
|
1079
|
+
return this.signer === null;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Get the space ID for this user.
|
|
1083
|
+
* Available after signIn().
|
|
1084
|
+
*/
|
|
1085
|
+
get spaceId() {
|
|
1086
|
+
return this.auth?.tinyCloudSession?.spaceId;
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Get the current TinyCloud session.
|
|
1090
|
+
* Available after signIn().
|
|
1091
|
+
*/
|
|
1092
|
+
get session() {
|
|
1093
|
+
return this.auth?.tinyCloudSession;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Sign in and create a new session.
|
|
1097
|
+
* This creates the user's space if it doesn't exist.
|
|
1098
|
+
* Requires wallet mode (privateKey in config).
|
|
1099
|
+
*/
|
|
1100
|
+
async signIn() {
|
|
1101
|
+
if (!this.signer || !this.tc) {
|
|
1102
|
+
throw new Error(
|
|
1103
|
+
"Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
await this.wasmBindings.ensureInitialized?.();
|
|
1107
|
+
this._address = await this.signer.getAddress();
|
|
1108
|
+
this._chainId = await this.signer.getChainId();
|
|
1109
|
+
this._kv = void 0;
|
|
1110
|
+
this._sql = void 0;
|
|
1111
|
+
this._duckdb = void 0;
|
|
1112
|
+
this._serviceContext = void 0;
|
|
1113
|
+
await this.tc.signIn();
|
|
1114
|
+
this.initializeServices();
|
|
1115
|
+
this.notificationHandler.success("Successfully signed in");
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Restore a previously established session from stored delegation data.
|
|
1119
|
+
*
|
|
1120
|
+
* This is used by the CLI to restore a session that was created via the
|
|
1121
|
+
* browser-based delegation flow (OpenKey `/delegate` page). Instead of
|
|
1122
|
+
* signing in with a private key, it injects the delegation data directly.
|
|
1123
|
+
*
|
|
1124
|
+
* @param sessionData - The stored delegation data from the browser flow
|
|
1125
|
+
*/
|
|
1126
|
+
async restoreSession(sessionData) {
|
|
1127
|
+
await this.wasmBindings.ensureInitialized?.();
|
|
1128
|
+
this._kv = void 0;
|
|
1129
|
+
this._sql = void 0;
|
|
1130
|
+
this._duckdb = void 0;
|
|
1131
|
+
this._serviceContext = void 0;
|
|
1132
|
+
if (sessionData.address) {
|
|
1133
|
+
this._address = sessionData.address;
|
|
1134
|
+
}
|
|
1135
|
+
if (sessionData.chainId) {
|
|
1136
|
+
this._chainId = sessionData.chainId;
|
|
1137
|
+
}
|
|
1138
|
+
this._serviceContext = new ServiceContext2({
|
|
1139
|
+
invoke: this.wasmBindings.invoke,
|
|
1140
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
1141
|
+
hosts: [this.config.host]
|
|
1142
|
+
});
|
|
1143
|
+
this._kv = new KVService2({});
|
|
1144
|
+
this._kv.initialize(this._serviceContext);
|
|
1145
|
+
this._serviceContext.registerService("kv", this._kv);
|
|
1146
|
+
this._sql = new SQLService2({});
|
|
1147
|
+
this._sql.initialize(this._serviceContext);
|
|
1148
|
+
this._serviceContext.registerService("sql", this._sql);
|
|
1149
|
+
this._duckdb = new DuckDbService2({});
|
|
1150
|
+
this._duckdb.initialize(this._serviceContext);
|
|
1151
|
+
this._serviceContext.registerService("duckdb", this._duckdb);
|
|
1152
|
+
const serviceSession = {
|
|
1153
|
+
delegationHeader: sessionData.delegationHeader,
|
|
1154
|
+
delegationCid: sessionData.delegationCid,
|
|
1155
|
+
spaceId: sessionData.spaceId,
|
|
1156
|
+
verificationMethod: sessionData.verificationMethod,
|
|
1157
|
+
jwk: sessionData.jwk
|
|
1158
|
+
};
|
|
1159
|
+
this._serviceContext.setSession(serviceSession);
|
|
1160
|
+
const wasm = this.wasmBindings;
|
|
1161
|
+
const vaultCrypto = createVaultCrypto({
|
|
1162
|
+
vault_encrypt: wasm.vault_encrypt,
|
|
1163
|
+
vault_decrypt: wasm.vault_decrypt,
|
|
1164
|
+
vault_derive_key: wasm.vault_derive_key,
|
|
1165
|
+
vault_x25519_from_seed: wasm.vault_x25519_from_seed,
|
|
1166
|
+
vault_x25519_dh: wasm.vault_x25519_dh,
|
|
1167
|
+
vault_random_bytes: wasm.vault_random_bytes,
|
|
1168
|
+
vault_sha256: wasm.vault_sha256
|
|
1169
|
+
});
|
|
1170
|
+
const self = this;
|
|
1171
|
+
this._vault = new DataVaultService({
|
|
1172
|
+
spaceId: sessionData.spaceId,
|
|
1173
|
+
crypto: vaultCrypto,
|
|
1174
|
+
tc: {
|
|
1175
|
+
kv: this._kv,
|
|
1176
|
+
ensurePublicSpace: async () => {
|
|
1177
|
+
try {
|
|
1178
|
+
await self.ensurePublicSpace();
|
|
1179
|
+
return { ok: true, data: void 0 };
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
return { ok: false, error: { code: "STORAGE_ERROR", message: error instanceof Error ? error.message : String(error), service: "vault" } };
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
get publicKV() {
|
|
1185
|
+
return self._publicKV ?? self.tc.publicKV;
|
|
1186
|
+
},
|
|
1187
|
+
readPublicSpace: (host, spaceId, key) => TinyCloud.readPublicSpace(host, spaceId, key),
|
|
1188
|
+
makePublicSpaceId: TinyCloud.makePublicSpaceId,
|
|
1189
|
+
did: this.did,
|
|
1190
|
+
address: sessionData.address ?? this._address ?? "",
|
|
1191
|
+
chainId: sessionData.chainId ?? this._chainId,
|
|
1192
|
+
hosts: [this.config.host]
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
this._vault.initialize(this._serviceContext);
|
|
1196
|
+
this._serviceContext.registerService("vault", this._vault);
|
|
1197
|
+
this.initializeV2Services(serviceSession);
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Connect a wallet to upgrade from session-only mode to wallet mode.
|
|
1201
|
+
*
|
|
1202
|
+
* This allows a user who started in session-only mode to later connect
|
|
1203
|
+
* a wallet and gain the ability to create their own space.
|
|
1204
|
+
*
|
|
1205
|
+
* Note: This does NOT automatically sign in. Call signIn() after connecting
|
|
1206
|
+
* the wallet to create your space.
|
|
1207
|
+
*
|
|
1208
|
+
* @param privateKey - The Ethereum private key (hex string, no 0x prefix)
|
|
1209
|
+
* @param options - Optional configuration
|
|
1210
|
+
* @param options.prefix - Space name prefix (defaults to "default")
|
|
1211
|
+
*
|
|
1212
|
+
* @example
|
|
1213
|
+
* ```typescript
|
|
1214
|
+
* // Start in session-only mode
|
|
1215
|
+
* const node = new TinyCloudNode({ host: "https://node.tinycloud.xyz" });
|
|
1216
|
+
* console.log(node.did); // did:key:z6Mk... (session key)
|
|
1217
|
+
*
|
|
1218
|
+
* // Later, connect a wallet
|
|
1219
|
+
* node.connectWallet(privateKey);
|
|
1220
|
+
* await node.signIn();
|
|
1221
|
+
* console.log(node.did); // did:pkh:eip155:1:0x... (PKH)
|
|
1222
|
+
* ```
|
|
1223
|
+
*/
|
|
1224
|
+
connectWallet(privateKey, options) {
|
|
1225
|
+
if (this.signer) {
|
|
1226
|
+
throw new Error("Wallet already connected. Cannot connect another wallet.");
|
|
1227
|
+
}
|
|
1228
|
+
const prefix = options?.prefix ?? "default";
|
|
1229
|
+
const host = this.config.host;
|
|
1230
|
+
const domain = new URL(host).hostname;
|
|
1231
|
+
if (!_TinyCloudNode.nodeDefaults) {
|
|
1232
|
+
throw new Error(
|
|
1233
|
+
"connectWallet() requires PrivateKeySigner. Use connectSigner() instead, or import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
this.signer = _TinyCloudNode.nodeDefaults.createSigner(privateKey);
|
|
1237
|
+
this.auth = new NodeUserAuthorization({
|
|
1238
|
+
signer: this.signer,
|
|
1239
|
+
signStrategy: { type: "auto-sign" },
|
|
1240
|
+
wasmBindings: this.wasmBindings,
|
|
1241
|
+
sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
|
|
1242
|
+
domain,
|
|
1243
|
+
spacePrefix: prefix,
|
|
1244
|
+
sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
|
|
1245
|
+
tinycloudHosts: [host],
|
|
1246
|
+
autoCreateSpace: this.config.autoCreateSpace,
|
|
1247
|
+
enablePublicSpace: this.config.enablePublicSpace ?? true,
|
|
1248
|
+
spaceCreationHandler: this.config.spaceCreationHandler
|
|
1249
|
+
});
|
|
1250
|
+
this.tc = new TinyCloud(this.auth);
|
|
1251
|
+
this.config.prefix = prefix;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Connect any ISigner to upgrade from session-only mode to wallet mode.
|
|
1255
|
+
*
|
|
1256
|
+
* Same as connectWallet() but accepts any ISigner implementation instead
|
|
1257
|
+
* of a raw private key string. Use this for browser wallets, hardware wallets,
|
|
1258
|
+
* or custom signing backends.
|
|
1259
|
+
*
|
|
1260
|
+
* Note: This does NOT automatically sign in. Call signIn() after connecting.
|
|
1261
|
+
*
|
|
1262
|
+
* @param signer - Any ISigner implementation
|
|
1263
|
+
* @param options - Optional configuration
|
|
1264
|
+
* @param options.prefix - Space name prefix (defaults to "default")
|
|
1265
|
+
*/
|
|
1266
|
+
connectSigner(signer, options) {
|
|
1267
|
+
if (this.signer) {
|
|
1268
|
+
throw new Error("Signer already connected. Cannot connect another signer.");
|
|
1269
|
+
}
|
|
1270
|
+
const prefix = options?.prefix ?? "default";
|
|
1271
|
+
const host = this.config.host;
|
|
1272
|
+
const domain = new URL(host).hostname;
|
|
1273
|
+
this.signer = signer;
|
|
1274
|
+
this.auth = new NodeUserAuthorization({
|
|
1275
|
+
signer: this.signer,
|
|
1276
|
+
signStrategy: { type: "auto-sign" },
|
|
1277
|
+
wasmBindings: this.wasmBindings,
|
|
1278
|
+
sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
|
|
1279
|
+
domain,
|
|
1280
|
+
spacePrefix: prefix,
|
|
1281
|
+
sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
|
|
1282
|
+
tinycloudHosts: [host],
|
|
1283
|
+
autoCreateSpace: this.config.autoCreateSpace,
|
|
1284
|
+
enablePublicSpace: this.config.enablePublicSpace ?? true,
|
|
1285
|
+
spaceCreationHandler: this.config.spaceCreationHandler
|
|
1286
|
+
});
|
|
1287
|
+
this.tc = new TinyCloud(this.auth);
|
|
1288
|
+
this.config.prefix = prefix;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Initialize the service context and KV service after sign-in.
|
|
1292
|
+
* @internal
|
|
1293
|
+
*/
|
|
1294
|
+
initializeServices() {
|
|
1295
|
+
const session = this.auth?.tinyCloudSession;
|
|
1296
|
+
if (!session) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
|
|
1300
|
+
this._serviceContext = new ServiceContext2({
|
|
1301
|
+
invoke: this.wasmBindings.invoke,
|
|
1302
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
1303
|
+
hosts: [this.config.host]
|
|
1304
|
+
});
|
|
1305
|
+
this._kv = new KVService2({});
|
|
1306
|
+
this._kv.initialize(this._serviceContext);
|
|
1307
|
+
this._serviceContext.registerService("kv", this._kv);
|
|
1308
|
+
const features = this.nodeFeatures;
|
|
1309
|
+
if (features.length === 0 || features.includes("sql")) {
|
|
1310
|
+
this._sql = new SQLService2({});
|
|
1311
|
+
this._sql.initialize(this._serviceContext);
|
|
1312
|
+
this._serviceContext.registerService("sql", this._sql);
|
|
1313
|
+
}
|
|
1314
|
+
if (features.length === 0 || features.includes("duckdb")) {
|
|
1315
|
+
this._duckdb = new DuckDbService2({});
|
|
1316
|
+
this._duckdb.initialize(this._serviceContext);
|
|
1317
|
+
this._serviceContext.registerService("duckdb", this._duckdb);
|
|
1318
|
+
}
|
|
1319
|
+
const serviceSession = {
|
|
1320
|
+
delegationHeader: session.delegationHeader,
|
|
1321
|
+
delegationCid: session.delegationCid,
|
|
1322
|
+
spaceId: session.spaceId,
|
|
1323
|
+
verificationMethod: session.verificationMethod,
|
|
1324
|
+
jwk: session.jwk
|
|
1325
|
+
};
|
|
1326
|
+
this._serviceContext.setSession(serviceSession);
|
|
1327
|
+
this.tc.serviceContext.setSession(serviceSession);
|
|
1328
|
+
const wasm = this.wasmBindings;
|
|
1329
|
+
const vaultCrypto = createVaultCrypto({
|
|
1330
|
+
vault_encrypt: wasm.vault_encrypt,
|
|
1331
|
+
vault_decrypt: wasm.vault_decrypt,
|
|
1332
|
+
vault_derive_key: wasm.vault_derive_key,
|
|
1333
|
+
vault_x25519_from_seed: wasm.vault_x25519_from_seed,
|
|
1334
|
+
vault_x25519_dh: wasm.vault_x25519_dh,
|
|
1335
|
+
vault_random_bytes: wasm.vault_random_bytes,
|
|
1336
|
+
vault_sha256: wasm.vault_sha256
|
|
1337
|
+
});
|
|
1338
|
+
const self = this;
|
|
1339
|
+
this._vault = new DataVaultService({
|
|
1340
|
+
spaceId: session.spaceId,
|
|
1341
|
+
crypto: vaultCrypto,
|
|
1342
|
+
tc: {
|
|
1343
|
+
kv: this._kv,
|
|
1344
|
+
ensurePublicSpace: async () => {
|
|
1345
|
+
try {
|
|
1346
|
+
await self.ensurePublicSpace();
|
|
1347
|
+
return { ok: true, data: void 0 };
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
return { ok: false, error: { code: "STORAGE_ERROR", message: error instanceof Error ? error.message : String(error), service: "vault" } };
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
get publicKV() {
|
|
1353
|
+
return self._publicKV ?? self.tc.publicKV;
|
|
1354
|
+
},
|
|
1355
|
+
readPublicSpace: (host, spaceId, key) => TinyCloud.readPublicSpace(host, spaceId, key),
|
|
1356
|
+
makePublicSpaceId: TinyCloud.makePublicSpaceId,
|
|
1357
|
+
did: this.did,
|
|
1358
|
+
address: this._address,
|
|
1359
|
+
chainId: this._chainId,
|
|
1360
|
+
hosts: [this.config.host]
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
this._vault.initialize(this._serviceContext);
|
|
1364
|
+
this._serviceContext.registerService("vault", this._vault);
|
|
1365
|
+
this.initializeV2Services(serviceSession);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Initialize the v2 delegation system services.
|
|
1369
|
+
* @internal
|
|
1370
|
+
*/
|
|
1371
|
+
initializeV2Services(serviceSession) {
|
|
1372
|
+
this._capabilityRegistry = new CapabilityKeyRegistry();
|
|
1373
|
+
const tcSession = this.auth?.tinyCloudSession;
|
|
1374
|
+
if (tcSession && this._address) {
|
|
1375
|
+
const sessionKey = {
|
|
1376
|
+
id: tcSession.sessionKey,
|
|
1377
|
+
did: tcSession.verificationMethod,
|
|
1378
|
+
type: "session",
|
|
1379
|
+
// Cast jwk from generic object to JWK - we know it has the required structure
|
|
1380
|
+
jwk: tcSession.jwk,
|
|
1381
|
+
priority: 0
|
|
1382
|
+
// Session keys have highest priority
|
|
1383
|
+
};
|
|
1384
|
+
const rootDelegation = {
|
|
1385
|
+
cid: tcSession.delegationCid,
|
|
1386
|
+
delegateDID: tcSession.verificationMethod,
|
|
1387
|
+
spaceId: tcSession.spaceId,
|
|
1388
|
+
path: "",
|
|
1389
|
+
// Root access
|
|
1390
|
+
actions: [
|
|
1391
|
+
"tinycloud.kv/put",
|
|
1392
|
+
"tinycloud.kv/get",
|
|
1393
|
+
"tinycloud.kv/del",
|
|
1394
|
+
"tinycloud.kv/list",
|
|
1395
|
+
"tinycloud.kv/metadata",
|
|
1396
|
+
"tinycloud.sql/read",
|
|
1397
|
+
"tinycloud.sql/write",
|
|
1398
|
+
"tinycloud.sql/admin",
|
|
1399
|
+
"tinycloud.sql/*",
|
|
1400
|
+
"tinycloud.duckdb/read",
|
|
1401
|
+
"tinycloud.duckdb/write",
|
|
1402
|
+
"tinycloud.duckdb/admin",
|
|
1403
|
+
"tinycloud.duckdb/describe",
|
|
1404
|
+
"tinycloud.duckdb/export",
|
|
1405
|
+
"tinycloud.duckdb/import",
|
|
1406
|
+
"tinycloud.duckdb/*"
|
|
1407
|
+
],
|
|
1408
|
+
expiry: this.getSessionExpiry(),
|
|
1409
|
+
isRevoked: false,
|
|
1410
|
+
allowSubDelegation: true
|
|
1411
|
+
};
|
|
1412
|
+
const delegations = [rootDelegation];
|
|
1413
|
+
if (tcSession.spaces) {
|
|
1414
|
+
for (const [spaceName, spaceId] of Object.entries(tcSession.spaces)) {
|
|
1415
|
+
delegations.push({
|
|
1416
|
+
cid: tcSession.delegationCid,
|
|
1417
|
+
delegateDID: tcSession.verificationMethod,
|
|
1418
|
+
spaceId,
|
|
1419
|
+
path: "",
|
|
1420
|
+
actions: [
|
|
1421
|
+
"tinycloud.kv/put",
|
|
1422
|
+
"tinycloud.kv/get",
|
|
1423
|
+
"tinycloud.kv/del",
|
|
1424
|
+
"tinycloud.kv/list",
|
|
1425
|
+
"tinycloud.kv/metadata",
|
|
1426
|
+
"tinycloud.sql/read",
|
|
1427
|
+
"tinycloud.sql/write",
|
|
1428
|
+
"tinycloud.sql/admin",
|
|
1429
|
+
"tinycloud.sql/*",
|
|
1430
|
+
"tinycloud.duckdb/read",
|
|
1431
|
+
"tinycloud.duckdb/write",
|
|
1432
|
+
"tinycloud.duckdb/admin",
|
|
1433
|
+
"tinycloud.duckdb/describe",
|
|
1434
|
+
"tinycloud.duckdb/export",
|
|
1435
|
+
"tinycloud.duckdb/import",
|
|
1436
|
+
"tinycloud.duckdb/*"
|
|
1437
|
+
],
|
|
1438
|
+
expiry: this.getSessionExpiry(),
|
|
1439
|
+
isRevoked: false,
|
|
1440
|
+
allowSubDelegation: true
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
this._capabilityRegistry.registerKey(sessionKey, delegations);
|
|
1445
|
+
}
|
|
1446
|
+
this._delegationManager = new DelegationManager({
|
|
1447
|
+
hosts: [this.config.host],
|
|
1448
|
+
session: serviceSession,
|
|
1449
|
+
invoke: this.wasmBindings.invoke,
|
|
1450
|
+
fetch: globalThis.fetch.bind(globalThis)
|
|
1451
|
+
});
|
|
1452
|
+
this._spaceService = new SpaceService({
|
|
1453
|
+
hosts: [this.config.host],
|
|
1454
|
+
session: serviceSession,
|
|
1455
|
+
invoke: this.wasmBindings.invoke,
|
|
1456
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
1457
|
+
capabilityRegistry: this._capabilityRegistry,
|
|
1458
|
+
userDid: this.did,
|
|
1459
|
+
createKVService: (spaceId) => {
|
|
1460
|
+
const kvService = new KVService2({});
|
|
1461
|
+
if (this._serviceContext) {
|
|
1462
|
+
const spaceScopedContext = new ServiceContext2({
|
|
1463
|
+
invoke: this._serviceContext.invoke,
|
|
1464
|
+
fetch: this._serviceContext.fetch,
|
|
1465
|
+
hosts: this._serviceContext.hosts
|
|
1466
|
+
});
|
|
1467
|
+
const session = this._serviceContext.session;
|
|
1468
|
+
if (session) {
|
|
1469
|
+
spaceScopedContext.setSession({ ...session, spaceId });
|
|
1470
|
+
}
|
|
1471
|
+
kvService.initialize(spaceScopedContext);
|
|
1472
|
+
}
|
|
1473
|
+
return kvService;
|
|
1474
|
+
},
|
|
1475
|
+
// Enable space.delegations.create() via SIWE-based delegation
|
|
1476
|
+
createDelegation: async (params) => {
|
|
1477
|
+
try {
|
|
1478
|
+
const portableDelegation = await this.createDelegation({
|
|
1479
|
+
delegateDID: params.delegateDID,
|
|
1480
|
+
path: params.path,
|
|
1481
|
+
actions: params.actions,
|
|
1482
|
+
disableSubDelegation: params.disableSubDelegation,
|
|
1483
|
+
expiryMs: params.expiry ? params.expiry.getTime() - Date.now() : void 0
|
|
1484
|
+
});
|
|
1485
|
+
const delegation = {
|
|
1486
|
+
cid: portableDelegation.cid,
|
|
1487
|
+
delegateDID: portableDelegation.delegateDID,
|
|
1488
|
+
delegatorDID: this.did,
|
|
1489
|
+
spaceId: portableDelegation.spaceId,
|
|
1490
|
+
path: portableDelegation.path,
|
|
1491
|
+
actions: portableDelegation.actions,
|
|
1492
|
+
expiry: portableDelegation.expiry,
|
|
1493
|
+
isRevoked: false,
|
|
1494
|
+
allowSubDelegation: !portableDelegation.disableSubDelegation,
|
|
1495
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1496
|
+
authHeader: portableDelegation.delegationHeader.Authorization
|
|
1497
|
+
};
|
|
1498
|
+
return { ok: true, data: delegation };
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
return {
|
|
1501
|
+
ok: false,
|
|
1502
|
+
error: {
|
|
1503
|
+
code: "CREATION_FAILED",
|
|
1504
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1505
|
+
service: "delegation"
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
this._sharingService.updateConfig({
|
|
1512
|
+
session: serviceSession,
|
|
1513
|
+
delegationManager: this._delegationManager,
|
|
1514
|
+
sessionExpiry: this.getSessionExpiry(),
|
|
1515
|
+
// WASM-based delegation creation (preferred - no server roundtrip)
|
|
1516
|
+
createDelegationWasm: (params) => this.createDelegationWrapper(params),
|
|
1517
|
+
// Root delegation for long-lived share links (bypasses session expiry)
|
|
1518
|
+
// In node-sdk we have direct signer access, so no popup needed
|
|
1519
|
+
onRootDelegationNeeded: this.signer ? async (params) => this.createRootDelegationForSharing(params) : void 0
|
|
1520
|
+
});
|
|
1521
|
+
this._spaceService.updateConfig({
|
|
1522
|
+
sharingService: this._sharingService
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Get the session expiry time.
|
|
1527
|
+
* @internal
|
|
1528
|
+
*/
|
|
1529
|
+
getSessionExpiry() {
|
|
1530
|
+
const expirationMs = this.config.sessionExpirationMs ?? 60 * 60 * 1e3;
|
|
1531
|
+
return new Date(Date.now() + expirationMs);
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Wrapper for the WASM createDelegation function.
|
|
1535
|
+
* Adapts the WASM interface to what SharingService expects.
|
|
1536
|
+
* @internal
|
|
1537
|
+
*/
|
|
1538
|
+
createDelegationWrapper(params) {
|
|
1539
|
+
const wasmSession = {
|
|
1540
|
+
delegationHeader: params.session.delegationHeader,
|
|
1541
|
+
delegationCid: params.session.delegationCid,
|
|
1542
|
+
jwk: params.session.jwk,
|
|
1543
|
+
spaceId: params.session.spaceId,
|
|
1544
|
+
verificationMethod: params.session.verificationMethod
|
|
1545
|
+
};
|
|
1546
|
+
const result = this.wasmBindings.createDelegation(
|
|
1547
|
+
wasmSession,
|
|
1548
|
+
params.delegateDID,
|
|
1549
|
+
params.spaceId,
|
|
1550
|
+
params.path,
|
|
1551
|
+
params.actions,
|
|
1552
|
+
params.expirationSecs,
|
|
1553
|
+
params.notBeforeSecs
|
|
1554
|
+
);
|
|
1555
|
+
return {
|
|
1556
|
+
delegation: result.delegation,
|
|
1557
|
+
cid: result.cid,
|
|
1558
|
+
delegateDID: result.delegateDid,
|
|
1559
|
+
path: result.path,
|
|
1560
|
+
actions: result.actions,
|
|
1561
|
+
expiry: new Date(result.expiry * 1e3)
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Create a direct root delegation from the wallet to a share key.
|
|
1566
|
+
* This bypasses the session delegation chain, allowing share links
|
|
1567
|
+
* with expiry longer than the current session.
|
|
1568
|
+
* @internal
|
|
1569
|
+
*/
|
|
1570
|
+
async createRootDelegationForSharing(params) {
|
|
1571
|
+
if (!this.signer) {
|
|
1572
|
+
return void 0;
|
|
1573
|
+
}
|
|
1574
|
+
const session = this.auth?.tinyCloudSession;
|
|
1575
|
+
if (!session) {
|
|
1576
|
+
return void 0;
|
|
1577
|
+
}
|
|
1578
|
+
try {
|
|
1579
|
+
const host = this.config.host;
|
|
1580
|
+
const now = /* @__PURE__ */ new Date();
|
|
1581
|
+
const abilities = {
|
|
1582
|
+
kv: {
|
|
1583
|
+
[params.path]: params.actions
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
const prepared = this.wasmBindings.prepareSession({
|
|
1587
|
+
abilities,
|
|
1588
|
+
address: this.wasmBindings.ensureEip55(session.address),
|
|
1589
|
+
chainId: session.chainId,
|
|
1590
|
+
domain: new URL(host).hostname,
|
|
1591
|
+
issuedAt: now.toISOString(),
|
|
1592
|
+
expirationTime: params.requestedExpiry.toISOString(),
|
|
1593
|
+
spaceId: params.spaceId,
|
|
1594
|
+
delegateUri: params.shareKeyDID
|
|
1595
|
+
});
|
|
1596
|
+
const signature = await this.signer.signMessage(prepared.siwe);
|
|
1597
|
+
const delegationSession = this.wasmBindings.completeSessionSetup({
|
|
1598
|
+
...prepared,
|
|
1599
|
+
signature
|
|
1600
|
+
});
|
|
1601
|
+
const activateResult = await activateSessionWithHost2(
|
|
1602
|
+
host,
|
|
1603
|
+
delegationSession.delegationHeader
|
|
1604
|
+
);
|
|
1605
|
+
if (!activateResult.success) {
|
|
1606
|
+
return void 0;
|
|
1607
|
+
}
|
|
1608
|
+
return {
|
|
1609
|
+
cid: delegationSession.delegationCid,
|
|
1610
|
+
delegateDID: params.shareKeyDID,
|
|
1611
|
+
delegatorDID: `did:pkh:eip155:${session.chainId}:${session.address}`,
|
|
1612
|
+
spaceId: params.spaceId,
|
|
1613
|
+
path: params.path,
|
|
1614
|
+
actions: params.actions,
|
|
1615
|
+
expiry: params.requestedExpiry,
|
|
1616
|
+
isRevoked: false,
|
|
1617
|
+
allowSubDelegation: true,
|
|
1618
|
+
createdAt: now,
|
|
1619
|
+
authHeader: delegationSession.delegationHeader.Authorization
|
|
1620
|
+
};
|
|
1621
|
+
} catch {
|
|
1622
|
+
return void 0;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Track a received delegation in the capability registry.
|
|
1627
|
+
* @internal
|
|
1628
|
+
*/
|
|
1629
|
+
trackReceivedDelegation(delegation, jwk) {
|
|
1630
|
+
if (!this._capabilityRegistry) {
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
const keyInfo = {
|
|
1634
|
+
id: `received:${delegation.cid}`,
|
|
1635
|
+
did: this.sessionDid,
|
|
1636
|
+
type: "ingested",
|
|
1637
|
+
jwk,
|
|
1638
|
+
priority: 2
|
|
1639
|
+
};
|
|
1640
|
+
const delegationRecord = {
|
|
1641
|
+
cid: delegation.cid,
|
|
1642
|
+
delegateDID: delegation.delegateDID,
|
|
1643
|
+
spaceId: delegation.spaceId,
|
|
1644
|
+
path: delegation.path,
|
|
1645
|
+
actions: delegation.actions,
|
|
1646
|
+
expiry: delegation.expiry,
|
|
1647
|
+
isRevoked: false,
|
|
1648
|
+
allowSubDelegation: !delegation.disableSubDelegation
|
|
1649
|
+
};
|
|
1650
|
+
this._capabilityRegistry.ingestKey(keyInfo, delegationRecord);
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Key-value storage operations on this user's space.
|
|
1654
|
+
*/
|
|
1655
|
+
get kv() {
|
|
1656
|
+
if (!this._kv) {
|
|
1657
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1658
|
+
}
|
|
1659
|
+
return this._kv;
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* SQL database operations on this user's space.
|
|
1663
|
+
*/
|
|
1664
|
+
get sql() {
|
|
1665
|
+
if (!this._sql) {
|
|
1666
|
+
const features = this.nodeFeatures;
|
|
1667
|
+
if (features.length > 0 && !features.includes("sql")) {
|
|
1668
|
+
throw new UnsupportedFeatureError("sql", this.config.host, features);
|
|
1669
|
+
}
|
|
1670
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1671
|
+
}
|
|
1672
|
+
return this._sql;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* DuckDB database operations on this user's space.
|
|
1676
|
+
*/
|
|
1677
|
+
get duckdb() {
|
|
1678
|
+
if (!this._duckdb) {
|
|
1679
|
+
const features = this.nodeFeatures;
|
|
1680
|
+
if (features.length > 0 && !features.includes("duckdb")) {
|
|
1681
|
+
throw new UnsupportedFeatureError("duckdb", this.config.host, features);
|
|
1682
|
+
}
|
|
1683
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1684
|
+
}
|
|
1685
|
+
return this._duckdb;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Data Vault operations - client-side encrypted KV storage.
|
|
1689
|
+
* Call `vault.unlock(signer)` after signIn() to derive encryption keys.
|
|
1690
|
+
*/
|
|
1691
|
+
get vault() {
|
|
1692
|
+
if (!this._vault) {
|
|
1693
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1694
|
+
}
|
|
1695
|
+
return this._vault;
|
|
1696
|
+
}
|
|
1697
|
+
// ===========================================================================
|
|
1698
|
+
// v2 Service Accessors
|
|
1699
|
+
// ===========================================================================
|
|
1700
|
+
/**
|
|
1701
|
+
* Get the CapabilityKeyRegistry for managing keys and their capabilities.
|
|
1702
|
+
*
|
|
1703
|
+
* The registry tracks keys (session, main, ingested) and their associated
|
|
1704
|
+
* delegations, enabling automatic key selection for operations.
|
|
1705
|
+
*
|
|
1706
|
+
* @example
|
|
1707
|
+
* ```typescript
|
|
1708
|
+
* const registry = alice.capabilityRegistry;
|
|
1709
|
+
*
|
|
1710
|
+
* // Get the best key for an operation
|
|
1711
|
+
* const key = registry.getKeyForCapability(
|
|
1712
|
+
* "tinycloud://my-space/kv/data",
|
|
1713
|
+
* "tinycloud.kv/get"
|
|
1714
|
+
* );
|
|
1715
|
+
*
|
|
1716
|
+
* // List all capabilities
|
|
1717
|
+
* const capabilities = registry.getAllCapabilities();
|
|
1718
|
+
* ```
|
|
1719
|
+
*/
|
|
1720
|
+
get capabilityRegistry() {
|
|
1721
|
+
if (!this._capabilityRegistry) {
|
|
1722
|
+
throw new Error("CapabilityKeyRegistry not initialized.");
|
|
1723
|
+
}
|
|
1724
|
+
return this._capabilityRegistry;
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Access received delegations (recipient view).
|
|
1728
|
+
*
|
|
1729
|
+
* Use this to see what delegations have been received via useDelegation().
|
|
1730
|
+
*
|
|
1731
|
+
* @example
|
|
1732
|
+
* ```typescript
|
|
1733
|
+
* // List all received delegations
|
|
1734
|
+
* const received = bob.delegations.list();
|
|
1735
|
+
* console.log("I have access to:", received.length, "spaces");
|
|
1736
|
+
*
|
|
1737
|
+
* // Get a specific delegation by CID
|
|
1738
|
+
* const delegation = bob.delegations.get(cid);
|
|
1739
|
+
* ```
|
|
1740
|
+
*/
|
|
1741
|
+
get delegations() {
|
|
1742
|
+
const registry = this._capabilityRegistry;
|
|
1743
|
+
if (!registry) {
|
|
1744
|
+
return {
|
|
1745
|
+
list: () => [],
|
|
1746
|
+
get: () => void 0
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
return {
|
|
1750
|
+
list: () => registry.getAllCapabilities().map((entry) => entry.delegation),
|
|
1751
|
+
get: (cid) => {
|
|
1752
|
+
const capabilities = registry.getAllCapabilities();
|
|
1753
|
+
const entry = capabilities.find((e) => e.delegation.cid === cid);
|
|
1754
|
+
return entry?.delegation;
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Get the DelegationManager for delegation CRUD operations.
|
|
1760
|
+
*
|
|
1761
|
+
* This is the v2 delegation service providing a cleaner API than
|
|
1762
|
+
* the legacy createDelegation/useDelegation methods.
|
|
1763
|
+
*
|
|
1764
|
+
* @example
|
|
1765
|
+
* ```typescript
|
|
1766
|
+
* const delegations = alice.delegationManager;
|
|
1767
|
+
*
|
|
1768
|
+
* // Create a delegation
|
|
1769
|
+
* const result = await delegations.create({
|
|
1770
|
+
* delegateDID: bob.did,
|
|
1771
|
+
* path: "shared/",
|
|
1772
|
+
* actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
|
|
1773
|
+
* expiry: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
|
|
1774
|
+
* });
|
|
1775
|
+
*
|
|
1776
|
+
* // List delegations
|
|
1777
|
+
* const listResult = await delegations.list();
|
|
1778
|
+
*
|
|
1779
|
+
* // Revoke a delegation
|
|
1780
|
+
* await delegations.revoke(delegationCid);
|
|
1781
|
+
* ```
|
|
1782
|
+
*/
|
|
1783
|
+
get delegationManager() {
|
|
1784
|
+
if (!this._delegationManager) {
|
|
1785
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1786
|
+
}
|
|
1787
|
+
return this._delegationManager;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Get the SpaceService for managing spaces.
|
|
1791
|
+
*
|
|
1792
|
+
* The SpaceService provides access to owned and delegated spaces,
|
|
1793
|
+
* including space creation, listing, and scoped operations.
|
|
1794
|
+
*
|
|
1795
|
+
* @example
|
|
1796
|
+
* ```typescript
|
|
1797
|
+
* const spaces = alice.spaces;
|
|
1798
|
+
*
|
|
1799
|
+
* // List all accessible spaces
|
|
1800
|
+
* const result = await spaces.list();
|
|
1801
|
+
*
|
|
1802
|
+
* // Create a new space
|
|
1803
|
+
* const createResult = await spaces.create('photos');
|
|
1804
|
+
*
|
|
1805
|
+
* // Get a space object for operations
|
|
1806
|
+
* const mySpace = spaces.get('default');
|
|
1807
|
+
* await mySpace.kv.put('key', 'value');
|
|
1808
|
+
*
|
|
1809
|
+
* // Check if a space exists
|
|
1810
|
+
* const exists = await spaces.exists('photos');
|
|
1811
|
+
* ```
|
|
1812
|
+
*/
|
|
1813
|
+
get spaces() {
|
|
1814
|
+
if (!this._spaceService) {
|
|
1815
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1816
|
+
}
|
|
1817
|
+
return this._spaceService;
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Alias for `spaces` - get the SpaceService.
|
|
1821
|
+
* @see spaces
|
|
1822
|
+
*/
|
|
1823
|
+
get spaceService() {
|
|
1824
|
+
return this.spaces;
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Get the SharingService for creating and receiving v2 sharing links.
|
|
1828
|
+
*
|
|
1829
|
+
* The SharingService creates sharing links with embedded private keys,
|
|
1830
|
+
* allowing recipients to exercise delegations without prior session setup.
|
|
1831
|
+
*
|
|
1832
|
+
* @example
|
|
1833
|
+
* ```typescript
|
|
1834
|
+
* const sharing = alice.sharing;
|
|
1835
|
+
*
|
|
1836
|
+
* // Generate a sharing link
|
|
1837
|
+
* const result = await sharing.generate({
|
|
1838
|
+
* path: "/kv/documents/report.pdf",
|
|
1839
|
+
* actions: ["tinycloud.kv/get"],
|
|
1840
|
+
* expiry: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
1841
|
+
* });
|
|
1842
|
+
*
|
|
1843
|
+
* if (result.ok) {
|
|
1844
|
+
* console.log("Share URL:", result.data.url);
|
|
1845
|
+
* // Send the URL to the recipient
|
|
1846
|
+
* }
|
|
1847
|
+
*
|
|
1848
|
+
* // Receive a sharing link
|
|
1849
|
+
* const receiveResult = await sharing.receive(shareUrl);
|
|
1850
|
+
* if (receiveResult.ok) {
|
|
1851
|
+
* // Use the pre-configured KV service
|
|
1852
|
+
* const data = await receiveResult.data.kv.get("report.pdf");
|
|
1853
|
+
* }
|
|
1854
|
+
* ```
|
|
1855
|
+
*/
|
|
1856
|
+
get sharing() {
|
|
1857
|
+
return this._sharingService;
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Alias for `sharing` - get the SharingService.
|
|
1861
|
+
* @see sharing
|
|
1862
|
+
*/
|
|
1863
|
+
get sharingService() {
|
|
1864
|
+
return this.sharing;
|
|
1865
|
+
}
|
|
1866
|
+
// ===========================================================================
|
|
1867
|
+
// Public Space Methods
|
|
1868
|
+
// ===========================================================================
|
|
1869
|
+
/**
|
|
1870
|
+
* Ensure the user's public space exists and is accessible.
|
|
1871
|
+
* Creates the space and activates a session delegation for it.
|
|
1872
|
+
* This is the trigger for lazy public space creation — call it
|
|
1873
|
+
* before writing to spaces.get('public').kv.
|
|
1874
|
+
*/
|
|
1875
|
+
async ensurePublicSpace() {
|
|
1876
|
+
if (!this.auth || !this.session || !this.signer) {
|
|
1877
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1878
|
+
}
|
|
1879
|
+
const publicSpaceId = this.session.spaces?.public;
|
|
1880
|
+
if (!publicSpaceId) {
|
|
1881
|
+
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
1882
|
+
}
|
|
1883
|
+
await this.auth.hostPublicSpace(publicSpaceId);
|
|
1884
|
+
const kvActions = [
|
|
1885
|
+
"tinycloud.kv/put",
|
|
1886
|
+
"tinycloud.kv/get",
|
|
1887
|
+
"tinycloud.kv/del",
|
|
1888
|
+
"tinycloud.kv/list",
|
|
1889
|
+
"tinycloud.kv/metadata"
|
|
1890
|
+
];
|
|
1891
|
+
const abilities = { kv: { "": kvActions } };
|
|
1892
|
+
const now = /* @__PURE__ */ new Date();
|
|
1893
|
+
const expiryMs = 60 * 60 * 1e3;
|
|
1894
|
+
const expirationTime = new Date(now.getTime() + expiryMs);
|
|
1895
|
+
const prepared = this.wasmBindings.prepareSession({
|
|
1896
|
+
abilities,
|
|
1897
|
+
address: this.wasmBindings.ensureEip55(this.session.address),
|
|
1898
|
+
chainId: this.session.chainId,
|
|
1899
|
+
domain: new URL(this.config.host).hostname,
|
|
1900
|
+
issuedAt: now.toISOString(),
|
|
1901
|
+
expirationTime: expirationTime.toISOString(),
|
|
1902
|
+
spaceId: publicSpaceId,
|
|
1903
|
+
jwk: this.session.jwk,
|
|
1904
|
+
parents: [this.session.delegationCid]
|
|
1905
|
+
});
|
|
1906
|
+
const signature = await this.signer.signMessage(prepared.siwe);
|
|
1907
|
+
const delegationSession = this.wasmBindings.completeSessionSetup({
|
|
1908
|
+
...prepared,
|
|
1909
|
+
signature
|
|
1910
|
+
});
|
|
1911
|
+
const activateResult = await activateSessionWithHost2(
|
|
1912
|
+
this.config.host,
|
|
1913
|
+
delegationSession.delegationHeader
|
|
1914
|
+
);
|
|
1915
|
+
if (!activateResult.success) {
|
|
1916
|
+
throw new Error(`Failed to activate public space delegation: ${activateResult.error}`);
|
|
1917
|
+
}
|
|
1918
|
+
if (this._capabilityRegistry && this.session) {
|
|
1919
|
+
const sessionKey = {
|
|
1920
|
+
id: this.session.sessionKey,
|
|
1921
|
+
did: this.session.verificationMethod,
|
|
1922
|
+
type: "session",
|
|
1923
|
+
jwk: this.session.jwk,
|
|
1924
|
+
priority: 0
|
|
1925
|
+
};
|
|
1926
|
+
this._capabilityRegistry.registerKey(sessionKey, [{
|
|
1927
|
+
cid: delegationSession.delegationCid,
|
|
1928
|
+
delegateDID: this.session.verificationMethod,
|
|
1929
|
+
spaceId: publicSpaceId,
|
|
1930
|
+
path: "",
|
|
1931
|
+
actions: kvActions,
|
|
1932
|
+
expiry: expirationTime,
|
|
1933
|
+
isRevoked: false,
|
|
1934
|
+
allowSubDelegation: true
|
|
1935
|
+
}]);
|
|
1936
|
+
}
|
|
1937
|
+
if (this._serviceContext) {
|
|
1938
|
+
const publicKV = new KVService2({ prefix: "" });
|
|
1939
|
+
const publicContext = new ServiceContext2({
|
|
1940
|
+
invoke: this.wasmBindings.invoke,
|
|
1941
|
+
fetch: this._serviceContext.fetch,
|
|
1942
|
+
hosts: this._serviceContext.hosts
|
|
1943
|
+
});
|
|
1944
|
+
publicContext.setSession({
|
|
1945
|
+
delegationHeader: delegationSession.delegationHeader,
|
|
1946
|
+
delegationCid: delegationSession.delegationCid,
|
|
1947
|
+
spaceId: publicSpaceId,
|
|
1948
|
+
verificationMethod: this.session.verificationMethod,
|
|
1949
|
+
jwk: this.session.jwk
|
|
1950
|
+
});
|
|
1951
|
+
publicKV.initialize(publicContext);
|
|
1952
|
+
this._publicKV = publicKV;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Get a KVService scoped to the user's own public space.
|
|
1957
|
+
* Writes require authentication (owner/delegate).
|
|
1958
|
+
*/
|
|
1959
|
+
get publicKV() {
|
|
1960
|
+
if (this._publicKV) {
|
|
1961
|
+
return this._publicKV;
|
|
1962
|
+
}
|
|
1963
|
+
if (!this.tc) {
|
|
1964
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
1965
|
+
}
|
|
1966
|
+
return this.tc.publicKV;
|
|
1967
|
+
}
|
|
1968
|
+
// ===========================================================================
|
|
1969
|
+
// v2 Delegation Convenience Methods
|
|
1970
|
+
// ===========================================================================
|
|
1971
|
+
/**
|
|
1972
|
+
* Create a delegation using the v2 DelegationManager.
|
|
1973
|
+
*
|
|
1974
|
+
* This is a convenience method that wraps DelegationManager.create().
|
|
1975
|
+
* For more control, use `this.delegationManager` directly.
|
|
1976
|
+
*
|
|
1977
|
+
* @param params - Delegation parameters
|
|
1978
|
+
* @returns Result containing the created Delegation
|
|
1979
|
+
*
|
|
1980
|
+
* @example
|
|
1981
|
+
* ```typescript
|
|
1982
|
+
* const result = await alice.delegate({
|
|
1983
|
+
* delegateDID: bob.did,
|
|
1984
|
+
* path: "shared/",
|
|
1985
|
+
* actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
|
|
1986
|
+
* expiry: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
1987
|
+
* });
|
|
1988
|
+
*
|
|
1989
|
+
* if (result.ok) {
|
|
1990
|
+
* console.log("Delegation created:", result.data.cid);
|
|
1991
|
+
* }
|
|
1992
|
+
* ```
|
|
1993
|
+
*/
|
|
1994
|
+
async delegate(params) {
|
|
1995
|
+
return this.delegationManager.create(params);
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Revoke a delegation using the v2 DelegationManager.
|
|
1999
|
+
*
|
|
2000
|
+
* @param cid - The CID of the delegation to revoke
|
|
2001
|
+
* @returns Result indicating success or failure
|
|
2002
|
+
*/
|
|
2003
|
+
async revokeDelegation(cid) {
|
|
2004
|
+
return this.delegationManager.revoke(cid);
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* List all delegations for the current session's space.
|
|
2008
|
+
*
|
|
2009
|
+
* @returns Result containing an array of Delegations
|
|
2010
|
+
*/
|
|
2011
|
+
async listDelegations() {
|
|
2012
|
+
return this.delegationManager.list();
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Check if the current session has permission for a path and action.
|
|
2016
|
+
*
|
|
2017
|
+
* @param path - The resource path to check
|
|
2018
|
+
* @param action - The action to check (e.g., "tinycloud.kv/get")
|
|
2019
|
+
* @returns Result containing boolean permission status
|
|
2020
|
+
*/
|
|
2021
|
+
async checkPermission(path, action) {
|
|
2022
|
+
return this.delegationManager.checkPermission(path, action);
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Create a delegation from this user to another user.
|
|
2026
|
+
*
|
|
2027
|
+
* The delegation grants the recipient access to a specific path and actions
|
|
2028
|
+
* within this user's space.
|
|
2029
|
+
*
|
|
2030
|
+
* @param params - Delegation parameters
|
|
2031
|
+
* @returns A portable delegation that can be sent to the recipient
|
|
2032
|
+
*/
|
|
2033
|
+
async createDelegation(params) {
|
|
2034
|
+
if (!this.signer) {
|
|
2035
|
+
throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
|
|
2036
|
+
}
|
|
2037
|
+
const session = this.auth?.tinyCloudSession;
|
|
2038
|
+
if (!session) {
|
|
2039
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
2040
|
+
}
|
|
2041
|
+
if (params.delegateDID.endsWith(".eth") && this.config.ensResolver) {
|
|
2042
|
+
const address = await this.config.ensResolver.resolveAddress(params.delegateDID);
|
|
2043
|
+
if (!address) throw new Error(`Could not resolve ENS name: ${params.delegateDID}`);
|
|
2044
|
+
params = { ...params, delegateDID: `did:pkh:eip155:1:${address}` };
|
|
2045
|
+
}
|
|
2046
|
+
const abilities = {};
|
|
2047
|
+
const kvActions = params.actions.filter((a) => a.startsWith("tinycloud.kv/"));
|
|
2048
|
+
const sqlActions = params.actions.filter((a) => a.startsWith("tinycloud.sql/"));
|
|
2049
|
+
const duckdbActions = params.actions.filter((a) => a.startsWith("tinycloud.duckdb/"));
|
|
2050
|
+
if (kvActions.length > 0) {
|
|
2051
|
+
abilities.kv = { [params.path]: kvActions };
|
|
2052
|
+
}
|
|
2053
|
+
if (sqlActions.length > 0) {
|
|
2054
|
+
abilities.sql = { [params.path]: sqlActions };
|
|
2055
|
+
}
|
|
2056
|
+
if (duckdbActions.length > 0) {
|
|
2057
|
+
abilities.duckdb = { [params.path]: duckdbActions };
|
|
2058
|
+
}
|
|
2059
|
+
const now = /* @__PURE__ */ new Date();
|
|
2060
|
+
const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
|
|
2061
|
+
const expirationTime = new Date(now.getTime() + expiryMs);
|
|
2062
|
+
const prepared = this.wasmBindings.prepareSession({
|
|
2063
|
+
abilities,
|
|
2064
|
+
address: this.wasmBindings.ensureEip55(session.address),
|
|
2065
|
+
chainId: session.chainId,
|
|
2066
|
+
domain: new URL(this.config.host).hostname,
|
|
2067
|
+
issuedAt: now.toISOString(),
|
|
2068
|
+
expirationTime: expirationTime.toISOString(),
|
|
2069
|
+
spaceId: params.spaceIdOverride ?? session.spaceId,
|
|
2070
|
+
delegateUri: params.delegateDID,
|
|
2071
|
+
parents: [session.delegationCid]
|
|
2072
|
+
});
|
|
2073
|
+
const signature = await this.signer.signMessage(prepared.siwe);
|
|
2074
|
+
const delegationSession = this.wasmBindings.completeSessionSetup({
|
|
2075
|
+
...prepared,
|
|
2076
|
+
signature
|
|
2077
|
+
});
|
|
2078
|
+
const activateResult = await activateSessionWithHost2(
|
|
2079
|
+
this.config.host,
|
|
2080
|
+
delegationSession.delegationHeader
|
|
2081
|
+
);
|
|
2082
|
+
if (!activateResult.success) {
|
|
2083
|
+
throw new Error(`Failed to activate delegation: ${activateResult.error}`);
|
|
2084
|
+
}
|
|
2085
|
+
const result = {
|
|
2086
|
+
cid: delegationSession.delegationCid,
|
|
2087
|
+
delegationHeader: delegationSession.delegationHeader,
|
|
2088
|
+
spaceId: params.spaceIdOverride ?? session.spaceId,
|
|
2089
|
+
path: params.path,
|
|
2090
|
+
actions: params.actions,
|
|
2091
|
+
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
2092
|
+
expiry: expirationTime,
|
|
2093
|
+
delegateDID: params.delegateDID,
|
|
2094
|
+
ownerAddress: session.address,
|
|
2095
|
+
chainId: session.chainId,
|
|
2096
|
+
host: this.config.host
|
|
2097
|
+
};
|
|
2098
|
+
const hasKvActions = params.actions.some((a) => a.startsWith("tinycloud.kv/"));
|
|
2099
|
+
if (hasKvActions && params.includePublicSpace !== false) {
|
|
2100
|
+
const publicSpaceId = makePublicSpaceId(
|
|
2101
|
+
this.wasmBindings.ensureEip55(session.address),
|
|
2102
|
+
session.chainId
|
|
2103
|
+
);
|
|
2104
|
+
const publicAbilities = {
|
|
2105
|
+
kv: { "": ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/metadata"] }
|
|
2106
|
+
};
|
|
2107
|
+
const publicPrepared = this.wasmBindings.prepareSession({
|
|
2108
|
+
abilities: publicAbilities,
|
|
2109
|
+
address: this.wasmBindings.ensureEip55(session.address),
|
|
2110
|
+
chainId: session.chainId,
|
|
2111
|
+
domain: new URL(this.config.host).hostname,
|
|
2112
|
+
issuedAt: now.toISOString(),
|
|
2113
|
+
expirationTime: expirationTime.toISOString(),
|
|
2114
|
+
spaceId: publicSpaceId,
|
|
2115
|
+
delegateUri: params.delegateDID,
|
|
2116
|
+
parents: [session.delegationCid]
|
|
2117
|
+
});
|
|
2118
|
+
const publicSignature = await this.signer.signMessage(publicPrepared.siwe);
|
|
2119
|
+
const publicSession = this.wasmBindings.completeSessionSetup({
|
|
2120
|
+
...publicPrepared,
|
|
2121
|
+
signature: publicSignature
|
|
2122
|
+
});
|
|
2123
|
+
const publicActivateResult = await activateSessionWithHost2(
|
|
2124
|
+
this.config.host,
|
|
2125
|
+
publicSession.delegationHeader
|
|
2126
|
+
);
|
|
2127
|
+
if (publicActivateResult.success) {
|
|
2128
|
+
result.publicDelegation = {
|
|
2129
|
+
cid: publicSession.delegationCid,
|
|
2130
|
+
delegationHeader: publicSession.delegationHeader,
|
|
2131
|
+
spaceId: publicSpaceId,
|
|
2132
|
+
path: "",
|
|
2133
|
+
actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/metadata"],
|
|
2134
|
+
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
2135
|
+
expiry: expirationTime,
|
|
2136
|
+
delegateDID: params.delegateDID,
|
|
2137
|
+
ownerAddress: session.address,
|
|
2138
|
+
chainId: session.chainId,
|
|
2139
|
+
host: this.config.host
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
return result;
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Use a delegation received from another user.
|
|
2147
|
+
*
|
|
2148
|
+
* This creates a new session key for this user that chains from the
|
|
2149
|
+
* received delegation, allowing operations on the delegator's space.
|
|
2150
|
+
*
|
|
2151
|
+
* Works in both modes:
|
|
2152
|
+
* - **Wallet mode**: Creates a SIWE sub-delegation from PKH to session key
|
|
2153
|
+
* - **Session-only mode**: Uses the delegation directly (must target session key DID)
|
|
2154
|
+
*
|
|
2155
|
+
* @param delegation - The PortableDelegation to use (from createDelegation or transport)
|
|
2156
|
+
* @returns A DelegatedAccess instance for performing operations
|
|
2157
|
+
*/
|
|
2158
|
+
async useDelegation(delegation) {
|
|
2159
|
+
const delegationHeader = delegation.delegationHeader;
|
|
2160
|
+
const targetHost = delegation.host ?? this.config.host;
|
|
2161
|
+
if (this.isSessionOnly) {
|
|
2162
|
+
const myDid = this.did;
|
|
2163
|
+
if (delegation.delegateDID !== myDid) {
|
|
2164
|
+
throw new Error(
|
|
2165
|
+
`Delegation targets ${delegation.delegateDID} but this user's DID is ${myDid}. The delegation must target this user's DID.`
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
const session2 = {
|
|
2169
|
+
address: delegation.ownerAddress,
|
|
2170
|
+
chainId: delegation.chainId,
|
|
2171
|
+
sessionKey: JSON.stringify(this.sessionKeyJwk),
|
|
2172
|
+
spaceId: delegation.spaceId,
|
|
2173
|
+
delegationCid: delegation.cid,
|
|
2174
|
+
delegationHeader,
|
|
2175
|
+
verificationMethod: this.sessionDid,
|
|
2176
|
+
jwk: this.sessionKeyJwk,
|
|
2177
|
+
siwe: "",
|
|
2178
|
+
// Not used in session-only mode
|
|
2179
|
+
signature: ""
|
|
2180
|
+
// Not used in session-only mode
|
|
2181
|
+
};
|
|
2182
|
+
this.trackReceivedDelegation(delegation, this.sessionKeyJwk);
|
|
2183
|
+
return new DelegatedAccess(session2, delegation, targetHost, this.wasmBindings.invoke);
|
|
2184
|
+
}
|
|
2185
|
+
const mySession = this.auth?.tinyCloudSession;
|
|
2186
|
+
if (!mySession) {
|
|
2187
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
2188
|
+
}
|
|
2189
|
+
const jwk = mySession.jwk;
|
|
2190
|
+
const abilities = {};
|
|
2191
|
+
const kvActions = delegation.actions.filter((a) => a.startsWith("tinycloud.kv/"));
|
|
2192
|
+
const sqlActions = delegation.actions.filter((a) => a.startsWith("tinycloud.sql/"));
|
|
2193
|
+
const duckdbActions = delegation.actions.filter((a) => a.startsWith("tinycloud.duckdb/"));
|
|
2194
|
+
if (kvActions.length > 0) {
|
|
2195
|
+
abilities.kv = { [delegation.path]: kvActions };
|
|
2196
|
+
}
|
|
2197
|
+
if (sqlActions.length > 0) {
|
|
2198
|
+
abilities.sql = { [delegation.path]: sqlActions };
|
|
2199
|
+
}
|
|
2200
|
+
if (duckdbActions.length > 0) {
|
|
2201
|
+
abilities.duckdb = { [delegation.path]: duckdbActions };
|
|
2202
|
+
}
|
|
2203
|
+
const now = /* @__PURE__ */ new Date();
|
|
2204
|
+
const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
|
|
2205
|
+
const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
|
|
2206
|
+
const prepared = this.wasmBindings.prepareSession({
|
|
2207
|
+
abilities,
|
|
2208
|
+
address: this.wasmBindings.ensureEip55(mySession.address),
|
|
2209
|
+
chainId: mySession.chainId,
|
|
2210
|
+
domain: new URL(targetHost).hostname,
|
|
2211
|
+
issuedAt: now.toISOString(),
|
|
2212
|
+
expirationTime: expirationTime.toISOString(),
|
|
2213
|
+
spaceId: delegation.spaceId,
|
|
2214
|
+
jwk,
|
|
2215
|
+
parents: [delegation.cid]
|
|
2216
|
+
});
|
|
2217
|
+
const signature = await this.signer.signMessage(prepared.siwe);
|
|
2218
|
+
const invokerSession = this.wasmBindings.completeSessionSetup({
|
|
2219
|
+
...prepared,
|
|
2220
|
+
signature
|
|
2221
|
+
});
|
|
2222
|
+
const activateResult = await activateSessionWithHost2(
|
|
2223
|
+
targetHost,
|
|
2224
|
+
invokerSession.delegationHeader
|
|
2225
|
+
);
|
|
2226
|
+
if (!activateResult.success) {
|
|
2227
|
+
throw new Error(`Failed to activate delegated session: ${activateResult.error}`);
|
|
2228
|
+
}
|
|
2229
|
+
const session = {
|
|
2230
|
+
address: mySession.address,
|
|
2231
|
+
chainId: mySession.chainId,
|
|
2232
|
+
sessionKey: mySession.sessionKey,
|
|
2233
|
+
spaceId: delegation.spaceId,
|
|
2234
|
+
delegationCid: invokerSession.delegationCid,
|
|
2235
|
+
delegationHeader: invokerSession.delegationHeader,
|
|
2236
|
+
verificationMethod: mySession.verificationMethod,
|
|
2237
|
+
jwk,
|
|
2238
|
+
siwe: prepared.siwe,
|
|
2239
|
+
signature
|
|
2240
|
+
};
|
|
2241
|
+
this.trackReceivedDelegation(delegation, jwk);
|
|
2242
|
+
return new DelegatedAccess(session, delegation, targetHost, this.wasmBindings.invoke);
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Create a sub-delegation from a received delegation.
|
|
2246
|
+
*
|
|
2247
|
+
* This allows further delegating access that was received from another user,
|
|
2248
|
+
* if the original delegation allows sub-delegation.
|
|
2249
|
+
*
|
|
2250
|
+
* @param parentDelegation - The delegation received from another user
|
|
2251
|
+
* @param params - Sub-delegation parameters (must be within parent's scope)
|
|
2252
|
+
* @returns A portable delegation for the sub-delegate
|
|
2253
|
+
*/
|
|
2254
|
+
async createSubDelegation(parentDelegation, params) {
|
|
2255
|
+
if (!this.signer) {
|
|
2256
|
+
throw new Error("Cannot createSubDelegation() in session-only mode. Requires wallet mode.");
|
|
2257
|
+
}
|
|
2258
|
+
if (!this._address) {
|
|
2259
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
2260
|
+
}
|
|
2261
|
+
if (parentDelegation.disableSubDelegation) {
|
|
2262
|
+
throw new Error("Parent delegation does not allow sub-delegation");
|
|
2263
|
+
}
|
|
2264
|
+
if (!params.path.startsWith(parentDelegation.path)) {
|
|
2265
|
+
throw new Error(
|
|
2266
|
+
`Sub-delegation path "${params.path}" must be within parent path "${parentDelegation.path}"`
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
const parentActions = new Set(parentDelegation.actions);
|
|
2270
|
+
for (const action of params.actions) {
|
|
2271
|
+
if (!parentActions.has(action)) {
|
|
2272
|
+
throw new Error(
|
|
2273
|
+
`Sub-delegation action "${action}" is not in parent's actions: ${parentDelegation.actions.join(", ")}`
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
const now = /* @__PURE__ */ new Date();
|
|
2278
|
+
const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
|
|
2279
|
+
const requestedExpiry = new Date(now.getTime() + expiryMs);
|
|
2280
|
+
const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
|
|
2281
|
+
const abilities = {};
|
|
2282
|
+
const kvActions = params.actions.filter((a) => a.startsWith("tinycloud.kv/"));
|
|
2283
|
+
const sqlActions = params.actions.filter((a) => a.startsWith("tinycloud.sql/"));
|
|
2284
|
+
const duckdbActions = params.actions.filter((a) => a.startsWith("tinycloud.duckdb/"));
|
|
2285
|
+
if (kvActions.length > 0) {
|
|
2286
|
+
abilities.kv = { [params.path]: kvActions };
|
|
2287
|
+
}
|
|
2288
|
+
if (sqlActions.length > 0) {
|
|
2289
|
+
abilities.sql = { [params.path]: sqlActions };
|
|
2290
|
+
}
|
|
2291
|
+
if (duckdbActions.length > 0) {
|
|
2292
|
+
abilities.duckdb = { [params.path]: duckdbActions };
|
|
2293
|
+
}
|
|
2294
|
+
const targetHost = parentDelegation.host ?? this.config.host;
|
|
2295
|
+
const prepared = this.wasmBindings.prepareSession({
|
|
2296
|
+
abilities,
|
|
2297
|
+
address: this.wasmBindings.ensureEip55(this._address),
|
|
2298
|
+
chainId: this._chainId,
|
|
2299
|
+
domain: new URL(targetHost).hostname,
|
|
2300
|
+
issuedAt: now.toISOString(),
|
|
2301
|
+
expirationTime: actualExpiry.toISOString(),
|
|
2302
|
+
spaceId: parentDelegation.spaceId,
|
|
2303
|
+
delegateUri: params.delegateDID,
|
|
2304
|
+
parents: [parentDelegation.cid]
|
|
2305
|
+
});
|
|
2306
|
+
const signature = await this.signer.signMessage(prepared.siwe);
|
|
2307
|
+
const subDelegationSession = this.wasmBindings.completeSessionSetup({
|
|
2308
|
+
...prepared,
|
|
2309
|
+
signature
|
|
2310
|
+
});
|
|
2311
|
+
const activateResult = await activateSessionWithHost2(
|
|
2312
|
+
targetHost,
|
|
2313
|
+
subDelegationSession.delegationHeader
|
|
2314
|
+
);
|
|
2315
|
+
if (!activateResult.success) {
|
|
2316
|
+
throw new Error(`Failed to activate sub-delegation: ${activateResult.error}`);
|
|
2317
|
+
}
|
|
2318
|
+
return {
|
|
2319
|
+
cid: subDelegationSession.delegationCid,
|
|
2320
|
+
delegationHeader: subDelegationSession.delegationHeader,
|
|
2321
|
+
spaceId: parentDelegation.spaceId,
|
|
2322
|
+
path: params.path,
|
|
2323
|
+
actions: params.actions,
|
|
2324
|
+
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
2325
|
+
expiry: actualExpiry,
|
|
2326
|
+
delegateDID: params.delegateDID,
|
|
2327
|
+
ownerAddress: parentDelegation.ownerAddress,
|
|
2328
|
+
chainId: parentDelegation.chainId,
|
|
2329
|
+
host: targetHost
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
|
|
2334
|
+
// src/delegation.ts
|
|
2335
|
+
function serializeDelegation(delegation) {
|
|
2336
|
+
return JSON.stringify({
|
|
2337
|
+
...delegation,
|
|
2338
|
+
expiry: delegation.expiry.toISOString()
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
function deserializeDelegation(data) {
|
|
2342
|
+
const parsed = JSON.parse(data);
|
|
2343
|
+
return {
|
|
2344
|
+
...parsed,
|
|
2345
|
+
cid: parsed.cid,
|
|
2346
|
+
expiry: new Date(parsed.expiry)
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// src/core.ts
|
|
2351
|
+
import { KVService as KVService3, PrefixedKVService } from "@tinycloud/sdk-core";
|
|
2352
|
+
import { SQLService as SQLService3, SQLAction, DatabaseHandle } from "@tinycloud/sdk-core";
|
|
2353
|
+
import { DuckDbService as DuckDbService3, DuckDbDatabaseHandle, DuckDbAction } from "@tinycloud/sdk-core";
|
|
2354
|
+
import { DataVaultService as DataVaultService2, VaultHeaders, VaultPublicSpaceKVActions, createVaultCrypto as createVaultCrypto2 } from "@tinycloud/sdk-core";
|
|
2355
|
+
import {
|
|
2356
|
+
DelegationManager as DelegationManager2,
|
|
2357
|
+
SharingService as SharingService2,
|
|
2358
|
+
createSharingService,
|
|
2359
|
+
DelegationErrorCodes
|
|
2360
|
+
} from "@tinycloud/sdk-core";
|
|
2361
|
+
import {
|
|
2362
|
+
CapabilityKeyRegistry as CapabilityKeyRegistry2,
|
|
2363
|
+
createCapabilityKeyRegistry,
|
|
2364
|
+
CapabilityKeyRegistryErrorCodes
|
|
2365
|
+
} from "@tinycloud/sdk-core";
|
|
2366
|
+
import {
|
|
2367
|
+
SpaceService as SpaceService2,
|
|
2368
|
+
SpaceErrorCodes,
|
|
2369
|
+
createSpaceService,
|
|
2370
|
+
parseSpaceUri,
|
|
2371
|
+
buildSpaceUri,
|
|
2372
|
+
makePublicSpaceId as makePublicSpaceId2,
|
|
2373
|
+
Space
|
|
2374
|
+
} from "@tinycloud/sdk-core";
|
|
2375
|
+
import {
|
|
2376
|
+
ProtocolMismatchError,
|
|
2377
|
+
VersionCheckError,
|
|
2378
|
+
UnsupportedFeatureError as UnsupportedFeatureError2,
|
|
2379
|
+
checkNodeInfo as checkNodeInfo2
|
|
2380
|
+
} from "@tinycloud/sdk-core";
|
|
2381
|
+
import { ServiceContext as ServiceContext3 } from "@tinycloud/sdk-core";
|
|
2382
|
+
export {
|
|
2383
|
+
AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
|
|
2384
|
+
CapabilityKeyRegistry2 as CapabilityKeyRegistry,
|
|
2385
|
+
CapabilityKeyRegistryErrorCodes,
|
|
2386
|
+
DataVaultService2 as DataVaultService,
|
|
2387
|
+
DatabaseHandle,
|
|
2388
|
+
DelegatedAccess,
|
|
2389
|
+
DelegationErrorCodes,
|
|
2390
|
+
DelegationManager2 as DelegationManager,
|
|
2391
|
+
DuckDbAction,
|
|
2392
|
+
DuckDbDatabaseHandle,
|
|
2393
|
+
DuckDbService3 as DuckDbService,
|
|
2394
|
+
FileSessionStorage,
|
|
2395
|
+
KVService3 as KVService,
|
|
2396
|
+
MemorySessionStorage,
|
|
2397
|
+
NodeUserAuthorization,
|
|
2398
|
+
PrefixedKVService,
|
|
2399
|
+
ProtocolMismatchError,
|
|
2400
|
+
SQLAction,
|
|
2401
|
+
SQLService3 as SQLService,
|
|
2402
|
+
ServiceContext3 as ServiceContext,
|
|
2403
|
+
SharingService2 as SharingService,
|
|
2404
|
+
SilentNotificationHandler2 as SilentNotificationHandler,
|
|
2405
|
+
Space,
|
|
2406
|
+
SpaceErrorCodes,
|
|
2407
|
+
SpaceService2 as SpaceService,
|
|
2408
|
+
TinyCloud2 as TinyCloud,
|
|
2409
|
+
TinyCloudNode,
|
|
2410
|
+
UnsupportedFeatureError2 as UnsupportedFeatureError,
|
|
2411
|
+
VaultHeaders,
|
|
2412
|
+
VaultPublicSpaceKVActions,
|
|
2413
|
+
VersionCheckError,
|
|
2414
|
+
WasmKeyProvider,
|
|
2415
|
+
buildSpaceUri,
|
|
2416
|
+
checkNodeInfo2 as checkNodeInfo,
|
|
2417
|
+
createCapabilityKeyRegistry,
|
|
2418
|
+
createSharingService,
|
|
2419
|
+
createSpaceService,
|
|
2420
|
+
createVaultCrypto2 as createVaultCrypto,
|
|
2421
|
+
createWasmKeyProvider,
|
|
2422
|
+
defaultSignStrategy,
|
|
2423
|
+
defaultSpaceCreationHandler,
|
|
2424
|
+
deserializeDelegation,
|
|
2425
|
+
makePublicSpaceId2 as makePublicSpaceId,
|
|
2426
|
+
parseSpaceUri,
|
|
2427
|
+
serializeDelegation
|
|
2428
|
+
};
|
|
2429
|
+
//# sourceMappingURL=core.js.map
|