@tinycloud/sdk-core 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/index.cjs +3816 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3867 -0
- package/dist/index.d.ts +3867 -0
- package/dist/index.js +3768 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3816 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AutoApproveSpaceCreationHandler: () => AutoApproveSpaceCreationHandler,
|
|
24
|
+
CapabilityKeyRegistry: () => CapabilityKeyRegistry,
|
|
25
|
+
CapabilityKeyRegistryErrorCodes: () => CapabilityKeyRegistryErrorCodes,
|
|
26
|
+
ClientSessionSchema: () => ClientSessionSchema,
|
|
27
|
+
DataVaultService: () => import_sdk_services4.DataVaultService,
|
|
28
|
+
DatabaseHandle: () => import_sdk_services4.DatabaseHandle,
|
|
29
|
+
DelegationErrorCodes: () => DelegationErrorCodes,
|
|
30
|
+
DelegationManager: () => DelegationManager,
|
|
31
|
+
DuckDbAction: () => import_sdk_services4.DuckDbAction,
|
|
32
|
+
DuckDbDatabaseHandle: () => import_sdk_services4.DuckDbDatabaseHandle,
|
|
33
|
+
DuckDbService: () => import_sdk_services4.DuckDbService,
|
|
34
|
+
EnsDataSchema: () => EnsDataSchema,
|
|
35
|
+
ErrorCodes: () => import_sdk_services4.ErrorCodes,
|
|
36
|
+
KVService: () => import_sdk_services4.KVService,
|
|
37
|
+
PrefixedKVService: () => import_sdk_services4.PrefixedKVService,
|
|
38
|
+
ProtocolMismatchError: () => ProtocolMismatchError,
|
|
39
|
+
SQLAction: () => import_sdk_services4.SQLAction,
|
|
40
|
+
SQLService: () => import_sdk_services4.SQLService,
|
|
41
|
+
ServiceContext: () => import_sdk_services4.ServiceContext,
|
|
42
|
+
SharingService: () => SharingService,
|
|
43
|
+
SilentNotificationHandler: () => SilentNotificationHandler,
|
|
44
|
+
SiweConfigSchema: () => SiweConfigSchema,
|
|
45
|
+
SiweMessage: () => import_siwe.SiweMessage,
|
|
46
|
+
Space: () => Space,
|
|
47
|
+
SpaceErrorCodes: () => SpaceErrorCodes,
|
|
48
|
+
SpaceService: () => SpaceService,
|
|
49
|
+
TinyCloud: () => TinyCloud,
|
|
50
|
+
UnsupportedFeatureError: () => UnsupportedFeatureError,
|
|
51
|
+
VaultHeaders: () => import_sdk_services4.VaultHeaders,
|
|
52
|
+
VaultPublicSpaceKVActions: () => import_sdk_services4.VaultPublicSpaceKVActions,
|
|
53
|
+
VersionCheckError: () => VersionCheckError,
|
|
54
|
+
activateSessionWithHost: () => activateSessionWithHost,
|
|
55
|
+
buildSpaceUri: () => buildSpaceUri,
|
|
56
|
+
checkNodeInfo: () => checkNodeInfo,
|
|
57
|
+
createCapabilityKeyRegistry: () => createCapabilityKeyRegistry,
|
|
58
|
+
createSharingService: () => createSharingService,
|
|
59
|
+
createSpaceService: () => createSpaceService,
|
|
60
|
+
createVaultCrypto: () => import_sdk_services4.createVaultCrypto,
|
|
61
|
+
defaultRetryPolicy: () => import_sdk_services4.defaultRetryPolicy,
|
|
62
|
+
defaultSignStrategy: () => defaultSignStrategy,
|
|
63
|
+
defaultSpaceCreationHandler: () => defaultSpaceCreationHandler,
|
|
64
|
+
err: () => import_sdk_services4.err,
|
|
65
|
+
fetchPeerId: () => fetchPeerId,
|
|
66
|
+
makePublicSpaceId: () => makePublicSpaceId,
|
|
67
|
+
ok: () => import_sdk_services4.ok,
|
|
68
|
+
parseSpaceUri: () => parseSpaceUri,
|
|
69
|
+
serviceError: () => import_sdk_services4.serviceError,
|
|
70
|
+
submitHostDelegation: () => submitHostDelegation,
|
|
71
|
+
validateClientSession: () => validateClientSession,
|
|
72
|
+
validatePersistedSessionData: () => validatePersistedSessionData
|
|
73
|
+
});
|
|
74
|
+
module.exports = __toCommonJS(index_exports);
|
|
75
|
+
|
|
76
|
+
// src/client-types.ts
|
|
77
|
+
var import_zod = require("zod");
|
|
78
|
+
var import_siwe = require("siwe");
|
|
79
|
+
var EnsDataSchema = import_zod.z.object({
|
|
80
|
+
domain: import_zod.z.string().nullable().optional(),
|
|
81
|
+
avatarUrl: import_zod.z.string().nullable().optional()
|
|
82
|
+
});
|
|
83
|
+
var SiweConfigSchema = import_zod.z.object({
|
|
84
|
+
domain: import_zod.z.string().optional(),
|
|
85
|
+
uri: import_zod.z.string().optional(),
|
|
86
|
+
chainId: import_zod.z.number().optional(),
|
|
87
|
+
statement: import_zod.z.string().optional(),
|
|
88
|
+
nonce: import_zod.z.string().optional(),
|
|
89
|
+
expirationTime: import_zod.z.string().optional(),
|
|
90
|
+
notBefore: import_zod.z.string().optional(),
|
|
91
|
+
requestId: import_zod.z.string().optional(),
|
|
92
|
+
resources: import_zod.z.array(import_zod.z.string()).optional()
|
|
93
|
+
}).passthrough();
|
|
94
|
+
var ClientSessionSchema = import_zod.z.object({
|
|
95
|
+
address: import_zod.z.string(),
|
|
96
|
+
walletAddress: import_zod.z.string(),
|
|
97
|
+
chainId: import_zod.z.number(),
|
|
98
|
+
sessionKey: import_zod.z.string(),
|
|
99
|
+
siwe: import_zod.z.string(),
|
|
100
|
+
signature: import_zod.z.string(),
|
|
101
|
+
ens: EnsDataSchema.optional()
|
|
102
|
+
});
|
|
103
|
+
function validateClientSession(data) {
|
|
104
|
+
const result = ClientSessionSchema.safeParse(data);
|
|
105
|
+
return result.success ? result.data : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/notifications.ts
|
|
109
|
+
var SilentNotificationHandler = class {
|
|
110
|
+
success() {
|
|
111
|
+
}
|
|
112
|
+
warning() {
|
|
113
|
+
}
|
|
114
|
+
error() {
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/storage.schema.ts
|
|
119
|
+
var import_zod2 = require("zod");
|
|
120
|
+
var ethereumAddressPattern = /^0x[a-fA-F0-9]{40}$/;
|
|
121
|
+
var EnsDataSchema2 = import_zod2.z.object({
|
|
122
|
+
/** ENS name/domain. */
|
|
123
|
+
domain: import_zod2.z.string().nullable().optional(),
|
|
124
|
+
/** ENS avatar URL. */
|
|
125
|
+
avatarUrl: import_zod2.z.string().nullable().optional()
|
|
126
|
+
});
|
|
127
|
+
var PersistedTinyCloudSessionSchema = import_zod2.z.object({
|
|
128
|
+
/** The delegation header containing the UCAN */
|
|
129
|
+
delegationHeader: import_zod2.z.object({
|
|
130
|
+
Authorization: import_zod2.z.string()
|
|
131
|
+
}),
|
|
132
|
+
/** The delegation CID */
|
|
133
|
+
delegationCid: import_zod2.z.string(),
|
|
134
|
+
/** The space ID for this session */
|
|
135
|
+
spaceId: import_zod2.z.string(),
|
|
136
|
+
/** Additional spaces included in this session's capabilities. Key is logical name, value is full spaceId URI */
|
|
137
|
+
spaces: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional(),
|
|
138
|
+
/** The verification method DID */
|
|
139
|
+
verificationMethod: import_zod2.z.string()
|
|
140
|
+
});
|
|
141
|
+
var PersistedSessionDataSchema = import_zod2.z.object({
|
|
142
|
+
/** User's Ethereum address */
|
|
143
|
+
address: import_zod2.z.string().regex(ethereumAddressPattern, "Invalid Ethereum address"),
|
|
144
|
+
/** EIP-155 Chain ID */
|
|
145
|
+
chainId: import_zod2.z.number().int().positive(),
|
|
146
|
+
/** Session key in JWK format (stringified) */
|
|
147
|
+
sessionKey: import_zod2.z.string(),
|
|
148
|
+
/** The signed SIWE message */
|
|
149
|
+
siwe: import_zod2.z.string(),
|
|
150
|
+
/** User's signature of the SIWE message */
|
|
151
|
+
signature: import_zod2.z.string(),
|
|
152
|
+
/** TinyCloud delegation data if available */
|
|
153
|
+
tinycloudSession: PersistedTinyCloudSessionSchema.optional(),
|
|
154
|
+
/** Session expiration timestamp (ISO 8601 with timezone offset) */
|
|
155
|
+
expiresAt: import_zod2.z.string().datetime({ offset: true }),
|
|
156
|
+
/** Session creation timestamp (ISO 8601 with timezone offset) */
|
|
157
|
+
createdAt: import_zod2.z.string().datetime({ offset: true }),
|
|
158
|
+
/** Schema version for migrations */
|
|
159
|
+
version: import_zod2.z.string(),
|
|
160
|
+
/** Optional ENS data */
|
|
161
|
+
ens: EnsDataSchema2.optional()
|
|
162
|
+
});
|
|
163
|
+
var TinyCloudSessionSchema = import_zod2.z.object({
|
|
164
|
+
/** User's Ethereum address */
|
|
165
|
+
address: import_zod2.z.string().regex(ethereumAddressPattern, "Invalid Ethereum address"),
|
|
166
|
+
/** EIP-155 Chain ID */
|
|
167
|
+
chainId: import_zod2.z.number().int().positive(),
|
|
168
|
+
/** Session key ID */
|
|
169
|
+
sessionKey: import_zod2.z.string(),
|
|
170
|
+
/** The space ID for this session */
|
|
171
|
+
spaceId: import_zod2.z.string(),
|
|
172
|
+
/** Additional spaces included in this session's capabilities. Key is logical name, value is full spaceId URI */
|
|
173
|
+
spaces: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional(),
|
|
174
|
+
/** The delegation CID */
|
|
175
|
+
delegationCid: import_zod2.z.string(),
|
|
176
|
+
/** The delegation header for API calls */
|
|
177
|
+
delegationHeader: import_zod2.z.object({
|
|
178
|
+
Authorization: import_zod2.z.string()
|
|
179
|
+
}),
|
|
180
|
+
/** The verification method DID */
|
|
181
|
+
verificationMethod: import_zod2.z.string(),
|
|
182
|
+
/** The session key JWK (required for invoke operations) */
|
|
183
|
+
jwk: import_zod2.z.object({}).passthrough(),
|
|
184
|
+
/** The signed SIWE message */
|
|
185
|
+
siwe: import_zod2.z.string(),
|
|
186
|
+
/** User's signature of the SIWE message */
|
|
187
|
+
signature: import_zod2.z.string()
|
|
188
|
+
});
|
|
189
|
+
function validatePersistedSessionData(data) {
|
|
190
|
+
const result = PersistedSessionDataSchema.safeParse(data);
|
|
191
|
+
if (!result.success) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
error: {
|
|
195
|
+
code: "VALIDATION_ERROR",
|
|
196
|
+
message: result.error.message,
|
|
197
|
+
service: "session",
|
|
198
|
+
meta: { issues: result.error.issues }
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { ok: true, data: result.data };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/TinyCloud.ts
|
|
206
|
+
var import_sdk_services2 = require("@tinycloud/sdk-services");
|
|
207
|
+
|
|
208
|
+
// src/spaces/SpaceService.ts
|
|
209
|
+
var import_sdk_services = require("@tinycloud/sdk-services");
|
|
210
|
+
|
|
211
|
+
// src/spaces/Space.ts
|
|
212
|
+
var Space = class {
|
|
213
|
+
/**
|
|
214
|
+
* Create a new Space instance.
|
|
215
|
+
*
|
|
216
|
+
* @param config - Space configuration
|
|
217
|
+
*/
|
|
218
|
+
constructor(config) {
|
|
219
|
+
this._id = config.id;
|
|
220
|
+
this._name = config.name;
|
|
221
|
+
this._kv = config.createKV(config.id);
|
|
222
|
+
this._delegations = config.createDelegations(config.id);
|
|
223
|
+
this._sharing = config.createSharing(config.id);
|
|
224
|
+
this._getInfo = config.getInfo;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The space identifier (full URI).
|
|
228
|
+
*/
|
|
229
|
+
get id() {
|
|
230
|
+
return this._id;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The short name of the space.
|
|
234
|
+
*/
|
|
235
|
+
get name() {
|
|
236
|
+
return this._name;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* KV operations scoped to this space.
|
|
240
|
+
*/
|
|
241
|
+
get kv() {
|
|
242
|
+
return this._kv;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Delegation operations scoped to this space.
|
|
246
|
+
*/
|
|
247
|
+
get delegations() {
|
|
248
|
+
return this._delegations;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Sharing operations scoped to this space.
|
|
252
|
+
*/
|
|
253
|
+
get sharing() {
|
|
254
|
+
return this._sharing;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get space metadata.
|
|
258
|
+
*
|
|
259
|
+
* @returns Result containing space information
|
|
260
|
+
*/
|
|
261
|
+
async info() {
|
|
262
|
+
return this._getInfo(this._id);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/spaces/spaces.schema.ts
|
|
267
|
+
var import_zod4 = require("zod");
|
|
268
|
+
|
|
269
|
+
// src/delegations/types.schema.ts
|
|
270
|
+
var import_zod3 = require("zod");
|
|
271
|
+
var JWKSchema = import_zod3.z.object({
|
|
272
|
+
/** Key type (e.g., "EC", "RSA", "OKP") */
|
|
273
|
+
kty: import_zod3.z.string(),
|
|
274
|
+
/** Curve for EC/OKP keys (e.g., "P-256", "Ed25519") */
|
|
275
|
+
crv: import_zod3.z.string().optional(),
|
|
276
|
+
/** X coordinate for EC keys, public key for OKP */
|
|
277
|
+
x: import_zod3.z.string().optional(),
|
|
278
|
+
/** Y coordinate for EC keys */
|
|
279
|
+
y: import_zod3.z.string().optional(),
|
|
280
|
+
/** Private key value (d parameter) */
|
|
281
|
+
d: import_zod3.z.string().optional(),
|
|
282
|
+
/** Public exponent for RSA keys */
|
|
283
|
+
e: import_zod3.z.string().optional(),
|
|
284
|
+
/** Modulus for RSA keys */
|
|
285
|
+
n: import_zod3.z.string().optional(),
|
|
286
|
+
/** Key ID */
|
|
287
|
+
kid: import_zod3.z.string().optional(),
|
|
288
|
+
/** Algorithm */
|
|
289
|
+
alg: import_zod3.z.string().optional(),
|
|
290
|
+
/** Key use (e.g., "sig", "enc") */
|
|
291
|
+
use: import_zod3.z.string().optional(),
|
|
292
|
+
/** Key operations (e.g., ["sign", "verify"]) */
|
|
293
|
+
key_ops: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
294
|
+
});
|
|
295
|
+
var KeyTypeSchema = import_zod3.z.enum(["main", "session", "ingested"]);
|
|
296
|
+
var KeyInfoSchema = import_zod3.z.object({
|
|
297
|
+
/** Unique identifier for this key */
|
|
298
|
+
id: import_zod3.z.string(),
|
|
299
|
+
/** DID associated with this key */
|
|
300
|
+
did: import_zod3.z.string(),
|
|
301
|
+
/** Type of key determining its authority level */
|
|
302
|
+
type: KeyTypeSchema,
|
|
303
|
+
/** Private key in JWK format */
|
|
304
|
+
jwk: JWKSchema.optional(),
|
|
305
|
+
/** Priority for key selection (lower = higher priority) */
|
|
306
|
+
priority: import_zod3.z.number()
|
|
307
|
+
});
|
|
308
|
+
var DelegationErrorSchema = import_zod3.z.object({
|
|
309
|
+
/** Error code for programmatic handling */
|
|
310
|
+
code: import_zod3.z.string(),
|
|
311
|
+
/** Human-readable error message */
|
|
312
|
+
message: import_zod3.z.string(),
|
|
313
|
+
/** The service that produced the error */
|
|
314
|
+
service: import_zod3.z.literal("delegation"),
|
|
315
|
+
/** Original error if wrapping another error */
|
|
316
|
+
cause: import_zod3.z.instanceof(Error).optional(),
|
|
317
|
+
/** Additional metadata about the error */
|
|
318
|
+
meta: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()).optional()
|
|
319
|
+
});
|
|
320
|
+
var DelegationErrorCodes = {
|
|
321
|
+
AUTH_REQUIRED: "AUTH_REQUIRED",
|
|
322
|
+
AUTH_EXPIRED: "AUTH_EXPIRED",
|
|
323
|
+
NOT_INITIALIZED: "NOT_INITIALIZED",
|
|
324
|
+
NOT_FOUND: "NOT_FOUND",
|
|
325
|
+
REVOKED: "REVOKED",
|
|
326
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
327
|
+
TIMEOUT: "TIMEOUT",
|
|
328
|
+
ABORTED: "ABORTED",
|
|
329
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
330
|
+
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
331
|
+
CREATION_FAILED: "CREATION_FAILED",
|
|
332
|
+
REVOCATION_FAILED: "REVOCATION_FAILED",
|
|
333
|
+
INVALID_TOKEN: "INVALID_TOKEN",
|
|
334
|
+
KV_SERVICE_UNAVAILABLE: "KV_SERVICE_UNAVAILABLE",
|
|
335
|
+
DATA_FETCH_FAILED: "DATA_FETCH_FAILED",
|
|
336
|
+
VALIDATION_ERROR: "VALIDATION_ERROR"
|
|
337
|
+
};
|
|
338
|
+
var DelegationSchema = import_zod3.z.object({
|
|
339
|
+
/** Content identifier (CID) of the delegation */
|
|
340
|
+
cid: import_zod3.z.string(),
|
|
341
|
+
/** DID of the delegate (the party receiving the delegation) */
|
|
342
|
+
delegateDID: import_zod3.z.string(),
|
|
343
|
+
/** Space ID this delegation applies to */
|
|
344
|
+
spaceId: import_zod3.z.string(),
|
|
345
|
+
/** Resource path this delegation grants access to */
|
|
346
|
+
path: import_zod3.z.string(),
|
|
347
|
+
/** Actions this delegation authorizes */
|
|
348
|
+
actions: import_zod3.z.array(import_zod3.z.string()),
|
|
349
|
+
/** When this delegation expires (accepts Date or ISO string from JSON) */
|
|
350
|
+
expiry: import_zod3.z.coerce.date(),
|
|
351
|
+
/** Whether this delegation has been revoked */
|
|
352
|
+
isRevoked: import_zod3.z.boolean(),
|
|
353
|
+
/** DID of the delegator (the party granting the delegation) */
|
|
354
|
+
delegatorDID: import_zod3.z.string().optional(),
|
|
355
|
+
/** When this delegation was created (accepts Date or ISO string from JSON) */
|
|
356
|
+
createdAt: import_zod3.z.coerce.date().optional(),
|
|
357
|
+
/** Parent delegation CID if this is a sub-delegation */
|
|
358
|
+
parentCid: import_zod3.z.string().optional(),
|
|
359
|
+
/** Whether sub-delegation is allowed */
|
|
360
|
+
allowSubDelegation: import_zod3.z.boolean().optional(),
|
|
361
|
+
/** Authorization header (UCAN bearer token) */
|
|
362
|
+
authHeader: import_zod3.z.string().optional()
|
|
363
|
+
});
|
|
364
|
+
var CapabilityEntrySchema = import_zod3.z.object({
|
|
365
|
+
/** Resource URI this capability applies to */
|
|
366
|
+
resource: import_zod3.z.string(),
|
|
367
|
+
/** Action this capability authorizes */
|
|
368
|
+
action: import_zod3.z.string(),
|
|
369
|
+
/** Keys that can exercise this capability, ordered by priority */
|
|
370
|
+
keys: import_zod3.z.array(KeyInfoSchema),
|
|
371
|
+
/** The delegation that grants this capability */
|
|
372
|
+
delegation: DelegationSchema,
|
|
373
|
+
/** When this capability expires (accepts Date or ISO string from JSON) */
|
|
374
|
+
expiresAt: import_zod3.z.coerce.date().optional()
|
|
375
|
+
});
|
|
376
|
+
var DelegationRecordSchema = import_zod3.z.object({
|
|
377
|
+
/** Content identifier (CID) of the delegation */
|
|
378
|
+
cid: import_zod3.z.string(),
|
|
379
|
+
/** Space ID this delegation applies to */
|
|
380
|
+
spaceId: import_zod3.z.string(),
|
|
381
|
+
/** DID of the delegator (grantor) */
|
|
382
|
+
delegator: import_zod3.z.string(),
|
|
383
|
+
/** DID of the delegatee (recipient) */
|
|
384
|
+
delegatee: import_zod3.z.string(),
|
|
385
|
+
/** Key ID used to sign/exercise this delegation */
|
|
386
|
+
keyId: import_zod3.z.string().optional(),
|
|
387
|
+
/** Resource path pattern this delegation grants access to */
|
|
388
|
+
path: import_zod3.z.string(),
|
|
389
|
+
/** Actions this delegation authorizes */
|
|
390
|
+
actions: import_zod3.z.array(import_zod3.z.string()),
|
|
391
|
+
/** When this delegation expires (accepts Date or ISO string from JSON) */
|
|
392
|
+
expiry: import_zod3.z.coerce.date().optional(),
|
|
393
|
+
/** When this delegation becomes valid (not before) (accepts Date or ISO string) */
|
|
394
|
+
notBefore: import_zod3.z.coerce.date().optional(),
|
|
395
|
+
/** Whether this delegation has been revoked */
|
|
396
|
+
isRevoked: import_zod3.z.boolean(),
|
|
397
|
+
/** When this delegation was created (accepts Date or ISO string from JSON) */
|
|
398
|
+
createdAt: import_zod3.z.coerce.date(),
|
|
399
|
+
/** Parent delegation CID if this is a sub-delegation */
|
|
400
|
+
parentCid: import_zod3.z.string().optional()
|
|
401
|
+
});
|
|
402
|
+
var CreateDelegationParamsSchema = import_zod3.z.object({
|
|
403
|
+
/** DID of the delegate (the party receiving the delegation) */
|
|
404
|
+
delegateDID: import_zod3.z.string(),
|
|
405
|
+
/** Resource path this delegation grants access to */
|
|
406
|
+
path: import_zod3.z.string(),
|
|
407
|
+
/** Actions to authorize */
|
|
408
|
+
actions: import_zod3.z.array(import_zod3.z.string()),
|
|
409
|
+
/** When this delegation expires (accepts Date or ISO string) */
|
|
410
|
+
expiry: import_zod3.z.coerce.date().optional(),
|
|
411
|
+
/** Whether to disable sub-delegation */
|
|
412
|
+
disableSubDelegation: import_zod3.z.boolean().optional(),
|
|
413
|
+
/** Optional statement for the SIWE message */
|
|
414
|
+
statement: import_zod3.z.string().optional()
|
|
415
|
+
});
|
|
416
|
+
var DelegationChainSchema = import_zod3.z.array(DelegationSchema);
|
|
417
|
+
var DelegationChainV2Schema = import_zod3.z.object({
|
|
418
|
+
/** The root delegation from the original authority */
|
|
419
|
+
root: DelegationSchema,
|
|
420
|
+
/** Intermediate delegations in the chain (may be empty) */
|
|
421
|
+
chain: import_zod3.z.array(DelegationSchema),
|
|
422
|
+
/** The final delegation to the current user */
|
|
423
|
+
leaf: DelegationSchema
|
|
424
|
+
});
|
|
425
|
+
var DelegationDirectionSchema = import_zod3.z.enum(["granted", "received", "all"]);
|
|
426
|
+
var DelegationFiltersSchema = import_zod3.z.object({
|
|
427
|
+
/** Filter by delegation direction */
|
|
428
|
+
direction: DelegationDirectionSchema.optional(),
|
|
429
|
+
/** Filter by resource path pattern */
|
|
430
|
+
path: import_zod3.z.string().optional(),
|
|
431
|
+
/** Filter by required actions */
|
|
432
|
+
actions: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
433
|
+
/** Include revoked delegations */
|
|
434
|
+
includeRevoked: import_zod3.z.boolean().optional(),
|
|
435
|
+
/** Filter by delegator DID */
|
|
436
|
+
delegator: import_zod3.z.string().optional(),
|
|
437
|
+
/** Filter by delegatee DID */
|
|
438
|
+
delegatee: import_zod3.z.string().optional(),
|
|
439
|
+
/** Only include delegations valid at this time */
|
|
440
|
+
validAt: import_zod3.z.coerce.date().optional(),
|
|
441
|
+
/** Maximum number of results to return */
|
|
442
|
+
limit: import_zod3.z.number().optional(),
|
|
443
|
+
/** Cursor for pagination */
|
|
444
|
+
cursor: import_zod3.z.string().optional()
|
|
445
|
+
});
|
|
446
|
+
var SpaceOwnershipSchema = import_zod3.z.enum(["owned", "delegated"]);
|
|
447
|
+
var SpaceInfoSchema = import_zod3.z.object({
|
|
448
|
+
/** Space identifier */
|
|
449
|
+
id: import_zod3.z.string(),
|
|
450
|
+
/** Human-readable name for the space */
|
|
451
|
+
name: import_zod3.z.string().optional(),
|
|
452
|
+
/** DID of the space owner */
|
|
453
|
+
owner: import_zod3.z.string(),
|
|
454
|
+
/** Whether user owns or has delegated access */
|
|
455
|
+
type: SpaceOwnershipSchema,
|
|
456
|
+
/** Permissions the user has in this space */
|
|
457
|
+
permissions: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
458
|
+
/** When the access expires (for delegated spaces) */
|
|
459
|
+
expiresAt: import_zod3.z.coerce.date().optional()
|
|
460
|
+
});
|
|
461
|
+
var ShareSchemaSchema = import_zod3.z.enum(["base64", "compact", "ipfs"]);
|
|
462
|
+
var ShareLinkSchema = import_zod3.z.object({
|
|
463
|
+
/** Unique token identifying this share link */
|
|
464
|
+
token: import_zod3.z.string(),
|
|
465
|
+
/** Full URL for sharing */
|
|
466
|
+
url: import_zod3.z.string(),
|
|
467
|
+
/** The delegation this link grants access to */
|
|
468
|
+
delegation: DelegationSchema,
|
|
469
|
+
/** Encoding schema used for the link */
|
|
470
|
+
schema: ShareSchemaSchema,
|
|
471
|
+
/** When this share link expires */
|
|
472
|
+
expiresAt: import_zod3.z.coerce.date().optional(),
|
|
473
|
+
/** Human-readable description of what is being shared */
|
|
474
|
+
description: import_zod3.z.string().optional()
|
|
475
|
+
});
|
|
476
|
+
function createShareLinkDataSchema(dataSchema) {
|
|
477
|
+
return import_zod3.z.object({
|
|
478
|
+
/** The retrieved data */
|
|
479
|
+
data: dataSchema,
|
|
480
|
+
/** The delegation that authorized this access */
|
|
481
|
+
delegation: DelegationSchema,
|
|
482
|
+
/** The space the data belongs to */
|
|
483
|
+
spaceId: import_zod3.z.string(),
|
|
484
|
+
/** The resource path that was accessed */
|
|
485
|
+
path: import_zod3.z.string()
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
var ShareLinkDataSchema = createShareLinkDataSchema(import_zod3.z.unknown());
|
|
489
|
+
var IngestOptionsSchema = import_zod3.z.object({
|
|
490
|
+
/** Whether to persist the delegation to storage */
|
|
491
|
+
persist: import_zod3.z.boolean().optional(),
|
|
492
|
+
/** Whether to validate the full delegation chain */
|
|
493
|
+
validateChain: import_zod3.z.boolean().optional(),
|
|
494
|
+
/** Name for the ingested key */
|
|
495
|
+
keyName: import_zod3.z.string().optional(),
|
|
496
|
+
/** Whether to create a session key for this delegation */
|
|
497
|
+
createSessionKey: import_zod3.z.boolean().optional(),
|
|
498
|
+
/** Override the priority for the ingested key */
|
|
499
|
+
priority: import_zod3.z.number().optional()
|
|
500
|
+
});
|
|
501
|
+
var GenerateShareParamsSchema = import_zod3.z.object({
|
|
502
|
+
/** Resource path to share */
|
|
503
|
+
path: import_zod3.z.string(),
|
|
504
|
+
/** Actions to authorize */
|
|
505
|
+
actions: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
506
|
+
/** When the share link expires */
|
|
507
|
+
expiry: import_zod3.z.coerce.date().optional(),
|
|
508
|
+
/** Encoding schema for the link */
|
|
509
|
+
schema: ShareSchemaSchema.optional(),
|
|
510
|
+
/** Human-readable description */
|
|
511
|
+
description: import_zod3.z.string().optional(),
|
|
512
|
+
/** Base URL for the share link */
|
|
513
|
+
baseUrl: import_zod3.z.string().optional()
|
|
514
|
+
});
|
|
515
|
+
var DelegationManagerConfigSchema = import_zod3.z.object({
|
|
516
|
+
/** TinyCloud host URLs */
|
|
517
|
+
hosts: import_zod3.z.array(import_zod3.z.string()),
|
|
518
|
+
/** Active session for authentication */
|
|
519
|
+
session: import_zod3.z.unknown().refine(
|
|
520
|
+
(val) => val !== null && typeof val === "object",
|
|
521
|
+
{ message: "Expected a ServiceSession object" }
|
|
522
|
+
),
|
|
523
|
+
/** Platform-specific invoke function */
|
|
524
|
+
invoke: import_zod3.z.unknown().refine(
|
|
525
|
+
(val) => typeof val === "function",
|
|
526
|
+
{ message: "Expected an invoke function" }
|
|
527
|
+
),
|
|
528
|
+
/** Optional custom fetch implementation */
|
|
529
|
+
fetch: import_zod3.z.unknown().refine(
|
|
530
|
+
(val) => val === void 0 || typeof val === "function",
|
|
531
|
+
{ message: "Expected a fetch function or undefined" }
|
|
532
|
+
).optional()
|
|
533
|
+
});
|
|
534
|
+
var KeyProviderSchema = import_zod3.z.object({
|
|
535
|
+
/** Generate a new session key, returns key ID */
|
|
536
|
+
createSessionKey: import_zod3.z.unknown().refine(
|
|
537
|
+
(val) => typeof val === "function",
|
|
538
|
+
{ message: "Expected a function" }
|
|
539
|
+
),
|
|
540
|
+
/** Get JWK for a key */
|
|
541
|
+
getJWK: import_zod3.z.unknown().refine(
|
|
542
|
+
(val) => typeof val === "function",
|
|
543
|
+
{ message: "Expected a function" }
|
|
544
|
+
),
|
|
545
|
+
/** Get DID for a key */
|
|
546
|
+
getDID: import_zod3.z.unknown().refine(
|
|
547
|
+
(val) => typeof val === "function",
|
|
548
|
+
{ message: "Expected a function" }
|
|
549
|
+
)
|
|
550
|
+
});
|
|
551
|
+
var DelegationApiResponseSchema = import_zod3.z.object({
|
|
552
|
+
/** SIWE message content */
|
|
553
|
+
siwe: import_zod3.z.string(),
|
|
554
|
+
/** Signature of the SIWE message */
|
|
555
|
+
signature: import_zod3.z.string(),
|
|
556
|
+
/** Delegation version */
|
|
557
|
+
version: import_zod3.z.number(),
|
|
558
|
+
/** CID of the created delegation */
|
|
559
|
+
cid: import_zod3.z.string().optional()
|
|
560
|
+
});
|
|
561
|
+
var CreateDelegationWasmParamsSchema = import_zod3.z.object({
|
|
562
|
+
/** The session containing delegation credentials */
|
|
563
|
+
session: import_zod3.z.unknown().refine(
|
|
564
|
+
(val) => val !== null && typeof val === "object",
|
|
565
|
+
{ message: "Expected a ServiceSession object" }
|
|
566
|
+
),
|
|
567
|
+
/** DID of the delegate */
|
|
568
|
+
delegateDID: import_zod3.z.string(),
|
|
569
|
+
/** Space ID this delegation applies to */
|
|
570
|
+
spaceId: import_zod3.z.string(),
|
|
571
|
+
/** Resource path this delegation grants access to */
|
|
572
|
+
path: import_zod3.z.string(),
|
|
573
|
+
/** Actions to authorize */
|
|
574
|
+
actions: import_zod3.z.array(import_zod3.z.string()),
|
|
575
|
+
/** Expiration time in seconds since Unix epoch */
|
|
576
|
+
expirationSecs: import_zod3.z.number(),
|
|
577
|
+
/** Optional not-before time in seconds since Unix epoch */
|
|
578
|
+
notBeforeSecs: import_zod3.z.number().optional()
|
|
579
|
+
});
|
|
580
|
+
var CreateDelegationWasmResultSchema = import_zod3.z.object({
|
|
581
|
+
/** Base64url-encoded UCAN delegation */
|
|
582
|
+
delegation: import_zod3.z.string(),
|
|
583
|
+
/** CID of the delegation */
|
|
584
|
+
cid: import_zod3.z.string(),
|
|
585
|
+
/** DID of the delegate */
|
|
586
|
+
delegateDID: import_zod3.z.string(),
|
|
587
|
+
/** Resource path the delegation grants access to */
|
|
588
|
+
path: import_zod3.z.string(),
|
|
589
|
+
/** Actions the delegation authorizes */
|
|
590
|
+
actions: import_zod3.z.array(import_zod3.z.string()),
|
|
591
|
+
/** Expiration time */
|
|
592
|
+
expiry: import_zod3.z.coerce.date()
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// src/spaces/spaces.schema.ts
|
|
596
|
+
var SpaceConfigSchema = import_zod4.z.object({
|
|
597
|
+
/** The space identifier (full URI) */
|
|
598
|
+
id: import_zod4.z.string(),
|
|
599
|
+
/** The short name of the space */
|
|
600
|
+
name: import_zod4.z.string(),
|
|
601
|
+
/** Factory function to create a space-scoped KV service */
|
|
602
|
+
createKV: import_zod4.z.function(),
|
|
603
|
+
/** Factory function to create space-scoped delegations */
|
|
604
|
+
createDelegations: import_zod4.z.function(),
|
|
605
|
+
/** Factory function to create space-scoped sharing */
|
|
606
|
+
createSharing: import_zod4.z.function(),
|
|
607
|
+
/** Function to get space info */
|
|
608
|
+
getInfo: import_zod4.z.function()
|
|
609
|
+
});
|
|
610
|
+
var SpaceServiceConfigSchema = import_zod4.z.object({
|
|
611
|
+
/** TinyCloud host URLs */
|
|
612
|
+
hosts: import_zod4.z.array(import_zod4.z.string()),
|
|
613
|
+
/** Active session for authentication */
|
|
614
|
+
session: import_zod4.z.unknown(),
|
|
615
|
+
/** Platform-specific invoke function */
|
|
616
|
+
invoke: import_zod4.z.function(),
|
|
617
|
+
/** Optional custom fetch implementation */
|
|
618
|
+
fetch: import_zod4.z.function().optional(),
|
|
619
|
+
/** Optional capability key registry for delegated space discovery */
|
|
620
|
+
capabilityRegistry: import_zod4.z.unknown().optional(),
|
|
621
|
+
/** Factory function to create a space-scoped KV service */
|
|
622
|
+
createKVService: import_zod4.z.function().optional(),
|
|
623
|
+
/** User's PKH DID (derived from address or provided explicitly) */
|
|
624
|
+
userDid: import_zod4.z.string().optional(),
|
|
625
|
+
/** Optional SharingService for v2 sharing links (client-side) */
|
|
626
|
+
sharingService: import_zod4.z.unknown().optional(),
|
|
627
|
+
/** Factory function to create delegations using SIWE-based flow */
|
|
628
|
+
createDelegation: import_zod4.z.function().optional()
|
|
629
|
+
});
|
|
630
|
+
var SpaceDelegationParamsSchema = CreateDelegationParamsSchema.extend({
|
|
631
|
+
/** The space ID to create the delegation for */
|
|
632
|
+
spaceId: import_zod4.z.string()
|
|
633
|
+
});
|
|
634
|
+
var ServerDelegationInfoSchema = import_zod4.z.object({
|
|
635
|
+
/** DID of the delegator */
|
|
636
|
+
delegator: import_zod4.z.string(),
|
|
637
|
+
/** DID of the delegate */
|
|
638
|
+
delegate: import_zod4.z.string(),
|
|
639
|
+
/** Parent delegation CIDs - accepts string or byte array format from server */
|
|
640
|
+
parents: import_zod4.z.array(import_zod4.z.union([import_zod4.z.string(), import_zod4.z.array(import_zod4.z.number())])),
|
|
641
|
+
/** Expiration time (ISO8601 string) */
|
|
642
|
+
expiry: import_zod4.z.string().optional(),
|
|
643
|
+
/** Not-before time (ISO8601 string) */
|
|
644
|
+
not_before: import_zod4.z.string().optional(),
|
|
645
|
+
/** Issued-at time (ISO8601 string) */
|
|
646
|
+
issued_at: import_zod4.z.string().optional(),
|
|
647
|
+
/** Capabilities granted by this delegation */
|
|
648
|
+
capabilities: import_zod4.z.array(
|
|
649
|
+
import_zod4.z.object({
|
|
650
|
+
resource: import_zod4.z.string(),
|
|
651
|
+
ability: import_zod4.z.string()
|
|
652
|
+
})
|
|
653
|
+
)
|
|
654
|
+
});
|
|
655
|
+
var ServerDelegationsResponseSchema = import_zod4.z.record(
|
|
656
|
+
import_zod4.z.string(),
|
|
657
|
+
ServerDelegationInfoSchema
|
|
658
|
+
);
|
|
659
|
+
var ServerOwnedSpaceSchema = import_zod4.z.object({
|
|
660
|
+
/** Space identifier */
|
|
661
|
+
id: import_zod4.z.string(),
|
|
662
|
+
/** Space name (optional, can be derived from id) */
|
|
663
|
+
name: import_zod4.z.string().optional(),
|
|
664
|
+
/** Owner DID */
|
|
665
|
+
owner: import_zod4.z.string(),
|
|
666
|
+
/** Creation timestamp */
|
|
667
|
+
createdAt: import_zod4.z.string().optional()
|
|
668
|
+
});
|
|
669
|
+
var ServerOwnedSpacesResponseSchema = import_zod4.z.array(ServerOwnedSpaceSchema);
|
|
670
|
+
var ServerCreateSpaceResponseSchema = import_zod4.z.object({
|
|
671
|
+
/** Space identifier */
|
|
672
|
+
id: import_zod4.z.string(),
|
|
673
|
+
/** Space name */
|
|
674
|
+
name: import_zod4.z.string(),
|
|
675
|
+
/** Owner DID */
|
|
676
|
+
owner: import_zod4.z.string(),
|
|
677
|
+
/** Creation timestamp */
|
|
678
|
+
createdAt: import_zod4.z.string().optional()
|
|
679
|
+
});
|
|
680
|
+
var ServerSpaceInfoResponseSchema = import_zod4.z.object({
|
|
681
|
+
/** Space identifier */
|
|
682
|
+
id: import_zod4.z.string(),
|
|
683
|
+
/** Space name (optional) */
|
|
684
|
+
name: import_zod4.z.string().optional(),
|
|
685
|
+
/** Owner DID */
|
|
686
|
+
owner: import_zod4.z.string(),
|
|
687
|
+
/** Ownership type */
|
|
688
|
+
type: import_zod4.z.enum(["owned", "delegated"]).optional(),
|
|
689
|
+
/** Permissions the user has in this space */
|
|
690
|
+
permissions: import_zod4.z.array(import_zod4.z.string()).optional(),
|
|
691
|
+
/** Expiration for delegated access */
|
|
692
|
+
expiresAt: import_zod4.z.string().optional()
|
|
693
|
+
});
|
|
694
|
+
function validateServerDelegationsResponse(data) {
|
|
695
|
+
if (data === null || data === void 0) {
|
|
696
|
+
return { ok: true, data: {} };
|
|
697
|
+
}
|
|
698
|
+
if (Array.isArray(data)) {
|
|
699
|
+
return { ok: true, data: {} };
|
|
700
|
+
}
|
|
701
|
+
const result = ServerDelegationsResponseSchema.safeParse(data);
|
|
702
|
+
if (!result.success) {
|
|
703
|
+
return {
|
|
704
|
+
ok: false,
|
|
705
|
+
error: {
|
|
706
|
+
code: "VALIDATION_ERROR",
|
|
707
|
+
message: `Invalid server delegations response: ${result.error.message}`,
|
|
708
|
+
service: "space",
|
|
709
|
+
meta: { issues: result.error.issues }
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
return { ok: true, data: result.data };
|
|
714
|
+
}
|
|
715
|
+
function validateServerOwnedSpacesResponse(data) {
|
|
716
|
+
const result = ServerOwnedSpacesResponseSchema.safeParse(data);
|
|
717
|
+
if (!result.success) {
|
|
718
|
+
return {
|
|
719
|
+
ok: false,
|
|
720
|
+
error: {
|
|
721
|
+
code: "VALIDATION_ERROR",
|
|
722
|
+
message: `Invalid server owned spaces response: ${result.error.message}`,
|
|
723
|
+
service: "space",
|
|
724
|
+
meta: { issues: result.error.issues }
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
return { ok: true, data: result.data };
|
|
729
|
+
}
|
|
730
|
+
function validateServerCreateSpaceResponse(data) {
|
|
731
|
+
const result = ServerCreateSpaceResponseSchema.safeParse(data);
|
|
732
|
+
if (!result.success) {
|
|
733
|
+
return {
|
|
734
|
+
ok: false,
|
|
735
|
+
error: {
|
|
736
|
+
code: "VALIDATION_ERROR",
|
|
737
|
+
message: `Invalid server create space response: ${result.error.message}`,
|
|
738
|
+
service: "space",
|
|
739
|
+
meta: { issues: result.error.issues }
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
return { ok: true, data: result.data };
|
|
744
|
+
}
|
|
745
|
+
function validateServerSpaceInfoResponse(data) {
|
|
746
|
+
const result = ServerSpaceInfoResponseSchema.safeParse(data);
|
|
747
|
+
if (!result.success) {
|
|
748
|
+
return {
|
|
749
|
+
ok: false,
|
|
750
|
+
error: {
|
|
751
|
+
code: "VALIDATION_ERROR",
|
|
752
|
+
message: `Invalid server space info response: ${result.error.message}`,
|
|
753
|
+
service: "space",
|
|
754
|
+
meta: { issues: result.error.issues }
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return { ok: true, data: result.data };
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/spaces/SpaceService.ts
|
|
762
|
+
var SERVICE_NAME = "space";
|
|
763
|
+
var SpaceErrorCodes = {
|
|
764
|
+
/** Space not found */
|
|
765
|
+
NOT_FOUND: "SPACE_NOT_FOUND",
|
|
766
|
+
/** Space already exists */
|
|
767
|
+
ALREADY_EXISTS: "SPACE_ALREADY_EXISTS",
|
|
768
|
+
/** Creation failed */
|
|
769
|
+
CREATION_FAILED: "SPACE_CREATION_FAILED",
|
|
770
|
+
/** Authentication required */
|
|
771
|
+
AUTH_REQUIRED: "AUTH_REQUIRED",
|
|
772
|
+
/** Invalid space name or URI */
|
|
773
|
+
INVALID_NAME: "INVALID_SPACE_NAME",
|
|
774
|
+
/** Network error */
|
|
775
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
776
|
+
/** Not initialized */
|
|
777
|
+
NOT_INITIALIZED: "NOT_INITIALIZED"
|
|
778
|
+
};
|
|
779
|
+
function makePublicSpaceId(address, chainId) {
|
|
780
|
+
return `tinycloud:pkh:eip155:${chainId}:${address}:public`;
|
|
781
|
+
}
|
|
782
|
+
function parseSpaceUri(uri) {
|
|
783
|
+
const fullUriMatch = uri.match(
|
|
784
|
+
/^tinycloud:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40}):(.+)$/
|
|
785
|
+
);
|
|
786
|
+
if (fullUriMatch) {
|
|
787
|
+
const [, chainId, address, name] = fullUriMatch;
|
|
788
|
+
return {
|
|
789
|
+
owner: `did:pkh:eip155:${chainId}:${address}`,
|
|
790
|
+
name,
|
|
791
|
+
chainId,
|
|
792
|
+
address
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
if (/^[a-zA-Z0-9_-]+$/.test(uri)) {
|
|
796
|
+
return {
|
|
797
|
+
owner: "",
|
|
798
|
+
// Will be filled in from session
|
|
799
|
+
name: uri
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
function buildSpaceUri(owner, name) {
|
|
805
|
+
const pkhMatch = owner.match(/^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/);
|
|
806
|
+
if (pkhMatch) {
|
|
807
|
+
const [, chainId, address] = pkhMatch;
|
|
808
|
+
return `tinycloud:pkh:eip155:${chainId}:${address}:${name}`;
|
|
809
|
+
}
|
|
810
|
+
return `tinycloud:${owner}:${name}`;
|
|
811
|
+
}
|
|
812
|
+
function transformServerDelegations(validatedData, defaultSpaceId) {
|
|
813
|
+
const result = [];
|
|
814
|
+
for (const [cid, info] of Object.entries(validatedData)) {
|
|
815
|
+
const capabilities = info.capabilities;
|
|
816
|
+
let path = "";
|
|
817
|
+
let spaceId = defaultSpaceId;
|
|
818
|
+
const actions = [];
|
|
819
|
+
for (const cap of capabilities) {
|
|
820
|
+
actions.push(cap.ability);
|
|
821
|
+
const resourceMatch = cap.resource.match(
|
|
822
|
+
/^(tinycloud:pkh:eip155:\d+:0x[a-fA-F0-9]+:[^/]+)\/[^/]+\/(.*)$/
|
|
823
|
+
);
|
|
824
|
+
if (resourceMatch) {
|
|
825
|
+
spaceId = resourceMatch[1];
|
|
826
|
+
path = resourceMatch[2] || "";
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const firstStringParent = info.parents?.find((p) => typeof p === "string");
|
|
830
|
+
result.push({
|
|
831
|
+
cid,
|
|
832
|
+
delegateDID: info.delegate,
|
|
833
|
+
delegatorDID: info.delegator,
|
|
834
|
+
spaceId,
|
|
835
|
+
path,
|
|
836
|
+
actions,
|
|
837
|
+
expiry: info.expiry ? new Date(info.expiry) : new Date(Date.now() + 24 * 60 * 60 * 1e3),
|
|
838
|
+
isRevoked: false,
|
|
839
|
+
createdAt: info.issued_at ? new Date(info.issued_at) : void 0,
|
|
840
|
+
parentCid: firstStringParent
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
return result;
|
|
844
|
+
}
|
|
845
|
+
var SpaceService = class {
|
|
846
|
+
/**
|
|
847
|
+
* Create a new SpaceService instance.
|
|
848
|
+
*
|
|
849
|
+
* @param config - Service configuration
|
|
850
|
+
*/
|
|
851
|
+
constructor(config) {
|
|
852
|
+
/** Cache of created Space objects */
|
|
853
|
+
this.spaceCache = /* @__PURE__ */ new Map();
|
|
854
|
+
/** Cache of space info */
|
|
855
|
+
this.infoCache = /* @__PURE__ */ new Map();
|
|
856
|
+
/** Cache TTL in milliseconds (5 minutes) */
|
|
857
|
+
this.cacheTTL = 5 * 60 * 1e3;
|
|
858
|
+
this.hosts = config.hosts;
|
|
859
|
+
this.session = config.session;
|
|
860
|
+
this.invoke = config.invoke;
|
|
861
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
862
|
+
this.capabilityRegistry = config.capabilityRegistry;
|
|
863
|
+
this.createKVServiceFn = config.createKVService;
|
|
864
|
+
this._userDid = config.userDid;
|
|
865
|
+
this.sharingService = config.sharingService;
|
|
866
|
+
this.createDelegationFn = config.createDelegation;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Update the service configuration.
|
|
870
|
+
*/
|
|
871
|
+
updateConfig(config) {
|
|
872
|
+
if (config.hosts) this.hosts = config.hosts;
|
|
873
|
+
if (config.session) this.session = config.session;
|
|
874
|
+
if (config.invoke) this.invoke = config.invoke;
|
|
875
|
+
if (config.fetch) this.fetchFn = config.fetch;
|
|
876
|
+
if (config.capabilityRegistry) this.capabilityRegistry = config.capabilityRegistry;
|
|
877
|
+
if (config.createKVService) this.createKVServiceFn = config.createKVService;
|
|
878
|
+
if (config.userDid !== void 0) this._userDid = config.userDid;
|
|
879
|
+
if (config.sharingService) this.sharingService = config.sharingService;
|
|
880
|
+
if (config.createDelegation) this.createDelegationFn = config.createDelegation;
|
|
881
|
+
this.spaceCache.clear();
|
|
882
|
+
this.infoCache.clear();
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Get the current user's primary space ID.
|
|
886
|
+
*/
|
|
887
|
+
getCurrentSpaceId() {
|
|
888
|
+
return this.session?.spaceId;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Get the primary host URL.
|
|
892
|
+
*/
|
|
893
|
+
get host() {
|
|
894
|
+
return this.hosts[0];
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Get the current user's PKH DID.
|
|
898
|
+
*/
|
|
899
|
+
get userDid() {
|
|
900
|
+
if (this._userDid) {
|
|
901
|
+
return this._userDid;
|
|
902
|
+
}
|
|
903
|
+
return void 0;
|
|
904
|
+
}
|
|
905
|
+
// ===========================================================================
|
|
906
|
+
// List Spaces
|
|
907
|
+
// ===========================================================================
|
|
908
|
+
/**
|
|
909
|
+
* List all spaces the user has access to.
|
|
910
|
+
*
|
|
911
|
+
* Combines owned spaces (from the server) with delegated spaces
|
|
912
|
+
* (from the capability registry).
|
|
913
|
+
*/
|
|
914
|
+
async list() {
|
|
915
|
+
if (!this.session) {
|
|
916
|
+
return (0, import_sdk_services.err)(
|
|
917
|
+
(0, import_sdk_services.serviceError)(SpaceErrorCodes.AUTH_REQUIRED, "Authentication required", SERVICE_NAME)
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
try {
|
|
921
|
+
const spaces = [];
|
|
922
|
+
const ownedResult = await this.listOwnedSpaces();
|
|
923
|
+
if (ownedResult.ok) {
|
|
924
|
+
spaces.push(...ownedResult.data);
|
|
925
|
+
}
|
|
926
|
+
if (this.capabilityRegistry) {
|
|
927
|
+
const delegatedSpaces = this.discoverDelegatedSpaces();
|
|
928
|
+
spaces.push(...delegatedSpaces);
|
|
929
|
+
}
|
|
930
|
+
const uniqueSpaces = this.deduplicateSpaces(spaces);
|
|
931
|
+
return (0, import_sdk_services.ok)(uniqueSpaces);
|
|
932
|
+
} catch (error) {
|
|
933
|
+
return (0, import_sdk_services.err)(
|
|
934
|
+
(0, import_sdk_services.serviceError)(
|
|
935
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
936
|
+
`Failed to list spaces: ${String(error)}`,
|
|
937
|
+
SERVICE_NAME,
|
|
938
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
939
|
+
)
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* List owned spaces from the server.
|
|
945
|
+
*/
|
|
946
|
+
async listOwnedSpaces() {
|
|
947
|
+
try {
|
|
948
|
+
const headers = this.invoke(this.session, "space", "", "tinycloud.space/list");
|
|
949
|
+
const response = await this.fetchFn(`${this.host}/invoke`, {
|
|
950
|
+
method: "POST",
|
|
951
|
+
headers
|
|
952
|
+
});
|
|
953
|
+
if (!response.ok) {
|
|
954
|
+
const errorText = await response.text();
|
|
955
|
+
return (0, import_sdk_services.err)(
|
|
956
|
+
(0, import_sdk_services.serviceError)(
|
|
957
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
958
|
+
`Failed to list owned spaces: ${response.status} - ${errorText}`,
|
|
959
|
+
SERVICE_NAME,
|
|
960
|
+
{ meta: { status: response.status } }
|
|
961
|
+
)
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
const rawData = await response.json();
|
|
965
|
+
const validationResult = validateServerOwnedSpacesResponse(rawData);
|
|
966
|
+
if (!validationResult.ok) {
|
|
967
|
+
return (0, import_sdk_services.err)(
|
|
968
|
+
(0, import_sdk_services.serviceError)(
|
|
969
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
970
|
+
validationResult.error.message,
|
|
971
|
+
SERVICE_NAME,
|
|
972
|
+
{ meta: validationResult.error.meta }
|
|
973
|
+
)
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
const spaces = validationResult.data.map((item) => ({
|
|
977
|
+
id: item.id,
|
|
978
|
+
name: item.name ?? this.extractNameFromId(item.id),
|
|
979
|
+
owner: item.owner,
|
|
980
|
+
type: "owned",
|
|
981
|
+
permissions: ["*"]
|
|
982
|
+
// Full permissions for owned spaces
|
|
983
|
+
}));
|
|
984
|
+
return (0, import_sdk_services.ok)(spaces);
|
|
985
|
+
} catch (error) {
|
|
986
|
+
return (0, import_sdk_services.err)(
|
|
987
|
+
(0, import_sdk_services.serviceError)(
|
|
988
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
989
|
+
`Network error listing owned spaces: ${String(error)}`,
|
|
990
|
+
SERVICE_NAME,
|
|
991
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
992
|
+
)
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Discover delegated spaces from the capability registry.
|
|
998
|
+
*/
|
|
999
|
+
discoverDelegatedSpaces() {
|
|
1000
|
+
if (!this.capabilityRegistry) {
|
|
1001
|
+
return [];
|
|
1002
|
+
}
|
|
1003
|
+
const spaces = /* @__PURE__ */ new Map();
|
|
1004
|
+
const capabilities = this.capabilityRegistry.getAllCapabilities();
|
|
1005
|
+
for (const capability of capabilities) {
|
|
1006
|
+
const spaceId = capability.delegation.spaceId;
|
|
1007
|
+
if (spaces.has(spaceId)) {
|
|
1008
|
+
const existing = spaces.get(spaceId);
|
|
1009
|
+
if (existing.permissions) {
|
|
1010
|
+
const actions = capability.delegation.actions;
|
|
1011
|
+
for (const action of actions) {
|
|
1012
|
+
if (!existing.permissions.includes(action)) {
|
|
1013
|
+
existing.permissions.push(action);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (spaceId === this.session?.spaceId) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const parsed = parseSpaceUri(spaceId);
|
|
1023
|
+
spaces.set(spaceId, {
|
|
1024
|
+
id: spaceId,
|
|
1025
|
+
name: parsed?.name ?? this.extractNameFromId(spaceId),
|
|
1026
|
+
owner: capability.delegation.delegatorDID ?? parsed?.owner ?? "",
|
|
1027
|
+
type: "delegated",
|
|
1028
|
+
permissions: [...capability.delegation.actions],
|
|
1029
|
+
expiresAt: capability.expiresAt
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
return Array.from(spaces.values());
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Extract space name from a full space ID.
|
|
1036
|
+
*/
|
|
1037
|
+
extractNameFromId(id) {
|
|
1038
|
+
const parsed = parseSpaceUri(id);
|
|
1039
|
+
if (parsed) {
|
|
1040
|
+
return parsed.name;
|
|
1041
|
+
}
|
|
1042
|
+
const parts = id.split(":");
|
|
1043
|
+
return parts[parts.length - 1] || id;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Deduplicate spaces, preferring owned over delegated.
|
|
1047
|
+
*/
|
|
1048
|
+
deduplicateSpaces(spaces) {
|
|
1049
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1050
|
+
for (const space of spaces) {
|
|
1051
|
+
const existing = seen.get(space.id);
|
|
1052
|
+
if (!existing || existing.type === "delegated" && space.type === "owned") {
|
|
1053
|
+
seen.set(space.id, space);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return Array.from(seen.values());
|
|
1057
|
+
}
|
|
1058
|
+
// ===========================================================================
|
|
1059
|
+
// Create Space
|
|
1060
|
+
// ===========================================================================
|
|
1061
|
+
/**
|
|
1062
|
+
* Create a new space.
|
|
1063
|
+
*
|
|
1064
|
+
* @param name - The name for the new space
|
|
1065
|
+
*/
|
|
1066
|
+
async create(name) {
|
|
1067
|
+
if (!this.session) {
|
|
1068
|
+
return (0, import_sdk_services.err)(
|
|
1069
|
+
(0, import_sdk_services.serviceError)(SpaceErrorCodes.AUTH_REQUIRED, "Authentication required", SERVICE_NAME)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
1073
|
+
return (0, import_sdk_services.err)(
|
|
1074
|
+
(0, import_sdk_services.serviceError)(
|
|
1075
|
+
SpaceErrorCodes.INVALID_NAME,
|
|
1076
|
+
"Space name must contain only alphanumeric characters, underscores, and hyphens",
|
|
1077
|
+
SERVICE_NAME
|
|
1078
|
+
)
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
try {
|
|
1082
|
+
const headers = this.invoke(this.session, "space", name, "tinycloud.space/create");
|
|
1083
|
+
const response = await this.fetchFn(`${this.host}/invoke`, {
|
|
1084
|
+
method: "POST",
|
|
1085
|
+
headers,
|
|
1086
|
+
body: JSON.stringify({ name })
|
|
1087
|
+
});
|
|
1088
|
+
if (!response.ok) {
|
|
1089
|
+
const errorText = await response.text();
|
|
1090
|
+
if (response.status === 409) {
|
|
1091
|
+
return (0, import_sdk_services.err)(
|
|
1092
|
+
(0, import_sdk_services.serviceError)(
|
|
1093
|
+
SpaceErrorCodes.ALREADY_EXISTS,
|
|
1094
|
+
`Space "${name}" already exists`,
|
|
1095
|
+
SERVICE_NAME
|
|
1096
|
+
)
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
return (0, import_sdk_services.err)(
|
|
1100
|
+
(0, import_sdk_services.serviceError)(
|
|
1101
|
+
SpaceErrorCodes.CREATION_FAILED,
|
|
1102
|
+
`Failed to create space: ${response.status} - ${errorText}`,
|
|
1103
|
+
SERVICE_NAME,
|
|
1104
|
+
{ meta: { status: response.status } }
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
const rawData = await response.json();
|
|
1109
|
+
const validationResult = validateServerCreateSpaceResponse(rawData);
|
|
1110
|
+
if (!validationResult.ok) {
|
|
1111
|
+
return (0, import_sdk_services.err)(
|
|
1112
|
+
(0, import_sdk_services.serviceError)(
|
|
1113
|
+
SpaceErrorCodes.CREATION_FAILED,
|
|
1114
|
+
validationResult.error.message,
|
|
1115
|
+
SERVICE_NAME,
|
|
1116
|
+
{ meta: validationResult.error.meta }
|
|
1117
|
+
)
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
const spaceInfo = {
|
|
1121
|
+
id: validationResult.data.id,
|
|
1122
|
+
name: validationResult.data.name || name,
|
|
1123
|
+
owner: validationResult.data.owner || this.userDid || "",
|
|
1124
|
+
type: "owned",
|
|
1125
|
+
permissions: ["*"]
|
|
1126
|
+
};
|
|
1127
|
+
this.infoCache.set(spaceInfo.id, { info: spaceInfo, cachedAt: Date.now() });
|
|
1128
|
+
return (0, import_sdk_services.ok)(spaceInfo);
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
return (0, import_sdk_services.err)(
|
|
1131
|
+
(0, import_sdk_services.serviceError)(
|
|
1132
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1133
|
+
`Network error creating space: ${String(error)}`,
|
|
1134
|
+
SERVICE_NAME,
|
|
1135
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
// ===========================================================================
|
|
1141
|
+
// Get Space
|
|
1142
|
+
// ===========================================================================
|
|
1143
|
+
/**
|
|
1144
|
+
* Get a Space object by name or full URI.
|
|
1145
|
+
*
|
|
1146
|
+
* @param nameOrUri - Short name or full URI
|
|
1147
|
+
*/
|
|
1148
|
+
get(nameOrUri) {
|
|
1149
|
+
const spaceId = this.resolveSpaceId(nameOrUri);
|
|
1150
|
+
const cached = this.spaceCache.get(spaceId);
|
|
1151
|
+
if (cached) {
|
|
1152
|
+
return cached;
|
|
1153
|
+
}
|
|
1154
|
+
const parsed = parseSpaceUri(spaceId);
|
|
1155
|
+
const name = parsed?.name ?? this.extractNameFromId(spaceId);
|
|
1156
|
+
const config = {
|
|
1157
|
+
id: spaceId,
|
|
1158
|
+
name,
|
|
1159
|
+
createKV: this.createSpaceScopedKV.bind(this),
|
|
1160
|
+
createDelegations: this.createSpaceScopedDelegations.bind(this),
|
|
1161
|
+
createSharing: this.createSpaceScopedSharing.bind(this),
|
|
1162
|
+
getInfo: this.getSpaceInfo.bind(this)
|
|
1163
|
+
};
|
|
1164
|
+
const space = new Space(config);
|
|
1165
|
+
this.spaceCache.set(spaceId, space);
|
|
1166
|
+
return space;
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Resolve a name or URI to a full space ID.
|
|
1170
|
+
*/
|
|
1171
|
+
resolveSpaceId(nameOrUri) {
|
|
1172
|
+
const parsed = parseSpaceUri(nameOrUri);
|
|
1173
|
+
if (!parsed) {
|
|
1174
|
+
return nameOrUri;
|
|
1175
|
+
}
|
|
1176
|
+
if (parsed.owner) {
|
|
1177
|
+
return nameOrUri;
|
|
1178
|
+
}
|
|
1179
|
+
if (this.userDid) {
|
|
1180
|
+
return buildSpaceUri(this.userDid, parsed.name);
|
|
1181
|
+
}
|
|
1182
|
+
return nameOrUri;
|
|
1183
|
+
}
|
|
1184
|
+
// ===========================================================================
|
|
1185
|
+
// Exists Check
|
|
1186
|
+
// ===========================================================================
|
|
1187
|
+
/**
|
|
1188
|
+
* Check if a space exists and the user has access.
|
|
1189
|
+
*/
|
|
1190
|
+
async exists(nameOrUri) {
|
|
1191
|
+
const spaceId = this.resolveSpaceId(nameOrUri);
|
|
1192
|
+
const cached = this.infoCache.get(spaceId);
|
|
1193
|
+
if (cached && Date.now() - cached.cachedAt < this.cacheTTL) {
|
|
1194
|
+
return (0, import_sdk_services.ok)(true);
|
|
1195
|
+
}
|
|
1196
|
+
const infoResult = await this.getSpaceInfo(spaceId);
|
|
1197
|
+
return (0, import_sdk_services.ok)(infoResult.ok);
|
|
1198
|
+
}
|
|
1199
|
+
// ===========================================================================
|
|
1200
|
+
// Space Info
|
|
1201
|
+
// ===========================================================================
|
|
1202
|
+
/**
|
|
1203
|
+
* Get space info from server or cache.
|
|
1204
|
+
*/
|
|
1205
|
+
async getSpaceInfo(spaceId) {
|
|
1206
|
+
const cached = this.infoCache.get(spaceId);
|
|
1207
|
+
if (cached && Date.now() - cached.cachedAt < this.cacheTTL) {
|
|
1208
|
+
return (0, import_sdk_services.ok)(cached.info);
|
|
1209
|
+
}
|
|
1210
|
+
if (!this.session) {
|
|
1211
|
+
return (0, import_sdk_services.err)(
|
|
1212
|
+
(0, import_sdk_services.serviceError)(SpaceErrorCodes.AUTH_REQUIRED, "Authentication required", SERVICE_NAME)
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
try {
|
|
1216
|
+
const headers = this.invoke(this.session, "space", spaceId, "tinycloud.space/info");
|
|
1217
|
+
const response = await this.fetchFn(`${this.host}/invoke`, {
|
|
1218
|
+
method: "POST",
|
|
1219
|
+
headers,
|
|
1220
|
+
body: JSON.stringify({ spaceId })
|
|
1221
|
+
});
|
|
1222
|
+
if (!response.ok) {
|
|
1223
|
+
if (response.status === 404) {
|
|
1224
|
+
return (0, import_sdk_services.err)(
|
|
1225
|
+
(0, import_sdk_services.serviceError)(SpaceErrorCodes.NOT_FOUND, `Space not found: ${spaceId}`, SERVICE_NAME)
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const errorText = await response.text();
|
|
1229
|
+
return (0, import_sdk_services.err)(
|
|
1230
|
+
(0, import_sdk_services.serviceError)(
|
|
1231
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1232
|
+
`Failed to get space info: ${response.status} - ${errorText}`,
|
|
1233
|
+
SERVICE_NAME
|
|
1234
|
+
)
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const rawData = await response.json();
|
|
1238
|
+
const validationResult = validateServerSpaceInfoResponse(rawData);
|
|
1239
|
+
if (!validationResult.ok) {
|
|
1240
|
+
return (0, import_sdk_services.err)(
|
|
1241
|
+
(0, import_sdk_services.serviceError)(
|
|
1242
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1243
|
+
validationResult.error.message,
|
|
1244
|
+
SERVICE_NAME,
|
|
1245
|
+
{ meta: validationResult.error.meta }
|
|
1246
|
+
)
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
const data = validationResult.data;
|
|
1250
|
+
const spaceInfo = {
|
|
1251
|
+
id: data.id,
|
|
1252
|
+
name: data.name ?? this.extractNameFromId(data.id),
|
|
1253
|
+
owner: data.owner,
|
|
1254
|
+
type: data.type ?? (data.owner === this.userDid ? "owned" : "delegated"),
|
|
1255
|
+
permissions: data.permissions,
|
|
1256
|
+
expiresAt: data.expiresAt ? new Date(data.expiresAt) : void 0
|
|
1257
|
+
};
|
|
1258
|
+
this.infoCache.set(spaceId, { info: spaceInfo, cachedAt: Date.now() });
|
|
1259
|
+
return (0, import_sdk_services.ok)(spaceInfo);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
return (0, import_sdk_services.err)(
|
|
1262
|
+
(0, import_sdk_services.serviceError)(
|
|
1263
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1264
|
+
`Network error getting space info: ${String(error)}`,
|
|
1265
|
+
SERVICE_NAME,
|
|
1266
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1267
|
+
)
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
// ===========================================================================
|
|
1272
|
+
// Space-Scoped Service Factories
|
|
1273
|
+
// ===========================================================================
|
|
1274
|
+
/**
|
|
1275
|
+
* Create a space-scoped KV service.
|
|
1276
|
+
*/
|
|
1277
|
+
createSpaceScopedKV(spaceId) {
|
|
1278
|
+
if (this.createKVServiceFn) {
|
|
1279
|
+
return this.createKVServiceFn(spaceId);
|
|
1280
|
+
}
|
|
1281
|
+
return new Proxy({}, {
|
|
1282
|
+
get: () => {
|
|
1283
|
+
throw new Error(
|
|
1284
|
+
"KV service factory not configured. Provide createKVService in SpaceServiceConfig."
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Create space-scoped delegation operations.
|
|
1291
|
+
*/
|
|
1292
|
+
createSpaceScopedDelegations(spaceId) {
|
|
1293
|
+
const self = this;
|
|
1294
|
+
return {
|
|
1295
|
+
async list() {
|
|
1296
|
+
try {
|
|
1297
|
+
const facts = [
|
|
1298
|
+
{
|
|
1299
|
+
capabilitiesReadParams: {
|
|
1300
|
+
type: "list",
|
|
1301
|
+
filters: { direction: "created" }
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
];
|
|
1305
|
+
const headers = self.invoke(
|
|
1306
|
+
self.session,
|
|
1307
|
+
"capabilities",
|
|
1308
|
+
"all",
|
|
1309
|
+
"tinycloud.capabilities/read",
|
|
1310
|
+
facts
|
|
1311
|
+
);
|
|
1312
|
+
const response = await self.fetchFn(`${self.host}/invoke`, {
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
headers
|
|
1315
|
+
});
|
|
1316
|
+
if (!response.ok) {
|
|
1317
|
+
const errorText = await response.text();
|
|
1318
|
+
return (0, import_sdk_services.err)(
|
|
1319
|
+
(0, import_sdk_services.serviceError)(
|
|
1320
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1321
|
+
`Failed to list delegations: ${response.status} - ${errorText}`,
|
|
1322
|
+
SERVICE_NAME
|
|
1323
|
+
)
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
const rawData = await response.json();
|
|
1327
|
+
const validationResult = validateServerDelegationsResponse(rawData);
|
|
1328
|
+
if (!validationResult.ok) {
|
|
1329
|
+
return (0, import_sdk_services.err)(
|
|
1330
|
+
(0, import_sdk_services.serviceError)(
|
|
1331
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1332
|
+
validationResult.error.message,
|
|
1333
|
+
SERVICE_NAME,
|
|
1334
|
+
{ meta: validationResult.error.meta }
|
|
1335
|
+
)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
const delegations = transformServerDelegations(validationResult.data, spaceId);
|
|
1339
|
+
return (0, import_sdk_services.ok)(delegations);
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
return (0, import_sdk_services.err)(
|
|
1342
|
+
(0, import_sdk_services.serviceError)(
|
|
1343
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1344
|
+
`Network error listing delegations: ${String(error)}`,
|
|
1345
|
+
SERVICE_NAME
|
|
1346
|
+
)
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
async listReceived() {
|
|
1351
|
+
try {
|
|
1352
|
+
const facts = [
|
|
1353
|
+
{
|
|
1354
|
+
capabilitiesReadParams: {
|
|
1355
|
+
type: "list",
|
|
1356
|
+
filters: { direction: "received" }
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
];
|
|
1360
|
+
const headers = self.invoke(
|
|
1361
|
+
self.session,
|
|
1362
|
+
"capabilities",
|
|
1363
|
+
"all",
|
|
1364
|
+
"tinycloud.capabilities/read",
|
|
1365
|
+
facts
|
|
1366
|
+
);
|
|
1367
|
+
const response = await self.fetchFn(`${self.host}/invoke`, {
|
|
1368
|
+
method: "POST",
|
|
1369
|
+
headers
|
|
1370
|
+
});
|
|
1371
|
+
if (!response.ok) {
|
|
1372
|
+
const errorText = await response.text();
|
|
1373
|
+
return (0, import_sdk_services.err)(
|
|
1374
|
+
(0, import_sdk_services.serviceError)(
|
|
1375
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1376
|
+
`Failed to list received delegations: ${response.status} - ${errorText}`,
|
|
1377
|
+
SERVICE_NAME
|
|
1378
|
+
)
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
const rawData = await response.json();
|
|
1382
|
+
const validationResult = validateServerDelegationsResponse(rawData);
|
|
1383
|
+
if (!validationResult.ok) {
|
|
1384
|
+
return (0, import_sdk_services.err)(
|
|
1385
|
+
(0, import_sdk_services.serviceError)(
|
|
1386
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1387
|
+
validationResult.error.message,
|
|
1388
|
+
SERVICE_NAME,
|
|
1389
|
+
{ meta: validationResult.error.meta }
|
|
1390
|
+
)
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
const delegations = transformServerDelegations(validationResult.data, spaceId);
|
|
1394
|
+
return (0, import_sdk_services.ok)(delegations);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
return (0, import_sdk_services.err)(
|
|
1397
|
+
(0, import_sdk_services.serviceError)(
|
|
1398
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1399
|
+
`Network error listing received delegations: ${String(error)}`,
|
|
1400
|
+
SERVICE_NAME
|
|
1401
|
+
)
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
},
|
|
1405
|
+
async create(params) {
|
|
1406
|
+
if (self.createDelegationFn) {
|
|
1407
|
+
return self.createDelegationFn({ ...params, spaceId });
|
|
1408
|
+
}
|
|
1409
|
+
return (0, import_sdk_services.err)(
|
|
1410
|
+
(0, import_sdk_services.serviceError)(
|
|
1411
|
+
SpaceErrorCodes.NOT_INITIALIZED,
|
|
1412
|
+
"Delegation creation requires a createDelegation function. This should be provided by the platform SDK (web-sdk or node-sdk).",
|
|
1413
|
+
SERVICE_NAME
|
|
1414
|
+
)
|
|
1415
|
+
);
|
|
1416
|
+
},
|
|
1417
|
+
async revoke(cid) {
|
|
1418
|
+
try {
|
|
1419
|
+
const headers = self.invoke(
|
|
1420
|
+
self.session,
|
|
1421
|
+
"delegation",
|
|
1422
|
+
cid,
|
|
1423
|
+
"tinycloud.delegation/revoke"
|
|
1424
|
+
);
|
|
1425
|
+
const response = await self.fetchFn(`${self.host}/revoke`, {
|
|
1426
|
+
method: "POST",
|
|
1427
|
+
headers,
|
|
1428
|
+
body: JSON.stringify({ cid, spaceId })
|
|
1429
|
+
});
|
|
1430
|
+
if (!response.ok) {
|
|
1431
|
+
const errorText = await response.text();
|
|
1432
|
+
return (0, import_sdk_services.err)(
|
|
1433
|
+
(0, import_sdk_services.serviceError)(
|
|
1434
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1435
|
+
`Failed to revoke delegation: ${response.status} - ${errorText}`,
|
|
1436
|
+
SERVICE_NAME
|
|
1437
|
+
)
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
return (0, import_sdk_services.ok)(void 0);
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
return (0, import_sdk_services.err)(
|
|
1443
|
+
(0, import_sdk_services.serviceError)(
|
|
1444
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1445
|
+
`Network error revoking delegation: ${String(error)}`,
|
|
1446
|
+
SERVICE_NAME
|
|
1447
|
+
)
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Create space-scoped sharing operations.
|
|
1455
|
+
*
|
|
1456
|
+
* When a SharingService is configured, delegates to client-side v2 sharing.
|
|
1457
|
+
* V2 sharing links are self-contained with embedded private keys - no server tracking.
|
|
1458
|
+
*/
|
|
1459
|
+
createSpaceScopedSharing(spaceId) {
|
|
1460
|
+
const self = this;
|
|
1461
|
+
return {
|
|
1462
|
+
async generate(params) {
|
|
1463
|
+
if (self.sharingService) {
|
|
1464
|
+
const result = await self.sharingService.generate(params);
|
|
1465
|
+
if (!result.ok) {
|
|
1466
|
+
return (0, import_sdk_services.err)(
|
|
1467
|
+
(0, import_sdk_services.serviceError)(
|
|
1468
|
+
SpaceErrorCodes.NETWORK_ERROR,
|
|
1469
|
+
result.error.message || "Failed to generate share link",
|
|
1470
|
+
SERVICE_NAME
|
|
1471
|
+
)
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return (0, import_sdk_services.ok)(result.data);
|
|
1475
|
+
}
|
|
1476
|
+
return (0, import_sdk_services.err)(
|
|
1477
|
+
(0, import_sdk_services.serviceError)(
|
|
1478
|
+
SpaceErrorCodes.NOT_INITIALIZED,
|
|
1479
|
+
"SharingService not configured. V2 sharing requires a SharingService instance.",
|
|
1480
|
+
SERVICE_NAME
|
|
1481
|
+
)
|
|
1482
|
+
);
|
|
1483
|
+
},
|
|
1484
|
+
async list() {
|
|
1485
|
+
return (0, import_sdk_services.err)(
|
|
1486
|
+
(0, import_sdk_services.serviceError)(
|
|
1487
|
+
SpaceErrorCodes.NOT_INITIALIZED,
|
|
1488
|
+
"Listing share links is not supported in v2. Share links are self-contained tokens that are not tracked on the server.",
|
|
1489
|
+
SERVICE_NAME
|
|
1490
|
+
)
|
|
1491
|
+
);
|
|
1492
|
+
},
|
|
1493
|
+
async revoke(token) {
|
|
1494
|
+
return (0, import_sdk_services.err)(
|
|
1495
|
+
(0, import_sdk_services.serviceError)(
|
|
1496
|
+
SpaceErrorCodes.NOT_INITIALIZED,
|
|
1497
|
+
"Revoking share links by token is not supported in v2. To revoke access, revoke the underlying delegation using space.delegations.revoke(cid).",
|
|
1498
|
+
SERVICE_NAME
|
|
1499
|
+
)
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
function createSpaceService(config) {
|
|
1506
|
+
return new SpaceService(config);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// src/TinyCloud.ts
|
|
1510
|
+
var TinyCloud = class _TinyCloud {
|
|
1511
|
+
/**
|
|
1512
|
+
* Create a new TinyCloud SDK instance.
|
|
1513
|
+
*
|
|
1514
|
+
* @param userAuthorization - Platform-specific authorization implementation
|
|
1515
|
+
* @param config - Optional SDK configuration
|
|
1516
|
+
*/
|
|
1517
|
+
constructor(userAuthorization, config) {
|
|
1518
|
+
/**
|
|
1519
|
+
* Registered extensions.
|
|
1520
|
+
*/
|
|
1521
|
+
this.extensions = [];
|
|
1522
|
+
/**
|
|
1523
|
+
* Registered services by name.
|
|
1524
|
+
*/
|
|
1525
|
+
this._services = /* @__PURE__ */ new Map();
|
|
1526
|
+
/**
|
|
1527
|
+
* Whether services have been initialized.
|
|
1528
|
+
*/
|
|
1529
|
+
this._servicesInitialized = false;
|
|
1530
|
+
this.userAuthorization = userAuthorization;
|
|
1531
|
+
this.config = config || {};
|
|
1532
|
+
}
|
|
1533
|
+
// === Service Management ===
|
|
1534
|
+
/**
|
|
1535
|
+
* Initialize services with platform dependencies.
|
|
1536
|
+
* Must be called before using services.
|
|
1537
|
+
*
|
|
1538
|
+
* @param invoke - Platform-specific invoke function from WASM binding
|
|
1539
|
+
* @param hosts - TinyCloud host URLs (optional, uses config.hosts)
|
|
1540
|
+
* @param fetchFn - Custom fetch implementation (optional)
|
|
1541
|
+
*/
|
|
1542
|
+
initializeServices(invoke, hosts, fetchFn) {
|
|
1543
|
+
const effectiveInvoke = invoke ?? this.config.invoke;
|
|
1544
|
+
const effectiveHosts = hosts ?? this.config.hosts;
|
|
1545
|
+
if (!effectiveInvoke) {
|
|
1546
|
+
throw new Error(
|
|
1547
|
+
"invoke function is required to initialize services. Provide it via config.invoke or initializeServices()."
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
if (!effectiveHosts || effectiveHosts.length === 0) {
|
|
1551
|
+
throw new Error(
|
|
1552
|
+
"hosts are required to initialize services. Provide them via config.hosts or initializeServices()."
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
this._serviceContext = new import_sdk_services2.ServiceContext({
|
|
1556
|
+
invoke: effectiveInvoke,
|
|
1557
|
+
fetch: fetchFn ?? this.config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
1558
|
+
hosts: effectiveHosts,
|
|
1559
|
+
retryPolicy: this.config.retryPolicy
|
|
1560
|
+
});
|
|
1561
|
+
const serviceConstructors = {
|
|
1562
|
+
kv: import_sdk_services2.KVService,
|
|
1563
|
+
sql: import_sdk_services2.SQLService,
|
|
1564
|
+
duckdb: import_sdk_services2.DuckDbService,
|
|
1565
|
+
...this.config.services
|
|
1566
|
+
};
|
|
1567
|
+
for (const [name, ServiceClass] of Object.entries(serviceConstructors)) {
|
|
1568
|
+
const serviceConfig = this.config.serviceConfigs?.[name] ?? {};
|
|
1569
|
+
const service = new ServiceClass(serviceConfig);
|
|
1570
|
+
service.initialize(this._serviceContext);
|
|
1571
|
+
this._serviceContext.registerService(name, service);
|
|
1572
|
+
this._services.set(name, service);
|
|
1573
|
+
}
|
|
1574
|
+
this._servicesInitialized = true;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Get the service context.
|
|
1578
|
+
* @throws Error if services are not initialized
|
|
1579
|
+
*/
|
|
1580
|
+
get serviceContext() {
|
|
1581
|
+
if (!this._serviceContext) {
|
|
1582
|
+
throw new Error(
|
|
1583
|
+
"Services not initialized. Call initializeServices() first."
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
return this._serviceContext;
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Get a registered service by name.
|
|
1590
|
+
*
|
|
1591
|
+
* @param name - Service name (e.g., 'kv')
|
|
1592
|
+
* @returns The service instance or undefined
|
|
1593
|
+
*/
|
|
1594
|
+
getService(name) {
|
|
1595
|
+
return this._services.get(name);
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Get the KV service.
|
|
1599
|
+
* @throws Error if services are not initialized
|
|
1600
|
+
*/
|
|
1601
|
+
get kv() {
|
|
1602
|
+
if (!this._servicesInitialized) {
|
|
1603
|
+
throw new Error(
|
|
1604
|
+
"Services not initialized. Call initializeServices() first, or use TinyCloudWeb/TinyCloudNode which handles this automatically."
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
const service = this._services.get("kv");
|
|
1608
|
+
if (!service) {
|
|
1609
|
+
throw new Error("KV service is not registered.");
|
|
1610
|
+
}
|
|
1611
|
+
return service;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Get the SQL service.
|
|
1615
|
+
* @throws Error if services are not initialized
|
|
1616
|
+
*/
|
|
1617
|
+
get sql() {
|
|
1618
|
+
if (!this._servicesInitialized) {
|
|
1619
|
+
throw new Error(
|
|
1620
|
+
"Services not initialized. Call initializeServices() first, or use TinyCloudWeb/TinyCloudNode which handles this automatically."
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
const service = this._services.get("sql");
|
|
1624
|
+
if (!service) {
|
|
1625
|
+
throw new Error("SQL service is not registered.");
|
|
1626
|
+
}
|
|
1627
|
+
return service;
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Get the DuckDB service.
|
|
1631
|
+
* @throws Error if services are not initialized
|
|
1632
|
+
*/
|
|
1633
|
+
get duckdb() {
|
|
1634
|
+
if (!this._servicesInitialized) {
|
|
1635
|
+
throw new Error(
|
|
1636
|
+
"Services not initialized. Call initializeServices() first, or use TinyCloudWeb/TinyCloudNode which handles this automatically."
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
const service = this._services.get("duckdb");
|
|
1640
|
+
if (!service) {
|
|
1641
|
+
throw new Error("DuckDB service is not registered.");
|
|
1642
|
+
}
|
|
1643
|
+
return service;
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Get the Data Vault service.
|
|
1647
|
+
* @throws Error if services are not initialized or vault service is not registered
|
|
1648
|
+
*/
|
|
1649
|
+
get vault() {
|
|
1650
|
+
if (!this._servicesInitialized) {
|
|
1651
|
+
throw new Error(
|
|
1652
|
+
"Services not initialized. Call initializeServices() first, or use TinyCloudWeb/TinyCloudNode which handles this automatically."
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
const service = this._services.get("vault");
|
|
1656
|
+
if (!service) {
|
|
1657
|
+
throw new Error("Vault service is not registered.");
|
|
1658
|
+
}
|
|
1659
|
+
return service;
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Notify services of session change.
|
|
1663
|
+
* Called internally after sign-in and sign-out.
|
|
1664
|
+
*
|
|
1665
|
+
* @param session - The new session, or null if signed out
|
|
1666
|
+
*/
|
|
1667
|
+
notifyServicesOfSessionChange(session) {
|
|
1668
|
+
if (this._serviceContext) {
|
|
1669
|
+
this._serviceContext.setSession(session);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Abort all pending service operations.
|
|
1674
|
+
* Called internally before sign-out.
|
|
1675
|
+
*/
|
|
1676
|
+
abortServiceOperations() {
|
|
1677
|
+
if (this._serviceContext) {
|
|
1678
|
+
this._serviceContext.abort();
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Convert ClientSession to ServiceSession.
|
|
1683
|
+
* Returns null if session lacks required fields.
|
|
1684
|
+
*/
|
|
1685
|
+
toServiceSession(clientSession) {
|
|
1686
|
+
if (!clientSession) return null;
|
|
1687
|
+
const tcSession = clientSession.tinycloudSession;
|
|
1688
|
+
if (!tcSession) return null;
|
|
1689
|
+
return {
|
|
1690
|
+
delegationHeader: tcSession.delegationHeader,
|
|
1691
|
+
delegationCid: tcSession.delegationCid,
|
|
1692
|
+
spaceId: tcSession.spaceId,
|
|
1693
|
+
verificationMethod: tcSession.verificationMethod,
|
|
1694
|
+
jwk: tcSession.jwk
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Add an extension to the SDK.
|
|
1699
|
+
* Extensions can add capabilities and lifecycle hooks.
|
|
1700
|
+
*/
|
|
1701
|
+
extend(extension) {
|
|
1702
|
+
this.extensions.push(extension);
|
|
1703
|
+
this.userAuthorization.extend(extension);
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Check if an extension is enabled.
|
|
1707
|
+
* @param namespace - The extension namespace to check
|
|
1708
|
+
*/
|
|
1709
|
+
isExtensionEnabled(namespace) {
|
|
1710
|
+
return this.extensions.some((ext) => ext.namespace === namespace);
|
|
1711
|
+
}
|
|
1712
|
+
// === Authentication Methods (delegate to userAuthorization) ===
|
|
1713
|
+
/**
|
|
1714
|
+
* Get the current session, if signed in.
|
|
1715
|
+
*/
|
|
1716
|
+
get session() {
|
|
1717
|
+
return this.userAuthorization.session;
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Check if the user is signed in.
|
|
1721
|
+
*/
|
|
1722
|
+
get isSignedIn() {
|
|
1723
|
+
return !!this.userAuthorization.session;
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Sign in and create a new session.
|
|
1727
|
+
* Notifies services of the new session after successful sign-in.
|
|
1728
|
+
* @returns The new session
|
|
1729
|
+
*/
|
|
1730
|
+
async signIn() {
|
|
1731
|
+
const session = await this.userAuthorization.signIn();
|
|
1732
|
+
const serviceSession = this.toServiceSession(session);
|
|
1733
|
+
this.notifyServicesOfSessionChange(serviceSession);
|
|
1734
|
+
return session;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Sign out and clear the current session.
|
|
1738
|
+
* Aborts pending service operations and notifies services.
|
|
1739
|
+
*/
|
|
1740
|
+
async signOut() {
|
|
1741
|
+
this.abortServiceOperations();
|
|
1742
|
+
await this.userAuthorization.signOut();
|
|
1743
|
+
this._publicKV = void 0;
|
|
1744
|
+
this.notifyServicesOfSessionChange(null);
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Get the current wallet address.
|
|
1748
|
+
*/
|
|
1749
|
+
address() {
|
|
1750
|
+
return this.userAuthorization.address();
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Get the current chain ID.
|
|
1754
|
+
*/
|
|
1755
|
+
chainId() {
|
|
1756
|
+
return this.userAuthorization.chainId();
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Sign a message with the connected wallet.
|
|
1760
|
+
* @param message - Message to sign
|
|
1761
|
+
*/
|
|
1762
|
+
async signMessage(message) {
|
|
1763
|
+
return this.userAuthorization.signMessage(message);
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Construct the deterministic public space ID for a given address and chain ID.
|
|
1767
|
+
*
|
|
1768
|
+
* @param address - Ethereum address (0x-prefixed)
|
|
1769
|
+
* @param chainId - Chain ID (e.g., 1 for mainnet)
|
|
1770
|
+
* @returns The public space ID
|
|
1771
|
+
*/
|
|
1772
|
+
static makePublicSpaceId(address, chainId) {
|
|
1773
|
+
return makePublicSpaceId(address, chainId);
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Ensure the user's public space exists.
|
|
1777
|
+
* Creates it via spaces.create('public') if it doesn't.
|
|
1778
|
+
* Called automatically by modules that need to publish data.
|
|
1779
|
+
*
|
|
1780
|
+
* Requires the user to be signed in and services to be initialized.
|
|
1781
|
+
*/
|
|
1782
|
+
async ensurePublicSpace() {
|
|
1783
|
+
const address = this.address();
|
|
1784
|
+
const chainId = this.chainId();
|
|
1785
|
+
if (!address || !chainId) {
|
|
1786
|
+
return (0, import_sdk_services2.err)(
|
|
1787
|
+
(0, import_sdk_services2.serviceError)(
|
|
1788
|
+
import_sdk_services2.ErrorCodes.AUTH_REQUIRED,
|
|
1789
|
+
"Must be signed in to ensure public space",
|
|
1790
|
+
"public-space"
|
|
1791
|
+
)
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
if (!this._serviceContext) {
|
|
1795
|
+
return (0, import_sdk_services2.err)(
|
|
1796
|
+
(0, import_sdk_services2.serviceError)(
|
|
1797
|
+
import_sdk_services2.ErrorCodes.AUTH_REQUIRED,
|
|
1798
|
+
"Services not initialized. Call initializeServices() or signIn() first.",
|
|
1799
|
+
"public-space"
|
|
1800
|
+
)
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
const spaceId = makePublicSpaceId(address, chainId);
|
|
1804
|
+
try {
|
|
1805
|
+
const session = this._serviceContext.session;
|
|
1806
|
+
if (!session) {
|
|
1807
|
+
return (0, import_sdk_services2.err)(
|
|
1808
|
+
(0, import_sdk_services2.serviceError)(
|
|
1809
|
+
import_sdk_services2.ErrorCodes.AUTH_REQUIRED,
|
|
1810
|
+
"No active session",
|
|
1811
|
+
"public-space"
|
|
1812
|
+
)
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
const headers = this._serviceContext.invoke(
|
|
1816
|
+
session,
|
|
1817
|
+
"space",
|
|
1818
|
+
spaceId,
|
|
1819
|
+
"tinycloud.space/info"
|
|
1820
|
+
);
|
|
1821
|
+
const response = await this._serviceContext.fetch(
|
|
1822
|
+
`${this._serviceContext.hosts[0]}/invoke`,
|
|
1823
|
+
{ method: "POST", headers, body: JSON.stringify({ spaceId }) }
|
|
1824
|
+
);
|
|
1825
|
+
if (response.ok) {
|
|
1826
|
+
return (0, import_sdk_services2.ok)(void 0);
|
|
1827
|
+
}
|
|
1828
|
+
if (response.status === 404) {
|
|
1829
|
+
const createHeaders = this._serviceContext.invoke(
|
|
1830
|
+
session,
|
|
1831
|
+
"space",
|
|
1832
|
+
"public",
|
|
1833
|
+
"tinycloud.space/create"
|
|
1834
|
+
);
|
|
1835
|
+
const createResponse = await this._serviceContext.fetch(
|
|
1836
|
+
`${this._serviceContext.hosts[0]}/invoke`,
|
|
1837
|
+
{
|
|
1838
|
+
method: "POST",
|
|
1839
|
+
headers: createHeaders,
|
|
1840
|
+
body: JSON.stringify({ name: "public" })
|
|
1841
|
+
}
|
|
1842
|
+
);
|
|
1843
|
+
if (!createResponse.ok) {
|
|
1844
|
+
if (createResponse.status === 409) {
|
|
1845
|
+
return (0, import_sdk_services2.ok)(void 0);
|
|
1846
|
+
}
|
|
1847
|
+
const errorText2 = await createResponse.text();
|
|
1848
|
+
return (0, import_sdk_services2.err)(
|
|
1849
|
+
(0, import_sdk_services2.serviceError)(
|
|
1850
|
+
import_sdk_services2.ErrorCodes.NETWORK_ERROR,
|
|
1851
|
+
`Failed to create public space: ${createResponse.status} - ${errorText2}`,
|
|
1852
|
+
"public-space"
|
|
1853
|
+
)
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
return (0, import_sdk_services2.ok)(void 0);
|
|
1857
|
+
}
|
|
1858
|
+
const errorText = await response.text();
|
|
1859
|
+
return (0, import_sdk_services2.err)(
|
|
1860
|
+
(0, import_sdk_services2.serviceError)(
|
|
1861
|
+
import_sdk_services2.ErrorCodes.NETWORK_ERROR,
|
|
1862
|
+
`Failed to check public space: ${response.status} - ${errorText}`,
|
|
1863
|
+
"public-space"
|
|
1864
|
+
)
|
|
1865
|
+
);
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
return (0, import_sdk_services2.err)(
|
|
1868
|
+
(0, import_sdk_services2.serviceError)(
|
|
1869
|
+
import_sdk_services2.ErrorCodes.NETWORK_ERROR,
|
|
1870
|
+
`Network error ensuring public space: ${String(error)}`,
|
|
1871
|
+
"public-space",
|
|
1872
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1873
|
+
)
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Get a KVService scoped to the user's own public space.
|
|
1879
|
+
* Writes require authentication (owner/delegate).
|
|
1880
|
+
*
|
|
1881
|
+
* @throws Error if not signed in or services not initialized
|
|
1882
|
+
*/
|
|
1883
|
+
get publicKV() {
|
|
1884
|
+
if (!this._servicesInitialized || !this._serviceContext) {
|
|
1885
|
+
throw new Error(
|
|
1886
|
+
"Services not initialized. Call initializeServices() first, or use TinyCloudWeb/TinyCloudNode which handles this automatically."
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
const address = this.address();
|
|
1890
|
+
const chainId = this.chainId();
|
|
1891
|
+
if (!address || !chainId) {
|
|
1892
|
+
throw new Error("Must be signed in to access publicKV.");
|
|
1893
|
+
}
|
|
1894
|
+
if (this._publicKV) {
|
|
1895
|
+
return this._publicKV;
|
|
1896
|
+
}
|
|
1897
|
+
const publicSpaceId = makePublicSpaceId(address, chainId);
|
|
1898
|
+
const session = this._serviceContext.session;
|
|
1899
|
+
if (!session) {
|
|
1900
|
+
throw new Error("No active session. Sign in first.");
|
|
1901
|
+
}
|
|
1902
|
+
const publicKV = new import_sdk_services2.KVService({ prefix: "" });
|
|
1903
|
+
const publicContext = new import_sdk_services2.ServiceContext({
|
|
1904
|
+
invoke: this._serviceContext.invoke,
|
|
1905
|
+
fetch: this._serviceContext.fetch,
|
|
1906
|
+
hosts: this._serviceContext.hosts,
|
|
1907
|
+
retryPolicy: this.config.retryPolicy
|
|
1908
|
+
});
|
|
1909
|
+
publicContext.setSession({
|
|
1910
|
+
...session,
|
|
1911
|
+
spaceId: publicSpaceId
|
|
1912
|
+
});
|
|
1913
|
+
publicKV.initialize(publicContext);
|
|
1914
|
+
this._publicKV = publicKV;
|
|
1915
|
+
return this._publicKV;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Read from any user's public space (unauthenticated).
|
|
1919
|
+
* Uses the public REST endpoint — no session needed.
|
|
1920
|
+
*
|
|
1921
|
+
* @param host - TinyCloud server URL (e.g., "https://node.tinycloud.xyz")
|
|
1922
|
+
* @param spaceId - Full public space ID
|
|
1923
|
+
* @param key - Key to read
|
|
1924
|
+
* @param fetchFn - Optional custom fetch function
|
|
1925
|
+
* @returns The data at the key
|
|
1926
|
+
*/
|
|
1927
|
+
static async readPublicSpace(host, spaceId, key, fetchFn) {
|
|
1928
|
+
const doFetch = fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
1929
|
+
const encodedKey = key.split("/").map(encodeURIComponent).join("/");
|
|
1930
|
+
const url = `${host}/public/${encodeURIComponent(spaceId)}/kv/${encodedKey}`;
|
|
1931
|
+
try {
|
|
1932
|
+
const response = await doFetch(url, { method: "GET" });
|
|
1933
|
+
if (!response.ok) {
|
|
1934
|
+
if (response.status === 404) {
|
|
1935
|
+
return (0, import_sdk_services2.err)(
|
|
1936
|
+
(0, import_sdk_services2.serviceError)(
|
|
1937
|
+
import_sdk_services2.ErrorCodes.NOT_FOUND,
|
|
1938
|
+
`Key not found: ${key} in space ${spaceId}`,
|
|
1939
|
+
"public-space"
|
|
1940
|
+
)
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
const errorText = await response.text();
|
|
1944
|
+
return (0, import_sdk_services2.err)(
|
|
1945
|
+
(0, import_sdk_services2.serviceError)(
|
|
1946
|
+
import_sdk_services2.ErrorCodes.NETWORK_ERROR,
|
|
1947
|
+
`Failed to read public space: ${response.status} - ${errorText}`,
|
|
1948
|
+
"public-space",
|
|
1949
|
+
{ meta: { status: response.status } }
|
|
1950
|
+
)
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
const contentType = response.headers.get("content-type");
|
|
1954
|
+
let data;
|
|
1955
|
+
if (contentType?.includes("application/json")) {
|
|
1956
|
+
data = await response.json();
|
|
1957
|
+
} else {
|
|
1958
|
+
const text = await response.text();
|
|
1959
|
+
try {
|
|
1960
|
+
data = JSON.parse(text);
|
|
1961
|
+
} catch {
|
|
1962
|
+
data = text;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return (0, import_sdk_services2.ok)(data);
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
return (0, import_sdk_services2.err)(
|
|
1968
|
+
(0, import_sdk_services2.serviceError)(
|
|
1969
|
+
import_sdk_services2.ErrorCodes.NETWORK_ERROR,
|
|
1970
|
+
`Network error reading public space: ${String(error)}`,
|
|
1971
|
+
"public-space",
|
|
1972
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1973
|
+
)
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Read from any user's public space by address (unauthenticated).
|
|
1979
|
+
* Convenience method that constructs the space ID from address and chain ID.
|
|
1980
|
+
*
|
|
1981
|
+
* @param host - TinyCloud server URL
|
|
1982
|
+
* @param address - Ethereum address (0x-prefixed)
|
|
1983
|
+
* @param chainId - Chain ID (e.g., 1 for mainnet)
|
|
1984
|
+
* @param key - Key to read
|
|
1985
|
+
* @param fetchFn - Optional custom fetch function
|
|
1986
|
+
* @returns The data at the key
|
|
1987
|
+
*/
|
|
1988
|
+
static async readPublicKey(host, address, chainId, key, fetchFn) {
|
|
1989
|
+
const spaceId = makePublicSpaceId(address, chainId);
|
|
1990
|
+
return _TinyCloud.readPublicSpace(host, spaceId, key, fetchFn);
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
|
|
1994
|
+
// src/index.ts
|
|
1995
|
+
var import_sdk_services4 = require("@tinycloud/sdk-services");
|
|
1996
|
+
|
|
1997
|
+
// src/space.ts
|
|
1998
|
+
async function fetchPeerId(host, spaceId) {
|
|
1999
|
+
const res = await fetch(
|
|
2000
|
+
`${host}/peer/generate/${encodeURIComponent(spaceId)}`
|
|
2001
|
+
);
|
|
2002
|
+
if (!res.ok) {
|
|
2003
|
+
const error = await res.text().catch(() => res.statusText);
|
|
2004
|
+
throw new Error(`Failed to get peer ID: ${res.status} - ${error}`);
|
|
2005
|
+
}
|
|
2006
|
+
return res.text();
|
|
2007
|
+
}
|
|
2008
|
+
async function submitHostDelegation(host, headers) {
|
|
2009
|
+
const res = await fetch(`${host}/delegate`, {
|
|
2010
|
+
method: "POST",
|
|
2011
|
+
headers
|
|
2012
|
+
});
|
|
2013
|
+
return {
|
|
2014
|
+
success: res.ok,
|
|
2015
|
+
status: res.status,
|
|
2016
|
+
error: res.ok ? void 0 : await res.text().catch(() => res.statusText)
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
async function activateSessionWithHost(host, delegationHeader) {
|
|
2020
|
+
const res = await fetch(`${host}/delegate`, {
|
|
2021
|
+
method: "POST",
|
|
2022
|
+
headers: delegationHeader
|
|
2023
|
+
});
|
|
2024
|
+
if (res.ok) {
|
|
2025
|
+
try {
|
|
2026
|
+
const body = await res.json();
|
|
2027
|
+
return {
|
|
2028
|
+
success: true,
|
|
2029
|
+
status: res.status,
|
|
2030
|
+
activated: body.activated ?? [],
|
|
2031
|
+
skipped: body.skipped ?? []
|
|
2032
|
+
};
|
|
2033
|
+
} catch {
|
|
2034
|
+
return {
|
|
2035
|
+
success: true,
|
|
2036
|
+
status: res.status,
|
|
2037
|
+
activated: [],
|
|
2038
|
+
skipped: []
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return {
|
|
2043
|
+
success: false,
|
|
2044
|
+
status: res.status,
|
|
2045
|
+
error: await res.text().catch(() => res.statusText)
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// src/delegations/DelegationManager.ts
|
|
2050
|
+
var DelegationAction = {
|
|
2051
|
+
CREATE: "tinycloud.delegation/create",
|
|
2052
|
+
REVOKE: "tinycloud.delegation/revoke",
|
|
2053
|
+
LIST: "tinycloud.delegation/list",
|
|
2054
|
+
GET: "tinycloud.delegation/get",
|
|
2055
|
+
CHECK: "tinycloud.delegation/check"
|
|
2056
|
+
};
|
|
2057
|
+
function createError(code, message, cause, meta) {
|
|
2058
|
+
return {
|
|
2059
|
+
code,
|
|
2060
|
+
message,
|
|
2061
|
+
service: "delegation",
|
|
2062
|
+
cause,
|
|
2063
|
+
meta
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
var DelegationManager = class {
|
|
2067
|
+
/**
|
|
2068
|
+
* Creates a new DelegationManager instance.
|
|
2069
|
+
*
|
|
2070
|
+
* @param config - Configuration including hosts, session, and invoke function
|
|
2071
|
+
*/
|
|
2072
|
+
constructor(config) {
|
|
2073
|
+
this.hosts = config.hosts;
|
|
2074
|
+
this.session = config.session;
|
|
2075
|
+
this.invoke = config.invoke;
|
|
2076
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Updates the session (e.g., after re-authentication).
|
|
2080
|
+
*
|
|
2081
|
+
* @param session - New session to use for operations
|
|
2082
|
+
*/
|
|
2083
|
+
updateSession(session) {
|
|
2084
|
+
this.session = session;
|
|
2085
|
+
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Gets the primary host URL.
|
|
2088
|
+
*/
|
|
2089
|
+
get host() {
|
|
2090
|
+
return this.hosts[0];
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Executes an invoke operation against the delegation API.
|
|
2094
|
+
*/
|
|
2095
|
+
async invokeOperation(path, action, body) {
|
|
2096
|
+
const headers = this.invoke(this.session, "delegation", path, action);
|
|
2097
|
+
return this.fetchFn(`${this.host}/invoke`, {
|
|
2098
|
+
method: "POST",
|
|
2099
|
+
headers,
|
|
2100
|
+
body
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Creates a new delegation.
|
|
2105
|
+
*
|
|
2106
|
+
* Delegates specific permissions to another DID for a given path.
|
|
2107
|
+
* The delegatee can then use these permissions to access resources
|
|
2108
|
+
* within the specified scope.
|
|
2109
|
+
*
|
|
2110
|
+
* @param params - Parameters for the delegation
|
|
2111
|
+
* @returns Result containing the created Delegation or an error
|
|
2112
|
+
*
|
|
2113
|
+
* @example
|
|
2114
|
+
* ```typescript
|
|
2115
|
+
* const result = await manager.create({
|
|
2116
|
+
* delegateDID: bob.did,
|
|
2117
|
+
* path: "documents/shared/",
|
|
2118
|
+
* actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
|
|
2119
|
+
* expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
|
|
2120
|
+
* });
|
|
2121
|
+
* ```
|
|
2122
|
+
*/
|
|
2123
|
+
async create(params) {
|
|
2124
|
+
if (!params.delegateDID) {
|
|
2125
|
+
return {
|
|
2126
|
+
ok: false,
|
|
2127
|
+
error: createError(
|
|
2128
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2129
|
+
"delegateDID is required"
|
|
2130
|
+
)
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
if (!params.path) {
|
|
2134
|
+
return {
|
|
2135
|
+
ok: false,
|
|
2136
|
+
error: createError(
|
|
2137
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2138
|
+
"path is required"
|
|
2139
|
+
)
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
if (!params.actions || params.actions.length === 0) {
|
|
2143
|
+
return {
|
|
2144
|
+
ok: false,
|
|
2145
|
+
error: createError(
|
|
2146
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2147
|
+
"at least one action is required"
|
|
2148
|
+
)
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
try {
|
|
2152
|
+
const body = JSON.stringify({
|
|
2153
|
+
delegateDID: params.delegateDID,
|
|
2154
|
+
path: params.path,
|
|
2155
|
+
actions: params.actions,
|
|
2156
|
+
expiry: params.expiry?.toISOString(),
|
|
2157
|
+
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
2158
|
+
statement: params.statement
|
|
2159
|
+
});
|
|
2160
|
+
const response = await this.invokeOperation(
|
|
2161
|
+
params.path,
|
|
2162
|
+
DelegationAction.CREATE,
|
|
2163
|
+
body
|
|
2164
|
+
);
|
|
2165
|
+
if (!response.ok) {
|
|
2166
|
+
const errorText = await response.text();
|
|
2167
|
+
return {
|
|
2168
|
+
ok: false,
|
|
2169
|
+
error: createError(
|
|
2170
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
2171
|
+
`Failed to create delegation: ${response.status} - ${errorText}`,
|
|
2172
|
+
void 0,
|
|
2173
|
+
{ status: response.status, path: params.path }
|
|
2174
|
+
)
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
const apiResponse = await response.json();
|
|
2178
|
+
const delegation = {
|
|
2179
|
+
cid: apiResponse.cid ?? "",
|
|
2180
|
+
delegateDID: params.delegateDID,
|
|
2181
|
+
spaceId: this.session.spaceId,
|
|
2182
|
+
path: params.path,
|
|
2183
|
+
actions: params.actions,
|
|
2184
|
+
expiry: params.expiry ?? new Date(Date.now() + 24 * 60 * 60 * 1e3),
|
|
2185
|
+
isRevoked: false,
|
|
2186
|
+
allowSubDelegation: !(params.disableSubDelegation ?? false),
|
|
2187
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2188
|
+
};
|
|
2189
|
+
return { ok: true, data: delegation };
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2192
|
+
return {
|
|
2193
|
+
ok: false,
|
|
2194
|
+
error: createError(
|
|
2195
|
+
DelegationErrorCodes.ABORTED,
|
|
2196
|
+
"Request aborted",
|
|
2197
|
+
error
|
|
2198
|
+
)
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
return {
|
|
2202
|
+
ok: false,
|
|
2203
|
+
error: createError(
|
|
2204
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2205
|
+
`Network error during delegation creation: ${String(error)}`,
|
|
2206
|
+
error instanceof Error ? error : void 0
|
|
2207
|
+
)
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Revokes an existing delegation.
|
|
2213
|
+
*
|
|
2214
|
+
* Once revoked, the delegation can no longer be used to access resources.
|
|
2215
|
+
* This also invalidates any sub-delegations derived from this delegation.
|
|
2216
|
+
*
|
|
2217
|
+
* @param cid - The CID of the delegation to revoke
|
|
2218
|
+
* @returns Result indicating success or an error
|
|
2219
|
+
*
|
|
2220
|
+
* @example
|
|
2221
|
+
* ```typescript
|
|
2222
|
+
* const result = await manager.revoke("bafy...");
|
|
2223
|
+
* if (result.ok) {
|
|
2224
|
+
* console.log("Delegation revoked successfully");
|
|
2225
|
+
* }
|
|
2226
|
+
* ```
|
|
2227
|
+
*/
|
|
2228
|
+
async revoke(cid) {
|
|
2229
|
+
if (!cid) {
|
|
2230
|
+
return {
|
|
2231
|
+
ok: false,
|
|
2232
|
+
error: createError(
|
|
2233
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2234
|
+
"cid is required"
|
|
2235
|
+
)
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
try {
|
|
2239
|
+
const body = JSON.stringify({ cid });
|
|
2240
|
+
const response = await this.invokeOperation(
|
|
2241
|
+
cid,
|
|
2242
|
+
DelegationAction.REVOKE,
|
|
2243
|
+
body
|
|
2244
|
+
);
|
|
2245
|
+
if (!response.ok) {
|
|
2246
|
+
const errorText = await response.text();
|
|
2247
|
+
if (response.status === 404) {
|
|
2248
|
+
return {
|
|
2249
|
+
ok: false,
|
|
2250
|
+
error: createError(
|
|
2251
|
+
DelegationErrorCodes.NOT_FOUND,
|
|
2252
|
+
`Delegation not found: ${cid}`
|
|
2253
|
+
)
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
return {
|
|
2257
|
+
ok: false,
|
|
2258
|
+
error: createError(
|
|
2259
|
+
DelegationErrorCodes.REVOCATION_FAILED,
|
|
2260
|
+
`Failed to revoke delegation: ${response.status} - ${errorText}`,
|
|
2261
|
+
void 0,
|
|
2262
|
+
{ status: response.status, cid }
|
|
2263
|
+
)
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
return { ok: true, data: void 0 };
|
|
2267
|
+
} catch (error) {
|
|
2268
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2269
|
+
return {
|
|
2270
|
+
ok: false,
|
|
2271
|
+
error: createError(
|
|
2272
|
+
DelegationErrorCodes.ABORTED,
|
|
2273
|
+
"Request aborted",
|
|
2274
|
+
error
|
|
2275
|
+
)
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
return {
|
|
2279
|
+
ok: false,
|
|
2280
|
+
error: createError(
|
|
2281
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2282
|
+
`Network error during delegation revocation: ${String(error)}`,
|
|
2283
|
+
error instanceof Error ? error : void 0
|
|
2284
|
+
)
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Lists all delegations for the current session's space.
|
|
2290
|
+
*
|
|
2291
|
+
* Returns both delegations created by the current user (as delegator)
|
|
2292
|
+
* and delegations granted to the current user (as delegatee).
|
|
2293
|
+
*
|
|
2294
|
+
* @returns Result containing an array of Delegations or an error
|
|
2295
|
+
*
|
|
2296
|
+
* @example
|
|
2297
|
+
* ```typescript
|
|
2298
|
+
* const result = await manager.list();
|
|
2299
|
+
* if (result.ok) {
|
|
2300
|
+
* for (const delegation of result.data) {
|
|
2301
|
+
* console.log(`${delegation.cid}: ${delegation.path} -> ${delegation.delegateDID}`);
|
|
2302
|
+
* }
|
|
2303
|
+
* }
|
|
2304
|
+
* ```
|
|
2305
|
+
*/
|
|
2306
|
+
async list() {
|
|
2307
|
+
try {
|
|
2308
|
+
const response = await this.invokeOperation("", DelegationAction.LIST);
|
|
2309
|
+
if (!response.ok) {
|
|
2310
|
+
const errorText = await response.text();
|
|
2311
|
+
return {
|
|
2312
|
+
ok: false,
|
|
2313
|
+
error: createError(
|
|
2314
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2315
|
+
`Failed to list delegations: ${response.status} - ${errorText}`,
|
|
2316
|
+
void 0,
|
|
2317
|
+
{ status: response.status }
|
|
2318
|
+
)
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
const data = await response.json();
|
|
2322
|
+
const delegations = data.map((item) => ({
|
|
2323
|
+
cid: item.cid,
|
|
2324
|
+
delegateDID: item.delegateDID,
|
|
2325
|
+
delegatorDID: item.delegatorDID,
|
|
2326
|
+
spaceId: item.spaceId,
|
|
2327
|
+
path: item.path,
|
|
2328
|
+
actions: item.actions,
|
|
2329
|
+
expiry: new Date(item.expiry),
|
|
2330
|
+
isRevoked: item.isRevoked,
|
|
2331
|
+
createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
|
|
2332
|
+
parentCid: item.parentCid,
|
|
2333
|
+
allowSubDelegation: item.allowSubDelegation
|
|
2334
|
+
}));
|
|
2335
|
+
return { ok: true, data: delegations };
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2338
|
+
return {
|
|
2339
|
+
ok: false,
|
|
2340
|
+
error: createError(
|
|
2341
|
+
DelegationErrorCodes.ABORTED,
|
|
2342
|
+
"Request aborted",
|
|
2343
|
+
error
|
|
2344
|
+
)
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
return {
|
|
2348
|
+
ok: false,
|
|
2349
|
+
error: createError(
|
|
2350
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2351
|
+
`Network error during delegation list: ${String(error)}`,
|
|
2352
|
+
error instanceof Error ? error : void 0
|
|
2353
|
+
)
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Gets the full delegation chain for a given delegation.
|
|
2359
|
+
*
|
|
2360
|
+
* Returns the chain of delegations from the root (original delegator)
|
|
2361
|
+
* to the specified delegation, including all intermediate sub-delegations.
|
|
2362
|
+
*
|
|
2363
|
+
* @param cid - The CID of the delegation to get the chain for
|
|
2364
|
+
* @returns Result containing the DelegationChain or an error
|
|
2365
|
+
*
|
|
2366
|
+
* @example
|
|
2367
|
+
* ```typescript
|
|
2368
|
+
* const result = await manager.getChain("bafy...");
|
|
2369
|
+
* if (result.ok) {
|
|
2370
|
+
* console.log("Chain length:", result.data.length);
|
|
2371
|
+
* for (const delegation of result.data) {
|
|
2372
|
+
* console.log(`- ${delegation.delegatorDID} -> ${delegation.delegateDID}`);
|
|
2373
|
+
* }
|
|
2374
|
+
* }
|
|
2375
|
+
* ```
|
|
2376
|
+
*/
|
|
2377
|
+
async getChain(cid) {
|
|
2378
|
+
if (!cid) {
|
|
2379
|
+
return {
|
|
2380
|
+
ok: false,
|
|
2381
|
+
error: createError(
|
|
2382
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2383
|
+
"cid is required"
|
|
2384
|
+
)
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
try {
|
|
2388
|
+
const body = JSON.stringify({ cid, includeChain: true });
|
|
2389
|
+
const response = await this.invokeOperation(
|
|
2390
|
+
cid,
|
|
2391
|
+
DelegationAction.GET,
|
|
2392
|
+
body
|
|
2393
|
+
);
|
|
2394
|
+
if (!response.ok) {
|
|
2395
|
+
const errorText = await response.text();
|
|
2396
|
+
if (response.status === 404) {
|
|
2397
|
+
return {
|
|
2398
|
+
ok: false,
|
|
2399
|
+
error: createError(
|
|
2400
|
+
DelegationErrorCodes.NOT_FOUND,
|
|
2401
|
+
`Delegation not found: ${cid}`
|
|
2402
|
+
)
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
return {
|
|
2406
|
+
ok: false,
|
|
2407
|
+
error: createError(
|
|
2408
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2409
|
+
`Failed to get delegation chain: ${response.status} - ${errorText}`,
|
|
2410
|
+
void 0,
|
|
2411
|
+
{ status: response.status, cid }
|
|
2412
|
+
)
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
const data = await response.json();
|
|
2416
|
+
const chain = data.chain.map((item) => ({
|
|
2417
|
+
cid: item.cid,
|
|
2418
|
+
delegateDID: item.delegateDID,
|
|
2419
|
+
delegatorDID: item.delegatorDID,
|
|
2420
|
+
spaceId: item.spaceId,
|
|
2421
|
+
path: item.path,
|
|
2422
|
+
actions: item.actions,
|
|
2423
|
+
expiry: new Date(item.expiry),
|
|
2424
|
+
isRevoked: item.isRevoked,
|
|
2425
|
+
createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
|
|
2426
|
+
parentCid: item.parentCid,
|
|
2427
|
+
allowSubDelegation: item.allowSubDelegation
|
|
2428
|
+
}));
|
|
2429
|
+
return { ok: true, data: chain };
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2432
|
+
return {
|
|
2433
|
+
ok: false,
|
|
2434
|
+
error: createError(
|
|
2435
|
+
DelegationErrorCodes.ABORTED,
|
|
2436
|
+
"Request aborted",
|
|
2437
|
+
error
|
|
2438
|
+
)
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
return {
|
|
2442
|
+
ok: false,
|
|
2443
|
+
error: createError(
|
|
2444
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2445
|
+
`Network error during chain retrieval: ${String(error)}`,
|
|
2446
|
+
error instanceof Error ? error : void 0
|
|
2447
|
+
)
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Checks if the current session has permission for a given path and action.
|
|
2453
|
+
*
|
|
2454
|
+
* This can be used to verify permissions before attempting an operation,
|
|
2455
|
+
* or to implement custom access control logic.
|
|
2456
|
+
*
|
|
2457
|
+
* @param path - The resource path to check
|
|
2458
|
+
* @param action - The action to check (e.g., "tinycloud.kv/get")
|
|
2459
|
+
* @returns Result containing a boolean indicating permission or an error
|
|
2460
|
+
*
|
|
2461
|
+
* @example
|
|
2462
|
+
* ```typescript
|
|
2463
|
+
* const result = await manager.checkPermission("documents/private/", "tinycloud.kv/put");
|
|
2464
|
+
* if (result.ok && result.data) {
|
|
2465
|
+
* console.log("Permission granted");
|
|
2466
|
+
* } else {
|
|
2467
|
+
* console.log("Permission denied");
|
|
2468
|
+
* }
|
|
2469
|
+
* ```
|
|
2470
|
+
*/
|
|
2471
|
+
async checkPermission(path, action) {
|
|
2472
|
+
if (!path) {
|
|
2473
|
+
return {
|
|
2474
|
+
ok: false,
|
|
2475
|
+
error: createError(
|
|
2476
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2477
|
+
"path is required"
|
|
2478
|
+
)
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
if (!action) {
|
|
2482
|
+
return {
|
|
2483
|
+
ok: false,
|
|
2484
|
+
error: createError(
|
|
2485
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2486
|
+
"action is required"
|
|
2487
|
+
)
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
try {
|
|
2491
|
+
const body = JSON.stringify({ path, action });
|
|
2492
|
+
const response = await this.invokeOperation(
|
|
2493
|
+
path,
|
|
2494
|
+
DelegationAction.CHECK,
|
|
2495
|
+
body
|
|
2496
|
+
);
|
|
2497
|
+
if (!response.ok) {
|
|
2498
|
+
if (response.status === 403) {
|
|
2499
|
+
return { ok: true, data: false };
|
|
2500
|
+
}
|
|
2501
|
+
const errorText = await response.text();
|
|
2502
|
+
return {
|
|
2503
|
+
ok: false,
|
|
2504
|
+
error: createError(
|
|
2505
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2506
|
+
`Failed to check permission: ${response.status} - ${errorText}`,
|
|
2507
|
+
void 0,
|
|
2508
|
+
{ status: response.status, path, action }
|
|
2509
|
+
)
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
const data = await response.json();
|
|
2513
|
+
return { ok: true, data: data.allowed };
|
|
2514
|
+
} catch (error) {
|
|
2515
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2516
|
+
return {
|
|
2517
|
+
ok: false,
|
|
2518
|
+
error: createError(
|
|
2519
|
+
DelegationErrorCodes.ABORTED,
|
|
2520
|
+
"Request aborted",
|
|
2521
|
+
error
|
|
2522
|
+
)
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
return {
|
|
2526
|
+
ok: false,
|
|
2527
|
+
error: createError(
|
|
2528
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
2529
|
+
`Network error during permission check: ${String(error)}`,
|
|
2530
|
+
error instanceof Error ? error : void 0
|
|
2531
|
+
)
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
|
|
2537
|
+
// src/delegations/SharingService.schema.ts
|
|
2538
|
+
var import_zod5 = require("zod");
|
|
2539
|
+
var EncodedShareDataSchema = import_zod5.z.object({
|
|
2540
|
+
/** Private key in JWK format (must include d parameter) */
|
|
2541
|
+
key: JWKSchema.refine(
|
|
2542
|
+
(jwk) => typeof jwk.d === "string" && jwk.d.length > 0,
|
|
2543
|
+
{ message: "JWK must include private key (d parameter)" }
|
|
2544
|
+
),
|
|
2545
|
+
/** DID of the key */
|
|
2546
|
+
keyDid: import_zod5.z.string().min(1, "keyDid is required"),
|
|
2547
|
+
/** The delegation granting access */
|
|
2548
|
+
delegation: DelegationSchema,
|
|
2549
|
+
/** Resource path this link grants access to */
|
|
2550
|
+
path: import_zod5.z.string().min(1, "path is required"),
|
|
2551
|
+
/** TinyCloud host URL */
|
|
2552
|
+
host: import_zod5.z.string().url("host must be a valid URL"),
|
|
2553
|
+
/** Space ID */
|
|
2554
|
+
spaceId: import_zod5.z.string().min(1, "spaceId is required"),
|
|
2555
|
+
/** Schema version (must be 1) */
|
|
2556
|
+
version: import_zod5.z.literal(1)
|
|
2557
|
+
});
|
|
2558
|
+
var ReceiveOptionsSchema = import_zod5.z.object({
|
|
2559
|
+
/**
|
|
2560
|
+
* Whether to automatically create a sub-delegation to the current session key.
|
|
2561
|
+
* Default: true
|
|
2562
|
+
*/
|
|
2563
|
+
autoSubdelegate: import_zod5.z.boolean().optional(),
|
|
2564
|
+
/**
|
|
2565
|
+
* Whether to use the current session key for operations (requires autoSubdelegate).
|
|
2566
|
+
* Default: true
|
|
2567
|
+
*/
|
|
2568
|
+
useSessionKey: import_zod5.z.boolean().optional(),
|
|
2569
|
+
/**
|
|
2570
|
+
* Ingestion options passed to CapabilityKeyRegistry.
|
|
2571
|
+
*/
|
|
2572
|
+
ingestOptions: IngestOptionsSchema.optional()
|
|
2573
|
+
});
|
|
2574
|
+
var SharingServiceConfigSchema = import_zod5.z.object({
|
|
2575
|
+
/** TinyCloud host URLs */
|
|
2576
|
+
hosts: import_zod5.z.array(import_zod5.z.string().url()).min(1, "At least one host URL is required"),
|
|
2577
|
+
/**
|
|
2578
|
+
* Active session for authentication.
|
|
2579
|
+
* Required for generate(), optional for receive().
|
|
2580
|
+
*/
|
|
2581
|
+
session: import_zod5.z.unknown().refine(
|
|
2582
|
+
(val) => val === void 0 || val !== null && typeof val === "object",
|
|
2583
|
+
{ message: "Expected a ServiceSession object or undefined" }
|
|
2584
|
+
).optional(),
|
|
2585
|
+
/** Platform-specific invoke function */
|
|
2586
|
+
invoke: import_zod5.z.unknown().refine((val) => typeof val === "function", {
|
|
2587
|
+
message: "Expected an invoke function"
|
|
2588
|
+
}),
|
|
2589
|
+
/** Optional custom fetch implementation */
|
|
2590
|
+
fetch: import_zod5.z.unknown().refine(
|
|
2591
|
+
(val) => val === void 0 || typeof val === "function",
|
|
2592
|
+
{ message: "Expected a fetch function or undefined" }
|
|
2593
|
+
).optional(),
|
|
2594
|
+
/** Key provider for cryptographic operations */
|
|
2595
|
+
keyProvider: KeyProviderSchema,
|
|
2596
|
+
/** Capability key registry for key/delegation management */
|
|
2597
|
+
registry: import_zod5.z.unknown().refine(
|
|
2598
|
+
(val) => val !== null && typeof val === "object",
|
|
2599
|
+
{ message: "Expected an ICapabilityKeyRegistry object" }
|
|
2600
|
+
),
|
|
2601
|
+
/**
|
|
2602
|
+
* Delegation manager for creating delegations.
|
|
2603
|
+
* Required for generate(), optional for receive().
|
|
2604
|
+
*/
|
|
2605
|
+
delegationManager: import_zod5.z.unknown().refine(
|
|
2606
|
+
(val) => val === void 0 || val !== null && typeof val === "object",
|
|
2607
|
+
{ message: "Expected a DelegationManager object or undefined" }
|
|
2608
|
+
).optional(),
|
|
2609
|
+
/** Factory for creating KV service instances */
|
|
2610
|
+
createKVService: import_zod5.z.unknown().refine(
|
|
2611
|
+
(val) => typeof val === "function",
|
|
2612
|
+
{ message: "Expected a createKVService factory function" }
|
|
2613
|
+
),
|
|
2614
|
+
/** Base URL for sharing links (e.g., "https://share.myapp.com") */
|
|
2615
|
+
baseUrl: import_zod5.z.string().optional(),
|
|
2616
|
+
/**
|
|
2617
|
+
* Custom delegation creation function.
|
|
2618
|
+
*/
|
|
2619
|
+
createDelegation: import_zod5.z.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
2620
|
+
message: "Expected a createDelegation function or undefined"
|
|
2621
|
+
}).optional(),
|
|
2622
|
+
/**
|
|
2623
|
+
* WASM function for client-side delegation creation.
|
|
2624
|
+
*/
|
|
2625
|
+
createDelegationWasm: import_zod5.z.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
2626
|
+
message: "Expected a createDelegationWasm function or undefined"
|
|
2627
|
+
}).optional(),
|
|
2628
|
+
/**
|
|
2629
|
+
* Path prefix for KV operations.
|
|
2630
|
+
*/
|
|
2631
|
+
pathPrefix: import_zod5.z.string().optional(),
|
|
2632
|
+
/**
|
|
2633
|
+
* Session expiry time.
|
|
2634
|
+
*/
|
|
2635
|
+
sessionExpiry: import_zod5.z.date().optional(),
|
|
2636
|
+
/**
|
|
2637
|
+
* Callback to create a DIRECT delegation from wallet to share key.
|
|
2638
|
+
* This is the preferred method for long-lived share links because it
|
|
2639
|
+
* bypasses the session delegation chain entirely.
|
|
2640
|
+
*/
|
|
2641
|
+
onRootDelegationNeeded: import_zod5.z.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
2642
|
+
message: "Expected an onRootDelegationNeeded function or undefined"
|
|
2643
|
+
}).optional()
|
|
2644
|
+
});
|
|
2645
|
+
function validateEncodedShareData(data) {
|
|
2646
|
+
const result = EncodedShareDataSchema.safeParse(data);
|
|
2647
|
+
if (!result.success) {
|
|
2648
|
+
return {
|
|
2649
|
+
ok: false,
|
|
2650
|
+
error: {
|
|
2651
|
+
code: DelegationErrorCodes.VALIDATION_ERROR,
|
|
2652
|
+
message: `Invalid share data: ${result.error.message}`,
|
|
2653
|
+
service: "delegation",
|
|
2654
|
+
meta: { issues: result.error.issues }
|
|
2655
|
+
}
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
return { ok: true, data: result.data };
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// src/delegations/SharingService.ts
|
|
2662
|
+
var DEFAULT_READ_ACTIONS = ["tinycloud.kv/get", "tinycloud.kv/metadata"];
|
|
2663
|
+
var DEFAULT_EXPIRY_MS = 24 * 60 * 60 * 1e3;
|
|
2664
|
+
var BASE64_PREFIX = "tc1:";
|
|
2665
|
+
function createError2(code, message, cause, meta) {
|
|
2666
|
+
return {
|
|
2667
|
+
code,
|
|
2668
|
+
message,
|
|
2669
|
+
service: "delegation",
|
|
2670
|
+
cause,
|
|
2671
|
+
meta
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
function base64UrlEncode(data) {
|
|
2675
|
+
let base64;
|
|
2676
|
+
if (typeof btoa !== "undefined") {
|
|
2677
|
+
base64 = btoa(unescape(encodeURIComponent(data)));
|
|
2678
|
+
} else if (typeof Buffer !== "undefined") {
|
|
2679
|
+
base64 = Buffer.from(data, "utf-8").toString("base64");
|
|
2680
|
+
} else {
|
|
2681
|
+
throw new Error("No base64 encoding available");
|
|
2682
|
+
}
|
|
2683
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2684
|
+
}
|
|
2685
|
+
function base64UrlDecode(encoded) {
|
|
2686
|
+
let base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
2687
|
+
while (base64.length % 4) {
|
|
2688
|
+
base64 += "=";
|
|
2689
|
+
}
|
|
2690
|
+
if (typeof atob !== "undefined") {
|
|
2691
|
+
return decodeURIComponent(escape(atob(base64)));
|
|
2692
|
+
} else if (typeof Buffer !== "undefined") {
|
|
2693
|
+
return Buffer.from(base64, "base64").toString("utf-8");
|
|
2694
|
+
} else {
|
|
2695
|
+
throw new Error("No base64 decoding available");
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
var SharingService = class {
|
|
2699
|
+
/**
|
|
2700
|
+
* Creates a new SharingService instance.
|
|
2701
|
+
*/
|
|
2702
|
+
constructor(config) {
|
|
2703
|
+
this.hosts = config.hosts;
|
|
2704
|
+
this.session = config.session;
|
|
2705
|
+
this.invoke = config.invoke;
|
|
2706
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2707
|
+
this.keyProvider = config.keyProvider;
|
|
2708
|
+
this.registry = config.registry;
|
|
2709
|
+
this.delegationManager = config.delegationManager;
|
|
2710
|
+
this.createKVService = config.createKVService;
|
|
2711
|
+
this.baseUrl = (config.baseUrl ?? "").replace(/\/$/, "");
|
|
2712
|
+
this.createDelegationFn = config.createDelegation;
|
|
2713
|
+
this.createDelegationWasmFn = config.createDelegationWasm;
|
|
2714
|
+
this.pathPrefix = config.pathPrefix ?? "";
|
|
2715
|
+
this.sessionExpiry = config.sessionExpiry;
|
|
2716
|
+
this.onRootDelegationNeeded = config.onRootDelegationNeeded;
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Gets the primary host URL.
|
|
2720
|
+
*/
|
|
2721
|
+
get host() {
|
|
2722
|
+
return this.hosts[0];
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Updates the session (e.g., after re-authentication).
|
|
2726
|
+
*/
|
|
2727
|
+
updateSession(session) {
|
|
2728
|
+
this.session = session;
|
|
2729
|
+
}
|
|
2730
|
+
/**
|
|
2731
|
+
* Updates the service configuration.
|
|
2732
|
+
* Used to add full capabilities (session, delegationManager, createDelegation, createDelegationWasm) after signIn.
|
|
2733
|
+
*/
|
|
2734
|
+
updateConfig(config) {
|
|
2735
|
+
if (config.session !== void 0) {
|
|
2736
|
+
this.session = config.session;
|
|
2737
|
+
}
|
|
2738
|
+
if (config.delegationManager !== void 0) {
|
|
2739
|
+
this.delegationManager = config.delegationManager;
|
|
2740
|
+
}
|
|
2741
|
+
if (config.createDelegation !== void 0) {
|
|
2742
|
+
this.createDelegationFn = config.createDelegation;
|
|
2743
|
+
}
|
|
2744
|
+
if (config.createDelegationWasm !== void 0) {
|
|
2745
|
+
this.createDelegationWasmFn = config.createDelegationWasm;
|
|
2746
|
+
}
|
|
2747
|
+
if (config.sessionExpiry !== void 0) {
|
|
2748
|
+
this.sessionExpiry = config.sessionExpiry;
|
|
2749
|
+
}
|
|
2750
|
+
if (config.onRootDelegationNeeded !== void 0) {
|
|
2751
|
+
this.onRootDelegationNeeded = config.onRootDelegationNeeded;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
/**
|
|
2755
|
+
* Generate a sharing link with an embedded private key.
|
|
2756
|
+
*
|
|
2757
|
+
* Flow:
|
|
2758
|
+
* 1. Spawn new session key (unique per share)
|
|
2759
|
+
* 2. Create delegation from current session to spawned key
|
|
2760
|
+
* 3. Package: { key (with private!), delegation, path, host }
|
|
2761
|
+
* 4. Encode based on schema (base64 for now)
|
|
2762
|
+
* 5. Return link string
|
|
2763
|
+
*/
|
|
2764
|
+
async generate(params) {
|
|
2765
|
+
if (!this.session) {
|
|
2766
|
+
return {
|
|
2767
|
+
ok: false,
|
|
2768
|
+
error: createError2(
|
|
2769
|
+
DelegationErrorCodes.NOT_INITIALIZED,
|
|
2770
|
+
"Session required for generating sharing links. Call signIn() first."
|
|
2771
|
+
)
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
if (!this.createDelegationWasmFn && !this.createDelegationFn && !this.delegationManager) {
|
|
2775
|
+
return {
|
|
2776
|
+
ok: false,
|
|
2777
|
+
error: createError2(
|
|
2778
|
+
DelegationErrorCodes.NOT_INITIALIZED,
|
|
2779
|
+
"DelegationManager, createDelegation, or createDelegationWasm function required for generating sharing links."
|
|
2780
|
+
)
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
if (!params.path) {
|
|
2784
|
+
return {
|
|
2785
|
+
ok: false,
|
|
2786
|
+
error: createError2(
|
|
2787
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2788
|
+
"path is required"
|
|
2789
|
+
)
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
const actions = params.actions ?? DEFAULT_READ_ACTIONS;
|
|
2793
|
+
const requestedExpiry = params.expiry ?? new Date(Date.now() + DEFAULT_EXPIRY_MS);
|
|
2794
|
+
let expiry = requestedExpiry;
|
|
2795
|
+
const schema = params.schema ?? "base64";
|
|
2796
|
+
const fullPath = this.pathPrefix ? `${this.pathPrefix}/${params.path}`.replace(/\/+/g, "/") : params.path;
|
|
2797
|
+
if (schema !== "base64") {
|
|
2798
|
+
return {
|
|
2799
|
+
ok: false,
|
|
2800
|
+
error: createError2(
|
|
2801
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
2802
|
+
`Schema '${schema}' not implemented. Only 'base64' is supported.`
|
|
2803
|
+
)
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
let keyId;
|
|
2807
|
+
let keyDid;
|
|
2808
|
+
let keyJwk;
|
|
2809
|
+
try {
|
|
2810
|
+
const shareKeyName = `share:${Date.now()}:${Math.random().toString(36).substring(2, 10)}`;
|
|
2811
|
+
keyId = await this.keyProvider.createSessionKey(shareKeyName);
|
|
2812
|
+
keyDid = await this.keyProvider.getDID(keyId);
|
|
2813
|
+
keyJwk = this.keyProvider.getJWK(keyId);
|
|
2814
|
+
if (!keyJwk.d) {
|
|
2815
|
+
return {
|
|
2816
|
+
ok: false,
|
|
2817
|
+
error: createError2(
|
|
2818
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
2819
|
+
"KeyProvider did not return private key (d parameter) in JWK"
|
|
2820
|
+
)
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
} catch (err5) {
|
|
2824
|
+
return {
|
|
2825
|
+
ok: false,
|
|
2826
|
+
error: createError2(
|
|
2827
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
2828
|
+
`Failed to generate session key for share: ${err5 instanceof Error ? err5.message : String(err5)}`,
|
|
2829
|
+
err5 instanceof Error ? err5 : void 0
|
|
2830
|
+
)
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
let delegation;
|
|
2834
|
+
const plainDID = keyDid.split("#")[0];
|
|
2835
|
+
const handleDelegationResult = (result) => {
|
|
2836
|
+
if (result && typeof result === "object" && "ok" in result) {
|
|
2837
|
+
return result;
|
|
2838
|
+
}
|
|
2839
|
+
return result;
|
|
2840
|
+
};
|
|
2841
|
+
const canSatisfyFromRegistry = this.findSuitableKeyForDelegation(
|
|
2842
|
+
fullPath,
|
|
2843
|
+
actions,
|
|
2844
|
+
requestedExpiry
|
|
2845
|
+
);
|
|
2846
|
+
if (canSatisfyFromRegistry) {
|
|
2847
|
+
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
2848
|
+
const parsed = handleDelegationResult(delegationResult);
|
|
2849
|
+
if ("ok" in parsed && parsed.ok === false) {
|
|
2850
|
+
return parsed;
|
|
2851
|
+
}
|
|
2852
|
+
delegation = parsed;
|
|
2853
|
+
} else if (this.onRootDelegationNeeded) {
|
|
2854
|
+
try {
|
|
2855
|
+
const rootDelegation = await this.onRootDelegationNeeded({
|
|
2856
|
+
shareKeyDID: plainDID,
|
|
2857
|
+
spaceId: this.session.spaceId,
|
|
2858
|
+
path: fullPath,
|
|
2859
|
+
actions,
|
|
2860
|
+
requestedExpiry
|
|
2861
|
+
});
|
|
2862
|
+
if (rootDelegation) {
|
|
2863
|
+
delegation = rootDelegation;
|
|
2864
|
+
expiry = requestedExpiry;
|
|
2865
|
+
} else {
|
|
2866
|
+
const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
|
|
2867
|
+
expiry = fallbackResult.expiry;
|
|
2868
|
+
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
2869
|
+
const parsed = handleDelegationResult(delegationResult);
|
|
2870
|
+
if ("ok" in parsed && parsed.ok === false) {
|
|
2871
|
+
return parsed;
|
|
2872
|
+
}
|
|
2873
|
+
delegation = parsed;
|
|
2874
|
+
}
|
|
2875
|
+
} catch (err5) {
|
|
2876
|
+
const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
|
|
2877
|
+
expiry = fallbackResult.expiry;
|
|
2878
|
+
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
2879
|
+
const parsed = handleDelegationResult(delegationResult);
|
|
2880
|
+
if ("ok" in parsed && parsed.ok === false) {
|
|
2881
|
+
return parsed;
|
|
2882
|
+
}
|
|
2883
|
+
delegation = parsed;
|
|
2884
|
+
}
|
|
2885
|
+
} else {
|
|
2886
|
+
const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
|
|
2887
|
+
expiry = fallbackResult.expiry;
|
|
2888
|
+
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
2889
|
+
const parsed = handleDelegationResult(delegationResult);
|
|
2890
|
+
if ("ok" in parsed && parsed.ok === false) {
|
|
2891
|
+
return parsed;
|
|
2892
|
+
}
|
|
2893
|
+
delegation = parsed;
|
|
2894
|
+
}
|
|
2895
|
+
const shareData = {
|
|
2896
|
+
key: keyJwk,
|
|
2897
|
+
keyDid,
|
|
2898
|
+
delegation,
|
|
2899
|
+
path: fullPath,
|
|
2900
|
+
host: this.host,
|
|
2901
|
+
spaceId: this.session.spaceId,
|
|
2902
|
+
version: 1
|
|
2903
|
+
};
|
|
2904
|
+
const encodedData = this.encodeLink(shareData, schema);
|
|
2905
|
+
const baseUrl = params.baseUrl ?? this.baseUrl;
|
|
2906
|
+
const url = baseUrl ? `${baseUrl}/share/${encodedData}` : encodedData;
|
|
2907
|
+
const shareLink = {
|
|
2908
|
+
token: encodedData,
|
|
2909
|
+
url,
|
|
2910
|
+
delegation,
|
|
2911
|
+
schema,
|
|
2912
|
+
expiresAt: expiry,
|
|
2913
|
+
description: params.description
|
|
2914
|
+
};
|
|
2915
|
+
return { ok: true, data: shareLink };
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Check if any key in the registry can satisfy the delegation request.
|
|
2919
|
+
* A key can satisfy if it has a delegation that:
|
|
2920
|
+
* 1. Covers the required path (exact match or parent path)
|
|
2921
|
+
* 2. Has all required actions
|
|
2922
|
+
* 3. Has sufficient expiry (delegation.expiry >= requestedExpiry)
|
|
2923
|
+
* 4. Allows sub-delegation
|
|
2924
|
+
* @internal
|
|
2925
|
+
*/
|
|
2926
|
+
findSuitableKeyForDelegation(path, actions, requestedExpiry) {
|
|
2927
|
+
if (this.sessionExpiry && requestedExpiry <= this.sessionExpiry) {
|
|
2928
|
+
return true;
|
|
2929
|
+
}
|
|
2930
|
+
const allKeys = this.registry.getAllKeys();
|
|
2931
|
+
for (const key of allKeys) {
|
|
2932
|
+
const delegations = this.registry.getDelegationsForKey(key.id);
|
|
2933
|
+
for (const delegation of delegations) {
|
|
2934
|
+
if (!this.registry.isDelegationValid(delegation)) {
|
|
2935
|
+
continue;
|
|
2936
|
+
}
|
|
2937
|
+
if (delegation.expiry < requestedExpiry) {
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
if (delegation.allowSubDelegation === false) {
|
|
2941
|
+
continue;
|
|
2942
|
+
}
|
|
2943
|
+
const delegationPath = delegation.path || "";
|
|
2944
|
+
if (!this.pathMatches(delegationPath, path)) {
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
const delegationActions = delegation.actions || [];
|
|
2948
|
+
const hasAllActions = actions.every(
|
|
2949
|
+
(action) => delegationActions.includes(action) || delegationActions.includes("*")
|
|
2950
|
+
);
|
|
2951
|
+
if (!hasAllActions) {
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2954
|
+
return true;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return false;
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Check if a delegation path matches/covers the requested path.
|
|
2961
|
+
* A delegation path covers the request if:
|
|
2962
|
+
* - It's an exact match
|
|
2963
|
+
* - It's a parent path (e.g., delegation for "" covers "foo/bar")
|
|
2964
|
+
* - It uses wildcards that match
|
|
2965
|
+
* @internal
|
|
2966
|
+
*/
|
|
2967
|
+
pathMatches(delegationPath, requestedPath) {
|
|
2968
|
+
if (delegationPath === "" || delegationPath === "*") {
|
|
2969
|
+
return true;
|
|
2970
|
+
}
|
|
2971
|
+
if (delegationPath === requestedPath) {
|
|
2972
|
+
return true;
|
|
2973
|
+
}
|
|
2974
|
+
const normalizedDelegation = delegationPath.replace(/\/$/, "");
|
|
2975
|
+
const normalizedRequest = requestedPath.replace(/\/$/, "");
|
|
2976
|
+
if (normalizedRequest.startsWith(normalizedDelegation + "/")) {
|
|
2977
|
+
return true;
|
|
2978
|
+
}
|
|
2979
|
+
return false;
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Handle fallback to session extension when root delegation is not available.
|
|
2983
|
+
* @internal
|
|
2984
|
+
*/
|
|
2985
|
+
async handleSessionExtensionFallback(requestedExpiry) {
|
|
2986
|
+
return { expiry: this.sessionExpiry ?? requestedExpiry };
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* Create a delegation from the current session to a share key.
|
|
2990
|
+
* This is the fallback path when root delegation is not available.
|
|
2991
|
+
* @internal
|
|
2992
|
+
*/
|
|
2993
|
+
async createSessionDelegation(delegateDID, path, actions, expiry) {
|
|
2994
|
+
if (!this.session) {
|
|
2995
|
+
return {
|
|
2996
|
+
ok: false,
|
|
2997
|
+
error: createError2(
|
|
2998
|
+
DelegationErrorCodes.NOT_INITIALIZED,
|
|
2999
|
+
"Session required for creating delegation"
|
|
3000
|
+
)
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
if (this.createDelegationWasmFn) {
|
|
3004
|
+
try {
|
|
3005
|
+
const wasmResult = this.createDelegationWasmFn({
|
|
3006
|
+
session: this.session,
|
|
3007
|
+
delegateDID,
|
|
3008
|
+
spaceId: this.session.spaceId,
|
|
3009
|
+
path,
|
|
3010
|
+
actions,
|
|
3011
|
+
expirationSecs: Math.floor(expiry.getTime() / 1e3)
|
|
3012
|
+
});
|
|
3013
|
+
const registerRes = await this.fetchFn(`${this.host}/delegate`, {
|
|
3014
|
+
method: "POST",
|
|
3015
|
+
headers: {
|
|
3016
|
+
Authorization: wasmResult.delegation
|
|
3017
|
+
}
|
|
3018
|
+
});
|
|
3019
|
+
if (!registerRes.ok) {
|
|
3020
|
+
const errorText = await registerRes.text();
|
|
3021
|
+
return {
|
|
3022
|
+
ok: false,
|
|
3023
|
+
error: createError2(
|
|
3024
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
3025
|
+
`Failed to register delegation with server: ${registerRes.status} ${errorText}`
|
|
3026
|
+
)
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
3029
|
+
return {
|
|
3030
|
+
cid: wasmResult.cid,
|
|
3031
|
+
delegateDID: wasmResult.delegateDID,
|
|
3032
|
+
spaceId: this.session.spaceId,
|
|
3033
|
+
path: wasmResult.path,
|
|
3034
|
+
actions: wasmResult.actions,
|
|
3035
|
+
expiry: wasmResult.expiry,
|
|
3036
|
+
isRevoked: false,
|
|
3037
|
+
authHeader: wasmResult.delegation,
|
|
3038
|
+
allowSubDelegation: true,
|
|
3039
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3040
|
+
};
|
|
3041
|
+
} catch (err5) {
|
|
3042
|
+
return {
|
|
3043
|
+
ok: false,
|
|
3044
|
+
error: createError2(
|
|
3045
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
3046
|
+
`Failed to create delegation via WASM: ${err5 instanceof Error ? err5.message : String(err5)}`,
|
|
3047
|
+
err5 instanceof Error ? err5 : void 0
|
|
3048
|
+
)
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
} else {
|
|
3052
|
+
const delegationParams = {
|
|
3053
|
+
delegateDID,
|
|
3054
|
+
path,
|
|
3055
|
+
actions,
|
|
3056
|
+
expiry,
|
|
3057
|
+
disableSubDelegation: false
|
|
3058
|
+
};
|
|
3059
|
+
const delegationResult = this.createDelegationFn ? await this.createDelegationFn(delegationParams) : await this.delegationManager.create(delegationParams);
|
|
3060
|
+
if (!delegationResult.ok) {
|
|
3061
|
+
return {
|
|
3062
|
+
ok: false,
|
|
3063
|
+
error: createError2(
|
|
3064
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
3065
|
+
`Failed to create delegation for share: ${delegationResult.error.message}`,
|
|
3066
|
+
delegationResult.error.cause,
|
|
3067
|
+
delegationResult.error.meta
|
|
3068
|
+
)
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
return delegationResult.data;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Receive and activate a sharing link.
|
|
3076
|
+
*
|
|
3077
|
+
* Flow:
|
|
3078
|
+
* 1. Decode link -> extract { key, delegation, path, host }
|
|
3079
|
+
* 2. Ingest key into CapabilityKeyRegistry
|
|
3080
|
+
* 3. If autoSubdelegate (default true) + useSessionKey:
|
|
3081
|
+
* - Create sub-delegation from ingested key -> current session
|
|
3082
|
+
* - Register sub-delegation capabilities
|
|
3083
|
+
* 4. Return ShareAccess with pre-configured KV service
|
|
3084
|
+
*/
|
|
3085
|
+
async receive(link, options = {}) {
|
|
3086
|
+
const {
|
|
3087
|
+
autoSubdelegate = true,
|
|
3088
|
+
useSessionKey = true,
|
|
3089
|
+
ingestOptions
|
|
3090
|
+
} = options;
|
|
3091
|
+
const decodeResult = this.decodeLinkWithValidation(link);
|
|
3092
|
+
if (!decodeResult.ok) {
|
|
3093
|
+
return decodeResult;
|
|
3094
|
+
}
|
|
3095
|
+
const shareData = decodeResult.data;
|
|
3096
|
+
const delegationExpiry = new Date(shareData.delegation.expiry);
|
|
3097
|
+
if (delegationExpiry < /* @__PURE__ */ new Date()) {
|
|
3098
|
+
return {
|
|
3099
|
+
ok: false,
|
|
3100
|
+
error: createError2(
|
|
3101
|
+
DelegationErrorCodes.AUTH_EXPIRED,
|
|
3102
|
+
"Sharing link has expired"
|
|
3103
|
+
)
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
if (shareData.delegation.isRevoked) {
|
|
3107
|
+
return {
|
|
3108
|
+
ok: false,
|
|
3109
|
+
error: createError2(
|
|
3110
|
+
DelegationErrorCodes.REVOKED,
|
|
3111
|
+
"Sharing link has been revoked"
|
|
3112
|
+
)
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
const keyInfo = {
|
|
3116
|
+
id: `ingested:${shareData.keyDid}`,
|
|
3117
|
+
did: shareData.keyDid,
|
|
3118
|
+
type: "ingested",
|
|
3119
|
+
jwk: shareData.key,
|
|
3120
|
+
priority: 2
|
|
3121
|
+
// Ingested keys have lowest priority
|
|
3122
|
+
};
|
|
3123
|
+
this.registry.ingestKey(keyInfo, shareData.delegation, ingestOptions);
|
|
3124
|
+
let activeDelegation = shareData.delegation;
|
|
3125
|
+
let activeKey = keyInfo;
|
|
3126
|
+
if (autoSubdelegate && useSessionKey && this.session) {
|
|
3127
|
+
try {
|
|
3128
|
+
} catch (err5) {
|
|
3129
|
+
console.warn("Auto-subdelegation failed, using ingested key directly:", err5);
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
const authHeader = shareData.delegation.authHeader ?? `Bearer ${shareData.delegation.cid}`;
|
|
3133
|
+
const shareSession = {
|
|
3134
|
+
delegationHeader: { Authorization: authHeader },
|
|
3135
|
+
delegationCid: shareData.delegation.cid,
|
|
3136
|
+
spaceId: shareData.spaceId,
|
|
3137
|
+
verificationMethod: shareData.keyDid,
|
|
3138
|
+
jwk: shareData.key
|
|
3139
|
+
};
|
|
3140
|
+
const kvService = this.createKVService({
|
|
3141
|
+
hosts: [shareData.host],
|
|
3142
|
+
session: shareSession,
|
|
3143
|
+
invoke: this.invoke,
|
|
3144
|
+
fetch: this.fetchFn,
|
|
3145
|
+
pathPrefix: shareData.path
|
|
3146
|
+
});
|
|
3147
|
+
const shareAccess = {
|
|
3148
|
+
delegation: activeDelegation,
|
|
3149
|
+
key: activeKey,
|
|
3150
|
+
kv: kvService,
|
|
3151
|
+
spaceId: shareData.spaceId,
|
|
3152
|
+
path: shareData.path
|
|
3153
|
+
};
|
|
3154
|
+
return { ok: true, data: shareAccess };
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Encode sharing data into a link string.
|
|
3158
|
+
*
|
|
3159
|
+
* @param data - The share data to encode
|
|
3160
|
+
* @param schema - The encoding schema (default: "base64")
|
|
3161
|
+
* @returns Encoded link string
|
|
3162
|
+
*/
|
|
3163
|
+
encodeLink(data, schema = "base64") {
|
|
3164
|
+
if (schema !== "base64") {
|
|
3165
|
+
throw new Error(`Schema '${schema}' not implemented. Only 'base64' is supported.`);
|
|
3166
|
+
}
|
|
3167
|
+
const jsonString = JSON.stringify(data);
|
|
3168
|
+
const encoded = base64UrlEncode(jsonString);
|
|
3169
|
+
return `${BASE64_PREFIX}${encoded}`;
|
|
3170
|
+
}
|
|
3171
|
+
/**
|
|
3172
|
+
* Decode a link string into sharing data.
|
|
3173
|
+
*
|
|
3174
|
+
* @param link - The encoded link string (may include URL prefix)
|
|
3175
|
+
* @returns Decoded share data
|
|
3176
|
+
* @throws Error if link format is invalid or data fails validation
|
|
3177
|
+
*/
|
|
3178
|
+
decodeLink(link) {
|
|
3179
|
+
const result = this.decodeLinkWithValidation(link);
|
|
3180
|
+
if (!result.ok) {
|
|
3181
|
+
throw new Error(result.error.message);
|
|
3182
|
+
}
|
|
3183
|
+
return result.data;
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
3186
|
+
* Decode and validate a link string into sharing data.
|
|
3187
|
+
*
|
|
3188
|
+
* Internal method that returns a Result instead of throwing.
|
|
3189
|
+
* Used by receive() for proper error handling.
|
|
3190
|
+
*
|
|
3191
|
+
* @param link - The encoded link string (may include URL prefix)
|
|
3192
|
+
* @returns Result with decoded share data or validation error
|
|
3193
|
+
*/
|
|
3194
|
+
decodeLinkWithValidation(link) {
|
|
3195
|
+
let encoded = link;
|
|
3196
|
+
if (link.includes("/share/")) {
|
|
3197
|
+
const parts = link.split("/share/");
|
|
3198
|
+
encoded = parts[parts.length - 1];
|
|
3199
|
+
}
|
|
3200
|
+
if (link.includes("?share=")) {
|
|
3201
|
+
try {
|
|
3202
|
+
const url = new URL(link);
|
|
3203
|
+
encoded = url.searchParams.get("share") ?? encoded;
|
|
3204
|
+
} catch {
|
|
3205
|
+
return {
|
|
3206
|
+
ok: false,
|
|
3207
|
+
error: createError2(
|
|
3208
|
+
DelegationErrorCodes.INVALID_TOKEN,
|
|
3209
|
+
"Invalid URL format in sharing link"
|
|
3210
|
+
)
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
if (!encoded.startsWith(BASE64_PREFIX)) {
|
|
3215
|
+
return {
|
|
3216
|
+
ok: false,
|
|
3217
|
+
error: createError2(
|
|
3218
|
+
DelegationErrorCodes.INVALID_TOKEN,
|
|
3219
|
+
`Invalid sharing link format. Expected prefix '${BASE64_PREFIX}'`
|
|
3220
|
+
)
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
const base64Data = encoded.slice(BASE64_PREFIX.length);
|
|
3224
|
+
let jsonString;
|
|
3225
|
+
try {
|
|
3226
|
+
jsonString = base64UrlDecode(base64Data);
|
|
3227
|
+
} catch (err5) {
|
|
3228
|
+
return {
|
|
3229
|
+
ok: false,
|
|
3230
|
+
error: createError2(
|
|
3231
|
+
DelegationErrorCodes.INVALID_TOKEN,
|
|
3232
|
+
`Failed to decode base64 data: ${err5 instanceof Error ? err5.message : String(err5)}`,
|
|
3233
|
+
err5 instanceof Error ? err5 : void 0
|
|
3234
|
+
)
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
let parsed;
|
|
3238
|
+
try {
|
|
3239
|
+
parsed = JSON.parse(jsonString);
|
|
3240
|
+
} catch (err5) {
|
|
3241
|
+
return {
|
|
3242
|
+
ok: false,
|
|
3243
|
+
error: createError2(
|
|
3244
|
+
DelegationErrorCodes.INVALID_TOKEN,
|
|
3245
|
+
`Failed to parse share data JSON: ${err5 instanceof Error ? err5.message : String(err5)}`,
|
|
3246
|
+
err5 instanceof Error ? err5 : void 0
|
|
3247
|
+
)
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
if (parsed && typeof parsed === "object" && "delegation" in parsed && parsed.delegation && typeof parsed.delegation === "object" && "expiry" in parsed.delegation && typeof parsed.delegation.expiry === "string") {
|
|
3251
|
+
parsed.delegation.expiry = new Date(parsed.delegation.expiry);
|
|
3252
|
+
}
|
|
3253
|
+
const validationResult = validateEncodedShareData(parsed);
|
|
3254
|
+
if (!validationResult.ok) {
|
|
3255
|
+
return {
|
|
3256
|
+
ok: false,
|
|
3257
|
+
error: createError2(
|
|
3258
|
+
DelegationErrorCodes.INVALID_TOKEN,
|
|
3259
|
+
validationResult.error.message,
|
|
3260
|
+
void 0,
|
|
3261
|
+
validationResult.error.meta
|
|
3262
|
+
)
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
return { ok: true, data: validationResult.data };
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3268
|
+
function createSharingService(config) {
|
|
3269
|
+
return new SharingService(config);
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// src/authorization/CapabilityKeyRegistry.ts
|
|
3273
|
+
var import_sdk_services3 = require("@tinycloud/sdk-services");
|
|
3274
|
+
var SERVICE_NAME2 = "capability-key-registry";
|
|
3275
|
+
var CapabilityKeyRegistryErrorCodes = {
|
|
3276
|
+
/** Key not found in registry */
|
|
3277
|
+
KEY_NOT_FOUND: "KEY_NOT_FOUND",
|
|
3278
|
+
/** No key available for the requested capability */
|
|
3279
|
+
NO_CAPABLE_KEY: "NO_CAPABLE_KEY",
|
|
3280
|
+
/** Delegation has expired */
|
|
3281
|
+
DELEGATION_EXPIRED: "DELEGATION_EXPIRED",
|
|
3282
|
+
/** Delegation has been revoked */
|
|
3283
|
+
DELEGATION_REVOKED: "DELEGATION_REVOKED",
|
|
3284
|
+
/** Invalid delegation data */
|
|
3285
|
+
INVALID_DELEGATION: "INVALID_DELEGATION",
|
|
3286
|
+
/** Key already registered */
|
|
3287
|
+
KEY_EXISTS: "KEY_EXISTS"
|
|
3288
|
+
};
|
|
3289
|
+
var CapabilityKeyRegistry = class {
|
|
3290
|
+
constructor() {
|
|
3291
|
+
/**
|
|
3292
|
+
* Registry of all keys indexed by ID.
|
|
3293
|
+
*/
|
|
3294
|
+
this.keys = /* @__PURE__ */ new Map();
|
|
3295
|
+
/**
|
|
3296
|
+
* Delegation storage.
|
|
3297
|
+
*/
|
|
3298
|
+
this.store = {
|
|
3299
|
+
byKey: /* @__PURE__ */ new Map(),
|
|
3300
|
+
byCid: /* @__PURE__ */ new Map(),
|
|
3301
|
+
byCapability: /* @__PURE__ */ new Map()
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
// ===========================================================================
|
|
3305
|
+
// Key Management
|
|
3306
|
+
// ===========================================================================
|
|
3307
|
+
/**
|
|
3308
|
+
* Register a key with its associated delegations.
|
|
3309
|
+
*
|
|
3310
|
+
* @param key - Key information
|
|
3311
|
+
* @param delegations - Delegations granted to this key
|
|
3312
|
+
*/
|
|
3313
|
+
registerKey(key, delegations) {
|
|
3314
|
+
this.keys.set(key.id, key);
|
|
3315
|
+
if (!this.store.byKey.has(key.id)) {
|
|
3316
|
+
this.store.byKey.set(key.id, []);
|
|
3317
|
+
}
|
|
3318
|
+
for (const delegation of delegations) {
|
|
3319
|
+
this.addDelegation(key, delegation);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Remove a key and all its associated delegations.
|
|
3324
|
+
*
|
|
3325
|
+
* @param keyId - The key ID to remove
|
|
3326
|
+
*/
|
|
3327
|
+
removeKey(keyId) {
|
|
3328
|
+
const delegations = this.store.byKey.get(keyId) || [];
|
|
3329
|
+
for (const delegation of delegations) {
|
|
3330
|
+
this.store.byCid.delete(delegation.cid);
|
|
3331
|
+
}
|
|
3332
|
+
for (const [capKey, entries] of this.store.byCapability) {
|
|
3333
|
+
const filtered = entries.filter(
|
|
3334
|
+
(entry) => !entry.keys.some((k) => k.id === keyId)
|
|
3335
|
+
);
|
|
3336
|
+
if (filtered.length === 0) {
|
|
3337
|
+
this.store.byCapability.delete(capKey);
|
|
3338
|
+
} else {
|
|
3339
|
+
for (const entry of filtered) {
|
|
3340
|
+
entry.keys = entry.keys.filter((k) => k.id !== keyId);
|
|
3341
|
+
}
|
|
3342
|
+
this.store.byCapability.set(capKey, filtered.filter((e) => e.keys.length > 0));
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
this.store.byKey.delete(keyId);
|
|
3346
|
+
this.keys.delete(keyId);
|
|
3347
|
+
}
|
|
3348
|
+
// ===========================================================================
|
|
3349
|
+
// Capability Lookup
|
|
3350
|
+
// ===========================================================================
|
|
3351
|
+
/**
|
|
3352
|
+
* Get a key that can exercise the specified capability.
|
|
3353
|
+
*
|
|
3354
|
+
* Key selection algorithm:
|
|
3355
|
+
* 1. Filter keys that have the required capability
|
|
3356
|
+
* 2. Check delegation validity (not expired, not revoked)
|
|
3357
|
+
* 3. Sort by priority (session=0, main=1, ingested=2)
|
|
3358
|
+
* 4. Return highest priority valid key
|
|
3359
|
+
*
|
|
3360
|
+
* @param resource - Resource URI
|
|
3361
|
+
* @param action - Action to perform
|
|
3362
|
+
* @returns The best matching key, or null if none available
|
|
3363
|
+
*/
|
|
3364
|
+
getKeyForCapability(resource, action) {
|
|
3365
|
+
const matchingEntries = this.findMatchingEntries(resource, action);
|
|
3366
|
+
if (matchingEntries.length === 0) {
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
const validKeys = [];
|
|
3370
|
+
for (const entry of matchingEntries) {
|
|
3371
|
+
if (!this.isDelegationValid(entry.delegation)) {
|
|
3372
|
+
continue;
|
|
3373
|
+
}
|
|
3374
|
+
for (const key of entry.keys) {
|
|
3375
|
+
if (!validKeys.some((k) => k.id === key.id)) {
|
|
3376
|
+
validKeys.push(key);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
if (validKeys.length === 0) {
|
|
3381
|
+
return null;
|
|
3382
|
+
}
|
|
3383
|
+
validKeys.sort((a, b) => a.priority - b.priority);
|
|
3384
|
+
return validKeys[0];
|
|
3385
|
+
}
|
|
3386
|
+
/**
|
|
3387
|
+
* Get all registered capabilities.
|
|
3388
|
+
*
|
|
3389
|
+
* @returns All capability entries in the registry
|
|
3390
|
+
*/
|
|
3391
|
+
getAllCapabilities() {
|
|
3392
|
+
const all = [];
|
|
3393
|
+
for (const entries of this.store.byCapability.values()) {
|
|
3394
|
+
all.push(...entries);
|
|
3395
|
+
}
|
|
3396
|
+
return all;
|
|
3397
|
+
}
|
|
3398
|
+
// ===========================================================================
|
|
3399
|
+
// Delegation Tracking
|
|
3400
|
+
// ===========================================================================
|
|
3401
|
+
/**
|
|
3402
|
+
* Get all delegations for a specific key.
|
|
3403
|
+
*
|
|
3404
|
+
* @param keyId - The key ID
|
|
3405
|
+
* @returns Array of delegations for this key
|
|
3406
|
+
*/
|
|
3407
|
+
getDelegationsForKey(keyId) {
|
|
3408
|
+
return this.store.byKey.get(keyId) || [];
|
|
3409
|
+
}
|
|
3410
|
+
// ===========================================================================
|
|
3411
|
+
// Ingestion
|
|
3412
|
+
// ===========================================================================
|
|
3413
|
+
/**
|
|
3414
|
+
* Ingest a key and delegation from an external source.
|
|
3415
|
+
*
|
|
3416
|
+
* @param key - Key information to ingest
|
|
3417
|
+
* @param delegation - Delegation to associate with the key
|
|
3418
|
+
* @param options - Ingestion options
|
|
3419
|
+
*/
|
|
3420
|
+
ingestKey(key, delegation, options) {
|
|
3421
|
+
const keyToStore = options?.priority !== void 0 ? { ...key, priority: options.priority } : key;
|
|
3422
|
+
this.keys.set(keyToStore.id, keyToStore);
|
|
3423
|
+
if (!this.store.byKey.has(keyToStore.id)) {
|
|
3424
|
+
this.store.byKey.set(keyToStore.id, []);
|
|
3425
|
+
}
|
|
3426
|
+
this.addDelegation(keyToStore, delegation);
|
|
3427
|
+
}
|
|
3428
|
+
// ===========================================================================
|
|
3429
|
+
// Validation
|
|
3430
|
+
// ===========================================================================
|
|
3431
|
+
/**
|
|
3432
|
+
* Check if a delegation is currently valid.
|
|
3433
|
+
*
|
|
3434
|
+
* @param delegation - The delegation to check
|
|
3435
|
+
* @returns true if valid, false if expired or revoked
|
|
3436
|
+
*/
|
|
3437
|
+
isDelegationValid(delegation) {
|
|
3438
|
+
if (delegation.isRevoked) {
|
|
3439
|
+
return false;
|
|
3440
|
+
}
|
|
3441
|
+
const now = /* @__PURE__ */ new Date();
|
|
3442
|
+
if (delegation.expiry && delegation.expiry < now) {
|
|
3443
|
+
return false;
|
|
3444
|
+
}
|
|
3445
|
+
return true;
|
|
3446
|
+
}
|
|
3447
|
+
// ===========================================================================
|
|
3448
|
+
// Key Access
|
|
3449
|
+
// ===========================================================================
|
|
3450
|
+
/**
|
|
3451
|
+
* Get a key by its ID.
|
|
3452
|
+
*
|
|
3453
|
+
* @param keyId - The key ID
|
|
3454
|
+
* @returns The key info, or undefined if not found
|
|
3455
|
+
*/
|
|
3456
|
+
getKey(keyId) {
|
|
3457
|
+
return this.keys.get(keyId);
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Get all registered keys.
|
|
3461
|
+
*
|
|
3462
|
+
* @returns Array of all registered keys
|
|
3463
|
+
*/
|
|
3464
|
+
getAllKeys() {
|
|
3465
|
+
return Array.from(this.keys.values());
|
|
3466
|
+
}
|
|
3467
|
+
// ===========================================================================
|
|
3468
|
+
// Clear
|
|
3469
|
+
// ===========================================================================
|
|
3470
|
+
/**
|
|
3471
|
+
* Clear all registered keys and delegations.
|
|
3472
|
+
*/
|
|
3473
|
+
clear() {
|
|
3474
|
+
this.keys.clear();
|
|
3475
|
+
this.store.byKey.clear();
|
|
3476
|
+
this.store.byCid.clear();
|
|
3477
|
+
this.store.byCapability.clear();
|
|
3478
|
+
}
|
|
3479
|
+
// ===========================================================================
|
|
3480
|
+
// Revocation
|
|
3481
|
+
// ===========================================================================
|
|
3482
|
+
/**
|
|
3483
|
+
* Revoke a delegation by CID.
|
|
3484
|
+
*
|
|
3485
|
+
* @param cid - The delegation CID to revoke
|
|
3486
|
+
* @returns Result indicating success or failure
|
|
3487
|
+
*/
|
|
3488
|
+
revokeDelegation(cid) {
|
|
3489
|
+
const stored = this.store.byCid.get(cid);
|
|
3490
|
+
if (!stored) {
|
|
3491
|
+
return (0, import_sdk_services3.err)(
|
|
3492
|
+
(0, import_sdk_services3.serviceError)(
|
|
3493
|
+
CapabilityKeyRegistryErrorCodes.KEY_NOT_FOUND,
|
|
3494
|
+
`Delegation not found: ${cid}`,
|
|
3495
|
+
SERVICE_NAME2
|
|
3496
|
+
)
|
|
3497
|
+
);
|
|
3498
|
+
}
|
|
3499
|
+
stored.delegation.isRevoked = true;
|
|
3500
|
+
const keyDelegations = this.store.byKey.get(stored.keyId);
|
|
3501
|
+
if (keyDelegations) {
|
|
3502
|
+
const delegation = keyDelegations.find((d) => d.cid === cid);
|
|
3503
|
+
if (delegation) {
|
|
3504
|
+
delegation.isRevoked = true;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
for (const entries of this.store.byCapability.values()) {
|
|
3508
|
+
for (const entry of entries) {
|
|
3509
|
+
if (entry.delegation.cid === cid) {
|
|
3510
|
+
entry.delegation.isRevoked = true;
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
return (0, import_sdk_services3.ok)(void 0);
|
|
3515
|
+
}
|
|
3516
|
+
// ===========================================================================
|
|
3517
|
+
// Search
|
|
3518
|
+
// ===========================================================================
|
|
3519
|
+
/**
|
|
3520
|
+
* Find capabilities that match a resource path pattern.
|
|
3521
|
+
*
|
|
3522
|
+
* @param resourcePattern - Resource pattern (supports wildcards)
|
|
3523
|
+
* @param action - Optional action filter
|
|
3524
|
+
* @returns Matching capability entries
|
|
3525
|
+
*/
|
|
3526
|
+
findCapabilities(resourcePattern, action) {
|
|
3527
|
+
const results = [];
|
|
3528
|
+
for (const entries of this.store.byCapability.values()) {
|
|
3529
|
+
for (const entry of entries) {
|
|
3530
|
+
if (action && entry.action !== action) {
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
if (this.matchesResourcePattern(entry.resource, resourcePattern)) {
|
|
3534
|
+
results.push(entry);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
return results;
|
|
3539
|
+
}
|
|
3540
|
+
// ===========================================================================
|
|
3541
|
+
// Private Methods
|
|
3542
|
+
// ===========================================================================
|
|
3543
|
+
/**
|
|
3544
|
+
* Add a delegation to the store.
|
|
3545
|
+
*
|
|
3546
|
+
* @param key - The key associated with this delegation
|
|
3547
|
+
* @param delegation - The delegation to add
|
|
3548
|
+
*/
|
|
3549
|
+
addDelegation(key, delegation) {
|
|
3550
|
+
const keyDelegations = this.store.byKey.get(key.id) || [];
|
|
3551
|
+
if (!keyDelegations.some((d) => d.cid === delegation.cid)) {
|
|
3552
|
+
keyDelegations.push(delegation);
|
|
3553
|
+
this.store.byKey.set(key.id, keyDelegations);
|
|
3554
|
+
}
|
|
3555
|
+
if (!this.store.byCid.has(delegation.cid)) {
|
|
3556
|
+
this.store.byCid.set(delegation.cid, {
|
|
3557
|
+
delegation,
|
|
3558
|
+
parentCid: delegation.parentCid,
|
|
3559
|
+
keyId: key.id,
|
|
3560
|
+
storedAt: /* @__PURE__ */ new Date()
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
3563
|
+
for (const action of delegation.actions) {
|
|
3564
|
+
const capKey = this.makeCapabilityKey(delegation.path, action);
|
|
3565
|
+
const entries = this.store.byCapability.get(capKey) || [];
|
|
3566
|
+
const existingEntry = entries.find((e) => e.delegation.cid === delegation.cid);
|
|
3567
|
+
if (existingEntry) {
|
|
3568
|
+
if (!existingEntry.keys.some((k) => k.id === key.id)) {
|
|
3569
|
+
existingEntry.keys.push(key);
|
|
3570
|
+
existingEntry.keys.sort((a, b) => a.priority - b.priority);
|
|
3571
|
+
}
|
|
3572
|
+
} else {
|
|
3573
|
+
const entry = {
|
|
3574
|
+
resource: delegation.path,
|
|
3575
|
+
action,
|
|
3576
|
+
keys: [key],
|
|
3577
|
+
delegation,
|
|
3578
|
+
expiresAt: delegation.expiry
|
|
3579
|
+
};
|
|
3580
|
+
entries.push(entry);
|
|
3581
|
+
this.store.byCapability.set(capKey, entries);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Create a capability key for indexing.
|
|
3587
|
+
*
|
|
3588
|
+
* @param resource - Resource path
|
|
3589
|
+
* @param action - Action
|
|
3590
|
+
* @returns Combined key string
|
|
3591
|
+
*/
|
|
3592
|
+
makeCapabilityKey(resource, action) {
|
|
3593
|
+
return `${resource}|${action}`;
|
|
3594
|
+
}
|
|
3595
|
+
/**
|
|
3596
|
+
* Find capability entries that match a resource and action.
|
|
3597
|
+
*
|
|
3598
|
+
* @param resource - Resource to match
|
|
3599
|
+
* @param action - Action to match
|
|
3600
|
+
* @returns Matching entries
|
|
3601
|
+
*/
|
|
3602
|
+
findMatchingEntries(resource, action) {
|
|
3603
|
+
const results = [];
|
|
3604
|
+
const exactKey = this.makeCapabilityKey(resource, action);
|
|
3605
|
+
const exactEntries = this.store.byCapability.get(exactKey);
|
|
3606
|
+
if (exactEntries) {
|
|
3607
|
+
results.push(...exactEntries);
|
|
3608
|
+
}
|
|
3609
|
+
for (const [capKey, entries] of this.store.byCapability) {
|
|
3610
|
+
if (capKey === exactKey) continue;
|
|
3611
|
+
for (const entry of entries) {
|
|
3612
|
+
if (!this.actionMatches(entry.action, action)) {
|
|
3613
|
+
continue;
|
|
3614
|
+
}
|
|
3615
|
+
if (this.resourceMatchesPattern(resource, entry.resource)) {
|
|
3616
|
+
if (!results.some((r) => r.delegation.cid === entry.delegation.cid)) {
|
|
3617
|
+
results.push(entry);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
return results;
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Check if an action pattern matches a specific action.
|
|
3626
|
+
*
|
|
3627
|
+
* @param pattern - Action pattern (may include wildcard like "tinycloud.kv/*")
|
|
3628
|
+
* @param action - Specific action to check
|
|
3629
|
+
* @returns true if pattern matches action
|
|
3630
|
+
*/
|
|
3631
|
+
actionMatches(pattern, action) {
|
|
3632
|
+
if (pattern === action) {
|
|
3633
|
+
return true;
|
|
3634
|
+
}
|
|
3635
|
+
if (pattern.endsWith("/*")) {
|
|
3636
|
+
const prefix = pattern.slice(0, -2);
|
|
3637
|
+
return action.startsWith(prefix + "/") || action === prefix;
|
|
3638
|
+
}
|
|
3639
|
+
return false;
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Check if a resource matches a pattern.
|
|
3643
|
+
*
|
|
3644
|
+
* Patterns support:
|
|
3645
|
+
* - Exact match: "/kv/data" matches "/kv/data"
|
|
3646
|
+
* - Wildcard suffix: "/kv/*" matches "/kv/anything"
|
|
3647
|
+
* - Double wildcard: "/kv/**" matches "/kv/any/nested/path"
|
|
3648
|
+
*
|
|
3649
|
+
* @param resource - The specific resource being accessed
|
|
3650
|
+
* @param pattern - The pattern from the delegation
|
|
3651
|
+
* @returns true if resource matches pattern
|
|
3652
|
+
*/
|
|
3653
|
+
resourceMatchesPattern(resource, pattern) {
|
|
3654
|
+
if (pattern === resource) {
|
|
3655
|
+
return true;
|
|
3656
|
+
}
|
|
3657
|
+
if (pattern.endsWith("/**")) {
|
|
3658
|
+
const prefix = pattern.slice(0, -3);
|
|
3659
|
+
return resource.startsWith(prefix);
|
|
3660
|
+
}
|
|
3661
|
+
if (pattern.endsWith("/*")) {
|
|
3662
|
+
const prefix = pattern.slice(0, -2);
|
|
3663
|
+
if (!resource.startsWith(prefix)) {
|
|
3664
|
+
return false;
|
|
3665
|
+
}
|
|
3666
|
+
const remainder = resource.slice(prefix.length);
|
|
3667
|
+
return !remainder.includes("/") || remainder === "/";
|
|
3668
|
+
}
|
|
3669
|
+
if (pattern.endsWith("/") && resource.startsWith(pattern)) {
|
|
3670
|
+
return true;
|
|
3671
|
+
}
|
|
3672
|
+
return false;
|
|
3673
|
+
}
|
|
3674
|
+
/**
|
|
3675
|
+
* Check if a specific resource matches a resource pattern for searching.
|
|
3676
|
+
*
|
|
3677
|
+
* @param entryResource - The resource from a capability entry
|
|
3678
|
+
* @param searchPattern - The pattern to search for
|
|
3679
|
+
* @returns true if entry resource matches search pattern
|
|
3680
|
+
*/
|
|
3681
|
+
matchesResourcePattern(entryResource, searchPattern) {
|
|
3682
|
+
return this.resourceMatchesPattern(entryResource, searchPattern) || this.resourceMatchesPattern(searchPattern, entryResource);
|
|
3683
|
+
}
|
|
3684
|
+
};
|
|
3685
|
+
function createCapabilityKeyRegistry() {
|
|
3686
|
+
return new CapabilityKeyRegistry();
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
// src/authorization/strategies.ts
|
|
3690
|
+
var defaultSignStrategy = { type: "auto-sign" };
|
|
3691
|
+
|
|
3692
|
+
// src/authorization/spaceCreation.ts
|
|
3693
|
+
var AutoApproveSpaceCreationHandler = class {
|
|
3694
|
+
/**
|
|
3695
|
+
* Always returns true to auto-approve space creation.
|
|
3696
|
+
*/
|
|
3697
|
+
async confirmSpaceCreation() {
|
|
3698
|
+
return true;
|
|
3699
|
+
}
|
|
3700
|
+
};
|
|
3701
|
+
var defaultSpaceCreationHandler = new AutoApproveSpaceCreationHandler();
|
|
3702
|
+
|
|
3703
|
+
// src/version.ts
|
|
3704
|
+
var ProtocolMismatchError = class extends Error {
|
|
3705
|
+
constructor(sdkProtocol, nodeProtocol, nodeVersion, host) {
|
|
3706
|
+
super(
|
|
3707
|
+
`SDK protocol version ${sdkProtocol} is incompatible with node protocol version ${nodeProtocol} (node v${nodeVersion}) at ${host}. ` + (sdkProtocol < nodeProtocol ? "Please update your SDK." : "Please update the TinyCloud node.")
|
|
3708
|
+
);
|
|
3709
|
+
this.sdkProtocol = sdkProtocol;
|
|
3710
|
+
this.nodeProtocol = nodeProtocol;
|
|
3711
|
+
this.nodeVersion = nodeVersion;
|
|
3712
|
+
this.host = host;
|
|
3713
|
+
this.name = "ProtocolMismatchError";
|
|
3714
|
+
}
|
|
3715
|
+
};
|
|
3716
|
+
var VersionCheckError = class extends Error {
|
|
3717
|
+
constructor(host, cause) {
|
|
3718
|
+
super(
|
|
3719
|
+
`Failed to fetch node info at ${host}. Ensure the node is running and the /info endpoint is accessible.`
|
|
3720
|
+
);
|
|
3721
|
+
this.host = host;
|
|
3722
|
+
this.cause = cause;
|
|
3723
|
+
this.name = "VersionCheckError";
|
|
3724
|
+
}
|
|
3725
|
+
};
|
|
3726
|
+
var UnsupportedFeatureError = class extends Error {
|
|
3727
|
+
constructor(feature, host, availableFeatures) {
|
|
3728
|
+
super(
|
|
3729
|
+
`Feature "${feature}" is not supported by the node at ${host}. Available features: ${availableFeatures.join(", ") || "none"}.`
|
|
3730
|
+
);
|
|
3731
|
+
this.feature = feature;
|
|
3732
|
+
this.host = host;
|
|
3733
|
+
this.availableFeatures = availableFeatures;
|
|
3734
|
+
this.name = "UnsupportedFeatureError";
|
|
3735
|
+
}
|
|
3736
|
+
};
|
|
3737
|
+
async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(globalThis)) {
|
|
3738
|
+
let response;
|
|
3739
|
+
try {
|
|
3740
|
+
response = await fetchFn(`${host}/info`, {
|
|
3741
|
+
signal: AbortSignal.timeout(5e3)
|
|
3742
|
+
});
|
|
3743
|
+
} catch (err5) {
|
|
3744
|
+
throw new VersionCheckError(host, err5);
|
|
3745
|
+
}
|
|
3746
|
+
if (!response.ok) {
|
|
3747
|
+
throw new VersionCheckError(host);
|
|
3748
|
+
}
|
|
3749
|
+
const data = await response.json();
|
|
3750
|
+
if (sdkProtocol !== data.protocol) {
|
|
3751
|
+
throw new ProtocolMismatchError(
|
|
3752
|
+
sdkProtocol,
|
|
3753
|
+
data.protocol,
|
|
3754
|
+
data.version,
|
|
3755
|
+
host
|
|
3756
|
+
);
|
|
3757
|
+
}
|
|
3758
|
+
return {
|
|
3759
|
+
features: data.features ?? [],
|
|
3760
|
+
quotaUrl: data.quota_url
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3764
|
+
0 && (module.exports = {
|
|
3765
|
+
AutoApproveSpaceCreationHandler,
|
|
3766
|
+
CapabilityKeyRegistry,
|
|
3767
|
+
CapabilityKeyRegistryErrorCodes,
|
|
3768
|
+
ClientSessionSchema,
|
|
3769
|
+
DataVaultService,
|
|
3770
|
+
DatabaseHandle,
|
|
3771
|
+
DelegationErrorCodes,
|
|
3772
|
+
DelegationManager,
|
|
3773
|
+
DuckDbAction,
|
|
3774
|
+
DuckDbDatabaseHandle,
|
|
3775
|
+
DuckDbService,
|
|
3776
|
+
EnsDataSchema,
|
|
3777
|
+
ErrorCodes,
|
|
3778
|
+
KVService,
|
|
3779
|
+
PrefixedKVService,
|
|
3780
|
+
ProtocolMismatchError,
|
|
3781
|
+
SQLAction,
|
|
3782
|
+
SQLService,
|
|
3783
|
+
ServiceContext,
|
|
3784
|
+
SharingService,
|
|
3785
|
+
SilentNotificationHandler,
|
|
3786
|
+
SiweConfigSchema,
|
|
3787
|
+
SiweMessage,
|
|
3788
|
+
Space,
|
|
3789
|
+
SpaceErrorCodes,
|
|
3790
|
+
SpaceService,
|
|
3791
|
+
TinyCloud,
|
|
3792
|
+
UnsupportedFeatureError,
|
|
3793
|
+
VaultHeaders,
|
|
3794
|
+
VaultPublicSpaceKVActions,
|
|
3795
|
+
VersionCheckError,
|
|
3796
|
+
activateSessionWithHost,
|
|
3797
|
+
buildSpaceUri,
|
|
3798
|
+
checkNodeInfo,
|
|
3799
|
+
createCapabilityKeyRegistry,
|
|
3800
|
+
createSharingService,
|
|
3801
|
+
createSpaceService,
|
|
3802
|
+
createVaultCrypto,
|
|
3803
|
+
defaultRetryPolicy,
|
|
3804
|
+
defaultSignStrategy,
|
|
3805
|
+
defaultSpaceCreationHandler,
|
|
3806
|
+
err,
|
|
3807
|
+
fetchPeerId,
|
|
3808
|
+
makePublicSpaceId,
|
|
3809
|
+
ok,
|
|
3810
|
+
parseSpaceUri,
|
|
3811
|
+
serviceError,
|
|
3812
|
+
submitHostDelegation,
|
|
3813
|
+
validateClientSession,
|
|
3814
|
+
validatePersistedSessionData
|
|
3815
|
+
});
|
|
3816
|
+
//# sourceMappingURL=index.cjs.map
|