@tinycloud/sdk-core 2.0.0 → 2.0.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist/index.cjs +3816 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +3867 -0
  4. package/dist/index.d.ts +3861 -21
  5. package/dist/index.js +3767 -61
  6. package/dist/index.js.map +1 -1
  7. package/package.json +6 -5
  8. package/dist/TinyCloud.d.ts +0 -271
  9. package/dist/TinyCloud.d.ts.map +0 -1
  10. package/dist/TinyCloud.js +0 -458
  11. package/dist/TinyCloud.js.map +0 -1
  12. package/dist/TinyCloud.schema.d.ts +0 -173
  13. package/dist/TinyCloud.schema.d.ts.map +0 -1
  14. package/dist/TinyCloud.schema.js +0 -136
  15. package/dist/TinyCloud.schema.js.map +0 -1
  16. package/dist/TinyCloud.schema.test.d.ts +0 -5
  17. package/dist/TinyCloud.schema.test.d.ts.map +0 -1
  18. package/dist/TinyCloud.schema.test.js +0 -286
  19. package/dist/TinyCloud.schema.test.js.map +0 -1
  20. package/dist/authorization/CapabilityKeyRegistry.d.ts +0 -317
  21. package/dist/authorization/CapabilityKeyRegistry.d.ts.map +0 -1
  22. package/dist/authorization/CapabilityKeyRegistry.js +0 -509
  23. package/dist/authorization/CapabilityKeyRegistry.js.map +0 -1
  24. package/dist/authorization/authorization.schema.d.ts +0 -233
  25. package/dist/authorization/authorization.schema.d.ts.map +0 -1
  26. package/dist/authorization/authorization.schema.js +0 -220
  27. package/dist/authorization/authorization.schema.js.map +0 -1
  28. package/dist/authorization/authorization.schema.test.d.ts +0 -5
  29. package/dist/authorization/authorization.schema.test.d.ts.map +0 -1
  30. package/dist/authorization/authorization.schema.test.js +0 -618
  31. package/dist/authorization/authorization.schema.test.js.map +0 -1
  32. package/dist/authorization/index.d.ts +0 -38
  33. package/dist/authorization/index.d.ts.map +0 -1
  34. package/dist/authorization/index.js +0 -52
  35. package/dist/authorization/index.js.map +0 -1
  36. package/dist/authorization/spaceCreation.d.ts +0 -96
  37. package/dist/authorization/spaceCreation.d.ts.map +0 -1
  38. package/dist/authorization/spaceCreation.js +0 -35
  39. package/dist/authorization/spaceCreation.js.map +0 -1
  40. package/dist/authorization/spaceCreation.schema.d.ts +0 -67
  41. package/dist/authorization/spaceCreation.schema.d.ts.map +0 -1
  42. package/dist/authorization/spaceCreation.schema.js +0 -95
  43. package/dist/authorization/spaceCreation.schema.js.map +0 -1
  44. package/dist/authorization/spaceCreation.schema.test.d.ts +0 -5
  45. package/dist/authorization/spaceCreation.schema.test.d.ts.map +0 -1
  46. package/dist/authorization/spaceCreation.schema.test.js +0 -168
  47. package/dist/authorization/spaceCreation.schema.test.js.map +0 -1
  48. package/dist/authorization/strategies.d.ts +0 -134
  49. package/dist/authorization/strategies.d.ts.map +0 -1
  50. package/dist/authorization/strategies.js +0 -15
  51. package/dist/authorization/strategies.js.map +0 -1
  52. package/dist/authorization/strategies.schema.d.ts +0 -185
  53. package/dist/authorization/strategies.schema.d.ts.map +0 -1
  54. package/dist/authorization/strategies.schema.js +0 -147
  55. package/dist/authorization/strategies.schema.js.map +0 -1
  56. package/dist/authorization/strategies.schema.test.d.ts +0 -5
  57. package/dist/authorization/strategies.schema.test.d.ts.map +0 -1
  58. package/dist/authorization/strategies.schema.test.js +0 -253
  59. package/dist/authorization/strategies.schema.test.js.map +0 -1
  60. package/dist/client-types.d.ts +0 -128
  61. package/dist/client-types.d.ts.map +0 -1
  62. package/dist/client-types.js +0 -40
  63. package/dist/client-types.js.map +0 -1
  64. package/dist/delegations/DelegationManager.d.ts +0 -164
  65. package/dist/delegations/DelegationManager.d.ts.map +0 -1
  66. package/dist/delegations/DelegationManager.js +0 -428
  67. package/dist/delegations/DelegationManager.js.map +0 -1
  68. package/dist/delegations/SharingService.d.ts +0 -341
  69. package/dist/delegations/SharingService.d.ts.map +0 -1
  70. package/dist/delegations/SharingService.js +0 -722
  71. package/dist/delegations/SharingService.js.map +0 -1
  72. package/dist/delegations/SharingService.schema.d.ts +0 -409
  73. package/dist/delegations/SharingService.schema.d.ts.map +0 -1
  74. package/dist/delegations/SharingService.schema.js +0 -222
  75. package/dist/delegations/SharingService.schema.js.map +0 -1
  76. package/dist/delegations/index.d.ts +0 -38
  77. package/dist/delegations/index.d.ts.map +0 -1
  78. package/dist/delegations/index.js +0 -42
  79. package/dist/delegations/index.js.map +0 -1
  80. package/dist/delegations/types.d.ts +0 -13
  81. package/dist/delegations/types.d.ts.map +0 -1
  82. package/dist/delegations/types.js +0 -42
  83. package/dist/delegations/types.js.map +0 -1
  84. package/dist/delegations/types.schema.d.ts +0 -1773
  85. package/dist/delegations/types.schema.d.ts.map +0 -1
  86. package/dist/delegations/types.schema.js +0 -535
  87. package/dist/delegations/types.schema.js.map +0 -1
  88. package/dist/delegations/types.schema.test.d.ts +0 -5
  89. package/dist/delegations/types.schema.test.d.ts.map +0 -1
  90. package/dist/delegations/types.schema.test.js +0 -627
  91. package/dist/delegations/types.schema.test.js.map +0 -1
  92. package/dist/ens.d.ts +0 -17
  93. package/dist/ens.d.ts.map +0 -1
  94. package/dist/ens.js +0 -10
  95. package/dist/ens.js.map +0 -1
  96. package/dist/index.d.ts.map +0 -1
  97. package/dist/json-schema.d.ts +0 -327
  98. package/dist/json-schema.d.ts.map +0 -1
  99. package/dist/json-schema.js +0 -703
  100. package/dist/json-schema.js.map +0 -1
  101. package/dist/json-schema.test.d.ts +0 -7
  102. package/dist/json-schema.test.d.ts.map +0 -1
  103. package/dist/json-schema.test.js +0 -365
  104. package/dist/json-schema.test.js.map +0 -1
  105. package/dist/notifications.d.ts +0 -33
  106. package/dist/notifications.d.ts.map +0 -1
  107. package/dist/notifications.js +0 -15
  108. package/dist/notifications.js.map +0 -1
  109. package/dist/signer.d.ts +0 -28
  110. package/dist/signer.d.ts.map +0 -1
  111. package/dist/signer.js +0 -2
  112. package/dist/signer.js.map +0 -1
  113. package/dist/space.d.ts +0 -57
  114. package/dist/space.d.ts.map +0 -1
  115. package/dist/space.js +0 -87
  116. package/dist/space.js.map +0 -1
  117. package/dist/space.schema.d.ts +0 -65
  118. package/dist/space.schema.d.ts.map +0 -1
  119. package/dist/space.schema.js +0 -65
  120. package/dist/space.schema.js.map +0 -1
  121. package/dist/space.schema.test.d.ts +0 -5
  122. package/dist/space.schema.test.d.ts.map +0 -1
  123. package/dist/space.schema.test.js +0 -148
  124. package/dist/space.schema.test.js.map +0 -1
  125. package/dist/space.test.d.ts +0 -5
  126. package/dist/space.test.d.ts.map +0 -1
  127. package/dist/space.test.js +0 -87
  128. package/dist/space.test.js.map +0 -1
  129. package/dist/spaces/Space.d.ts +0 -175
  130. package/dist/spaces/Space.d.ts.map +0 -1
  131. package/dist/spaces/Space.js +0 -84
  132. package/dist/spaces/Space.js.map +0 -1
  133. package/dist/spaces/SpaceService.d.ts +0 -291
  134. package/dist/spaces/SpaceService.d.ts.map +0 -1
  135. package/dist/spaces/SpaceService.js +0 -740
  136. package/dist/spaces/SpaceService.js.map +0 -1
  137. package/dist/spaces/index.d.ts +0 -11
  138. package/dist/spaces/index.d.ts.map +0 -1
  139. package/dist/spaces/index.js +0 -22
  140. package/dist/spaces/index.js.map +0 -1
  141. package/dist/spaces/spaces.schema.d.ts +0 -421
  142. package/dist/spaces/spaces.schema.d.ts.map +0 -1
  143. package/dist/spaces/spaces.schema.js +0 -342
  144. package/dist/spaces/spaces.schema.js.map +0 -1
  145. package/dist/spaces/spaces.schema.test.d.ts +0 -5
  146. package/dist/spaces/spaces.schema.test.d.ts.map +0 -1
  147. package/dist/spaces/spaces.schema.test.js +0 -471
  148. package/dist/spaces/spaces.schema.test.js.map +0 -1
  149. package/dist/storage.d.ts +0 -47
  150. package/dist/storage.d.ts.map +0 -1
  151. package/dist/storage.js +0 -14
  152. package/dist/storage.js.map +0 -1
  153. package/dist/storage.schema.d.ts +0 -291
  154. package/dist/storage.schema.d.ts.map +0 -1
  155. package/dist/storage.schema.js +0 -189
  156. package/dist/storage.schema.js.map +0 -1
  157. package/dist/storage.schema.test.d.ts +0 -5
  158. package/dist/storage.schema.test.d.ts.map +0 -1
  159. package/dist/storage.schema.test.js +0 -346
  160. package/dist/storage.schema.test.js.map +0 -1
  161. package/dist/userAuthorization.d.ts +0 -117
  162. package/dist/userAuthorization.d.ts.map +0 -1
  163. package/dist/userAuthorization.js +0 -3
  164. package/dist/userAuthorization.js.map +0 -1
  165. package/dist/userAuthorization.schema.d.ts +0 -260
  166. package/dist/userAuthorization.schema.d.ts.map +0 -1
  167. package/dist/userAuthorization.schema.js +0 -169
  168. package/dist/userAuthorization.schema.js.map +0 -1
  169. package/dist/userAuthorization.schema.test.d.ts +0 -5
  170. package/dist/userAuthorization.schema.test.d.ts.map +0 -1
  171. package/dist/userAuthorization.schema.test.js +0 -356
  172. package/dist/userAuthorization.schema.test.js.map +0 -1
  173. package/dist/version.d.ts +0 -32
  174. package/dist/version.d.ts.map +0 -1
  175. package/dist/version.js +0 -59
  176. package/dist/version.js.map +0 -1
  177. package/dist/wasm-validation.d.ts +0 -291
  178. package/dist/wasm-validation.d.ts.map +0 -1
  179. package/dist/wasm-validation.js +0 -221
  180. package/dist/wasm-validation.js.map +0 -1
  181. package/dist/wasm-validation.test.d.ts +0 -5
  182. package/dist/wasm-validation.test.d.ts.map +0 -1
  183. package/dist/wasm-validation.test.js +0 -233
  184. package/dist/wasm-validation.test.js.map +0 -1
  185. package/dist/wasm.d.ts +0 -66
  186. package/dist/wasm.d.ts.map +0 -1
  187. package/dist/wasm.js +0 -10
  188. package/dist/wasm.js.map +0 -1
@@ -1,722 +0,0 @@
1
- /**
2
- * SharingService - v2 sharing link service with embedded private keys.
3
- *
4
- * This service implements the v2 sharing specification, which embeds private keys
5
- * directly in sharing links. This allows recipients to exercise delegations
6
- * without requiring prior session setup.
7
- *
8
- * Key differences from v1 SharingLinks:
9
- * - Private keys are embedded in the link (not just tokens)
10
- * - Recipients can optionally sub-delegate to their own session key
11
- * - Pre-configured KV service returned for immediate use
12
- *
13
- * @packageDocumentation
14
- */
15
- import { DelegationErrorCodes } from "./types";
16
- import { validateEncodedShareData } from "./SharingService.schema.js";
17
- // =============================================================================
18
- // Constants
19
- // =============================================================================
20
- /**
21
- * Default actions for read-only sharing links.
22
- */
23
- const DEFAULT_READ_ACTIONS = ["tinycloud.kv/get", "tinycloud.kv/metadata"];
24
- /**
25
- * Default expiry for sharing links (24 hours).
26
- */
27
- const DEFAULT_EXPIRY_MS = 24 * 60 * 60 * 1000;
28
- /**
29
- * Prefix for the base64 schema.
30
- */
31
- const BASE64_PREFIX = "tc1:";
32
- // =============================================================================
33
- // Helper Functions
34
- // =============================================================================
35
- /**
36
- * Creates a DelegationError with the given parameters.
37
- */
38
- function createError(code, message, cause, meta) {
39
- return {
40
- code,
41
- message,
42
- service: "delegation",
43
- cause,
44
- meta,
45
- };
46
- }
47
- /**
48
- * Base64 encode for URLs (URL-safe base64).
49
- */
50
- function base64UrlEncode(data) {
51
- // Use btoa for browser, Buffer for Node.js
52
- let base64;
53
- if (typeof btoa !== "undefined") {
54
- base64 = btoa(unescape(encodeURIComponent(data)));
55
- }
56
- else if (typeof Buffer !== "undefined") {
57
- base64 = Buffer.from(data, "utf-8").toString("base64");
58
- }
59
- else {
60
- throw new Error("No base64 encoding available");
61
- }
62
- // Make URL-safe
63
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
64
- }
65
- /**
66
- * Base64 decode for URLs (URL-safe base64).
67
- */
68
- function base64UrlDecode(encoded) {
69
- // Restore standard base64
70
- let base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
71
- // Add padding if needed
72
- while (base64.length % 4) {
73
- base64 += "=";
74
- }
75
- // Decode
76
- if (typeof atob !== "undefined") {
77
- return decodeURIComponent(escape(atob(base64)));
78
- }
79
- else if (typeof Buffer !== "undefined") {
80
- return Buffer.from(base64, "base64").toString("utf-8");
81
- }
82
- else {
83
- throw new Error("No base64 decoding available");
84
- }
85
- }
86
- // =============================================================================
87
- // Implementation
88
- // =============================================================================
89
- /**
90
- * SharingService - v2 sharing link service with embedded private keys.
91
- *
92
- * @example
93
- * ```typescript
94
- * import { SharingService } from "@tinycloud/sdk-core/delegations";
95
- *
96
- * const sharing = new SharingService({
97
- * hosts: ["https://node.tinycloud.xyz"],
98
- * session,
99
- * invoke,
100
- * keyProvider,
101
- * registry,
102
- * delegationManager,
103
- * createKVService,
104
- * baseUrl: "https://share.myapp.com"
105
- * });
106
- *
107
- * // Generate a sharing link
108
- * const result = await sharing.generate({
109
- * path: "/kv/documents/report.pdf",
110
- * actions: ["tinycloud.kv/get"],
111
- * expiry: new Date("2024-12-31")
112
- * });
113
- *
114
- * if (result.ok) {
115
- * console.log("Share this URL:", result.data.url);
116
- * }
117
- *
118
- * // Receive a sharing link
119
- * const receiveResult = await sharing.receive(shareUrl);
120
- * if (receiveResult.ok) {
121
- * // Use the pre-configured KV service
122
- * const data = await receiveResult.data.kv.get("report.pdf");
123
- * }
124
- * ```
125
- */
126
- export class SharingService {
127
- /**
128
- * Creates a new SharingService instance.
129
- */
130
- constructor(config) {
131
- this.hosts = config.hosts;
132
- this.session = config.session;
133
- this.invoke = config.invoke;
134
- this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
135
- this.keyProvider = config.keyProvider;
136
- this.registry = config.registry;
137
- this.delegationManager = config.delegationManager;
138
- this.createKVService = config.createKVService;
139
- this.baseUrl = (config.baseUrl ?? "").replace(/\/$/, ""); // Remove trailing slash
140
- this.createDelegationFn = config.createDelegation;
141
- this.createDelegationWasmFn = config.createDelegationWasm;
142
- this.pathPrefix = config.pathPrefix ?? "";
143
- this.sessionExpiry = config.sessionExpiry;
144
- this.onRootDelegationNeeded = config.onRootDelegationNeeded;
145
- }
146
- /**
147
- * Gets the primary host URL.
148
- */
149
- get host() {
150
- return this.hosts[0];
151
- }
152
- /**
153
- * Updates the session (e.g., after re-authentication).
154
- */
155
- updateSession(session) {
156
- this.session = session;
157
- }
158
- /**
159
- * Updates the service configuration.
160
- * Used to add full capabilities (session, delegationManager, createDelegation, createDelegationWasm) after signIn.
161
- */
162
- updateConfig(config) {
163
- if (config.session !== undefined) {
164
- this.session = config.session;
165
- }
166
- if (config.delegationManager !== undefined) {
167
- this.delegationManager = config.delegationManager;
168
- }
169
- if (config.createDelegation !== undefined) {
170
- this.createDelegationFn = config.createDelegation;
171
- }
172
- if (config.createDelegationWasm !== undefined) {
173
- this.createDelegationWasmFn = config.createDelegationWasm;
174
- }
175
- if (config.sessionExpiry !== undefined) {
176
- this.sessionExpiry = config.sessionExpiry;
177
- }
178
- if (config.onRootDelegationNeeded !== undefined) {
179
- this.onRootDelegationNeeded = config.onRootDelegationNeeded;
180
- }
181
- }
182
- /**
183
- * Generate a sharing link with an embedded private key.
184
- *
185
- * Flow:
186
- * 1. Spawn new session key (unique per share)
187
- * 2. Create delegation from current session to spawned key
188
- * 3. Package: { key (with private!), delegation, path, host }
189
- * 4. Encode based on schema (base64 for now)
190
- * 5. Return link string
191
- */
192
- async generate(params) {
193
- // Require session for generating (not for receiving)
194
- if (!this.session) {
195
- return {
196
- ok: false,
197
- error: createError(DelegationErrorCodes.NOT_INITIALIZED, "Session required for generating sharing links. Call signIn() first."),
198
- };
199
- }
200
- // Require delegation capability
201
- if (!this.createDelegationWasmFn && !this.createDelegationFn && !this.delegationManager) {
202
- return {
203
- ok: false,
204
- error: createError(DelegationErrorCodes.NOT_INITIALIZED, "DelegationManager, createDelegation, or createDelegationWasm function required for generating sharing links."),
205
- };
206
- }
207
- // Validate path
208
- if (!params.path) {
209
- return {
210
- ok: false,
211
- error: createError(DelegationErrorCodes.INVALID_INPUT, "path is required"),
212
- };
213
- }
214
- const actions = params.actions ?? DEFAULT_READ_ACTIONS;
215
- const requestedExpiry = params.expiry ?? new Date(Date.now() + DEFAULT_EXPIRY_MS);
216
- let expiry = requestedExpiry;
217
- const schema = params.schema ?? "base64";
218
- // Build full path with prefix (matches how KVService stores data)
219
- // If pathPrefix is "demo-app" and path is "hello", fullPath is "demo-app/hello"
220
- const fullPath = this.pathPrefix
221
- ? `${this.pathPrefix}/${params.path}`.replace(/\/+/g, "/") // Normalize slashes
222
- : params.path;
223
- // Only base64 schema is implemented in v1
224
- if (schema !== "base64") {
225
- return {
226
- ok: false,
227
- error: createError(DelegationErrorCodes.INVALID_INPUT, `Schema '${schema}' not implemented. Only 'base64' is supported.`),
228
- };
229
- }
230
- // Step 1: Spawn a new session key unique to this share
231
- // We create this FIRST so we can pass its DID to onRootDelegationNeeded if needed
232
- let keyId;
233
- let keyDid;
234
- let keyJwk;
235
- try {
236
- const shareKeyName = `share:${Date.now()}:${Math.random().toString(36).substring(2, 10)}`;
237
- keyId = await this.keyProvider.createSessionKey(shareKeyName);
238
- keyDid = await this.keyProvider.getDID(keyId);
239
- keyJwk = this.keyProvider.getJWK(keyId);
240
- // Ensure the private key is included
241
- if (!keyJwk.d) {
242
- return {
243
- ok: false,
244
- error: createError(DelegationErrorCodes.CREATION_FAILED, "KeyProvider did not return private key (d parameter) in JWK"),
245
- };
246
- }
247
- }
248
- catch (err) {
249
- return {
250
- ok: false,
251
- error: createError(DelegationErrorCodes.CREATION_FAILED, `Failed to generate session key for share: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : undefined),
252
- };
253
- }
254
- // Step 2: Check if any existing key can satisfy this delegation
255
- // Only prompt for root delegation if NO existing key in the registry can handle it
256
- let delegation;
257
- // Strip fragment from DID URL to get plain DID for UCAN audience
258
- // getDID() returns "did:key:z6Mk...#z6Mk..." but audience needs "did:key:z6Mk..."
259
- const plainDID = keyDid.split('#')[0];
260
- // Helper to handle delegation result (returns early on error)
261
- const handleDelegationResult = (result) => {
262
- if (result && typeof result === 'object' && 'ok' in result) {
263
- return result;
264
- }
265
- return result;
266
- };
267
- // Check if any key in the registry can satisfy this delegation request
268
- // A key can satisfy the request if it has a delegation that:
269
- // 1. Covers the required path and actions
270
- // 2. Has sufficient expiry (delegation.expiry >= requestedExpiry)
271
- // 3. Allows sub-delegation
272
- const canSatisfyFromRegistry = this.findSuitableKeyForDelegation(fullPath, actions, requestedExpiry);
273
- if (canSatisfyFromRegistry) {
274
- // An existing key can satisfy this request - use session delegation (no prompt)
275
- const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
276
- const parsed = handleDelegationResult(delegationResult);
277
- if ('ok' in parsed && parsed.ok === false) {
278
- return parsed;
279
- }
280
- delegation = parsed;
281
- }
282
- else if (this.onRootDelegationNeeded) {
283
- // No existing key can satisfy the request - try root delegation
284
- try {
285
- const rootDelegation = await this.onRootDelegationNeeded({
286
- shareKeyDID: plainDID,
287
- spaceId: this.session.spaceId,
288
- path: fullPath,
289
- actions,
290
- requestedExpiry,
291
- });
292
- if (rootDelegation) {
293
- delegation = rootDelegation;
294
- expiry = requestedExpiry;
295
- }
296
- else {
297
- // Root delegation declined, clamp to session expiry
298
- const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
299
- expiry = fallbackResult.expiry;
300
- const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
301
- const parsed = handleDelegationResult(delegationResult);
302
- if ('ok' in parsed && parsed.ok === false) {
303
- return parsed;
304
- }
305
- delegation = parsed;
306
- }
307
- }
308
- catch (err) {
309
- // Root delegation failed, clamp to session expiry
310
- const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
311
- expiry = fallbackResult.expiry;
312
- const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
313
- const parsed = handleDelegationResult(delegationResult);
314
- if ('ok' in parsed && parsed.ok === false) {
315
- return parsed;
316
- }
317
- delegation = parsed;
318
- }
319
- }
320
- else {
321
- // No root delegation callback, clamp to what session can provide
322
- const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
323
- expiry = fallbackResult.expiry;
324
- const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
325
- const parsed = handleDelegationResult(delegationResult);
326
- if ('ok' in parsed && parsed.ok === false) {
327
- return parsed;
328
- }
329
- delegation = parsed;
330
- }
331
- // Step 3: Package the share data
332
- const shareData = {
333
- key: keyJwk,
334
- keyDid,
335
- delegation,
336
- path: fullPath,
337
- host: this.host,
338
- spaceId: this.session.spaceId,
339
- version: 1,
340
- };
341
- // Step 4: Encode the link
342
- const encodedData = this.encodeLink(shareData, schema);
343
- // Step 5: Build the full URL
344
- const baseUrl = params.baseUrl ?? this.baseUrl;
345
- const url = baseUrl ? `${baseUrl}/share/${encodedData}` : encodedData;
346
- const shareLink = {
347
- token: encodedData,
348
- url,
349
- delegation,
350
- schema,
351
- expiresAt: expiry,
352
- description: params.description,
353
- };
354
- return { ok: true, data: shareLink };
355
- }
356
- /**
357
- * Check if any key in the registry can satisfy the delegation request.
358
- * A key can satisfy if it has a delegation that:
359
- * 1. Covers the required path (exact match or parent path)
360
- * 2. Has all required actions
361
- * 3. Has sufficient expiry (delegation.expiry >= requestedExpiry)
362
- * 4. Allows sub-delegation
363
- * @internal
364
- */
365
- findSuitableKeyForDelegation(path, actions, requestedExpiry) {
366
- // Check session expiry first (most common case)
367
- if (this.sessionExpiry && requestedExpiry <= this.sessionExpiry) {
368
- return true;
369
- }
370
- // Check registry for keys with sufficient capabilities
371
- const allKeys = this.registry.getAllKeys();
372
- for (const key of allKeys) {
373
- const delegations = this.registry.getDelegationsForKey(key.id);
374
- for (const delegation of delegations) {
375
- // Check if delegation is valid and not expired
376
- if (!this.registry.isDelegationValid(delegation)) {
377
- continue;
378
- }
379
- // Check if delegation has sufficient expiry
380
- if (delegation.expiry < requestedExpiry) {
381
- continue;
382
- }
383
- // Check if delegation allows sub-delegation
384
- if (delegation.allowSubDelegation === false) {
385
- continue;
386
- }
387
- // Check if delegation covers the path (exact match or parent path)
388
- const delegationPath = delegation.path || '';
389
- if (!this.pathMatches(delegationPath, path)) {
390
- continue;
391
- }
392
- // Check if delegation has all required actions
393
- const delegationActions = delegation.actions || [];
394
- const hasAllActions = actions.every(action => delegationActions.includes(action) || delegationActions.includes('*'));
395
- if (!hasAllActions) {
396
- continue;
397
- }
398
- // Found a suitable key
399
- return true;
400
- }
401
- }
402
- return false;
403
- }
404
- /**
405
- * Check if a delegation path matches/covers the requested path.
406
- * A delegation path covers the request if:
407
- * - It's an exact match
408
- * - It's a parent path (e.g., delegation for "" covers "foo/bar")
409
- * - It uses wildcards that match
410
- * @internal
411
- */
412
- pathMatches(delegationPath, requestedPath) {
413
- // Empty delegation path covers everything
414
- if (delegationPath === '' || delegationPath === '*') {
415
- return true;
416
- }
417
- // Exact match
418
- if (delegationPath === requestedPath) {
419
- return true;
420
- }
421
- // Check if delegation path is a parent of requested path
422
- const normalizedDelegation = delegationPath.replace(/\/$/, '');
423
- const normalizedRequest = requestedPath.replace(/\/$/, '');
424
- if (normalizedRequest.startsWith(normalizedDelegation + '/')) {
425
- return true;
426
- }
427
- return false;
428
- }
429
- /**
430
- * Handle fallback to session extension when root delegation is not available.
431
- * @internal
432
- */
433
- async handleSessionExtensionFallback(requestedExpiry) {
434
- // Clamp to current session expiry
435
- return { expiry: this.sessionExpiry ?? requestedExpiry };
436
- }
437
- /**
438
- * Create a delegation from the current session to a share key.
439
- * This is the fallback path when root delegation is not available.
440
- * @internal
441
- */
442
- async createSessionDelegation(delegateDID, path, actions, expiry) {
443
- if (!this.session) {
444
- return {
445
- ok: false,
446
- error: createError(DelegationErrorCodes.NOT_INITIALIZED, "Session required for creating delegation"),
447
- };
448
- }
449
- if (this.createDelegationWasmFn) {
450
- // Client-side delegation creation via WASM
451
- try {
452
- const wasmResult = this.createDelegationWasmFn({
453
- session: this.session,
454
- delegateDID,
455
- spaceId: this.session.spaceId,
456
- path,
457
- actions,
458
- expirationSecs: Math.floor(expiry.getTime() / 1000),
459
- });
460
- // Register the delegation with the server
461
- const registerRes = await this.fetchFn(`${this.host}/delegate`, {
462
- method: "POST",
463
- headers: {
464
- Authorization: wasmResult.delegation,
465
- },
466
- });
467
- if (!registerRes.ok) {
468
- const errorText = await registerRes.text();
469
- return {
470
- ok: false,
471
- error: createError(DelegationErrorCodes.CREATION_FAILED, `Failed to register delegation with server: ${registerRes.status} ${errorText}`),
472
- };
473
- }
474
- return {
475
- cid: wasmResult.cid,
476
- delegateDID: wasmResult.delegateDID,
477
- spaceId: this.session.spaceId,
478
- path: wasmResult.path,
479
- actions: wasmResult.actions,
480
- expiry: wasmResult.expiry,
481
- isRevoked: false,
482
- authHeader: wasmResult.delegation,
483
- allowSubDelegation: true,
484
- createdAt: new Date(),
485
- };
486
- }
487
- catch (err) {
488
- return {
489
- ok: false,
490
- error: createError(DelegationErrorCodes.CREATION_FAILED, `Failed to create delegation via WASM: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : undefined),
491
- };
492
- }
493
- }
494
- else {
495
- // Server-side delegation creation (fallback)
496
- const delegationParams = {
497
- delegateDID,
498
- path,
499
- actions,
500
- expiry,
501
- disableSubDelegation: false,
502
- };
503
- const delegationResult = this.createDelegationFn
504
- ? await this.createDelegationFn(delegationParams)
505
- : await this.delegationManager.create(delegationParams);
506
- if (!delegationResult.ok) {
507
- return {
508
- ok: false,
509
- error: createError(DelegationErrorCodes.CREATION_FAILED, `Failed to create delegation for share: ${delegationResult.error.message}`, delegationResult.error.cause, delegationResult.error.meta),
510
- };
511
- }
512
- return delegationResult.data;
513
- }
514
- }
515
- /**
516
- * Receive and activate a sharing link.
517
- *
518
- * Flow:
519
- * 1. Decode link -> extract { key, delegation, path, host }
520
- * 2. Ingest key into CapabilityKeyRegistry
521
- * 3. If autoSubdelegate (default true) + useSessionKey:
522
- * - Create sub-delegation from ingested key -> current session
523
- * - Register sub-delegation capabilities
524
- * 4. Return ShareAccess with pre-configured KV service
525
- */
526
- async receive(link, options = {}) {
527
- const { autoSubdelegate = true, useSessionKey = true, ingestOptions, } = options;
528
- // Step 1: Decode and validate the link
529
- const decodeResult = this.decodeLinkWithValidation(link);
530
- if (!decodeResult.ok) {
531
- return decodeResult;
532
- }
533
- const shareData = decodeResult.data;
534
- // Schema validation ensures key.d and delegation exist, but we need
535
- // to check business rules (expiry, revocation) separately
536
- // Check delegation expiry
537
- const delegationExpiry = new Date(shareData.delegation.expiry);
538
- if (delegationExpiry < new Date()) {
539
- return {
540
- ok: false,
541
- error: createError(DelegationErrorCodes.AUTH_EXPIRED, "Sharing link has expired"),
542
- };
543
- }
544
- // Check delegation revocation
545
- if (shareData.delegation.isRevoked) {
546
- return {
547
- ok: false,
548
- error: createError(DelegationErrorCodes.REVOKED, "Sharing link has been revoked"),
549
- };
550
- }
551
- // Step 2: Create KeyInfo and ingest into registry
552
- const keyInfo = {
553
- id: `ingested:${shareData.keyDid}`,
554
- did: shareData.keyDid,
555
- type: "ingested",
556
- jwk: shareData.key,
557
- priority: 2, // Ingested keys have lowest priority
558
- };
559
- this.registry.ingestKey(keyInfo, shareData.delegation, ingestOptions);
560
- // The delegation and key to use for operations
561
- let activeDelegation = shareData.delegation;
562
- let activeKey = keyInfo;
563
- // Step 3: Auto-subdelegate if requested
564
- if (autoSubdelegate && useSessionKey && this.session) {
565
- try {
566
- // Get current session key DID
567
- // Note: We need to create a sub-delegation from the ingested key to the session key
568
- // This requires the session key DID, which should be available from the session
569
- // For now, we'll register the ingested key's capabilities directly
570
- // The auto-subdelegation would require additional infrastructure to sign with the ingested key
571
- // This is a simplification - full implementation would sign a new delegation with the ingested key
572
- // TODO: Implement full auto-subdelegation when signing infrastructure is available
573
- // For now, the ingested key can be used directly via the registry
574
- }
575
- catch (err) {
576
- // Log but don't fail - can still use the ingested key directly
577
- console.warn("Auto-subdelegation failed, using ingested key directly:", err);
578
- }
579
- }
580
- // Step 4: Create pre-configured KV service for the shared path
581
- // Construct session from share data - no need for existing session
582
- // Use the authHeader if available, otherwise fall back to constructing from CID
583
- const authHeader = shareData.delegation.authHeader ?? `Bearer ${shareData.delegation.cid}`;
584
- const shareSession = {
585
- delegationHeader: { Authorization: authHeader },
586
- delegationCid: shareData.delegation.cid,
587
- spaceId: shareData.spaceId,
588
- verificationMethod: shareData.keyDid,
589
- jwk: shareData.key,
590
- };
591
- const kvService = this.createKVService({
592
- hosts: [shareData.host],
593
- session: shareSession,
594
- invoke: this.invoke,
595
- fetch: this.fetchFn,
596
- pathPrefix: shareData.path,
597
- });
598
- const shareAccess = {
599
- delegation: activeDelegation,
600
- key: activeKey,
601
- kv: kvService,
602
- spaceId: shareData.spaceId,
603
- path: shareData.path,
604
- };
605
- return { ok: true, data: shareAccess };
606
- }
607
- /**
608
- * Encode sharing data into a link string.
609
- *
610
- * @param data - The share data to encode
611
- * @param schema - The encoding schema (default: "base64")
612
- * @returns Encoded link string
613
- */
614
- encodeLink(data, schema = "base64") {
615
- if (schema !== "base64") {
616
- throw new Error(`Schema '${schema}' not implemented. Only 'base64' is supported.`);
617
- }
618
- const jsonString = JSON.stringify(data);
619
- const encoded = base64UrlEncode(jsonString);
620
- return `${BASE64_PREFIX}${encoded}`;
621
- }
622
- /**
623
- * Decode a link string into sharing data.
624
- *
625
- * @param link - The encoded link string (may include URL prefix)
626
- * @returns Decoded share data
627
- * @throws Error if link format is invalid or data fails validation
628
- */
629
- decodeLink(link) {
630
- const result = this.decodeLinkWithValidation(link);
631
- if (!result.ok) {
632
- throw new Error(result.error.message);
633
- }
634
- return result.data;
635
- }
636
- /**
637
- * Decode and validate a link string into sharing data.
638
- *
639
- * Internal method that returns a Result instead of throwing.
640
- * Used by receive() for proper error handling.
641
- *
642
- * @param link - The encoded link string (may include URL prefix)
643
- * @returns Result with decoded share data or validation error
644
- */
645
- decodeLinkWithValidation(link) {
646
- // Extract the encoded data from the link
647
- let encoded = link;
648
- // Handle full URL format: https://share.example.com/share/tc1:...
649
- if (link.includes("/share/")) {
650
- const parts = link.split("/share/");
651
- encoded = parts[parts.length - 1];
652
- }
653
- // Handle query parameter format: ?share=tc1:...
654
- if (link.includes("?share=")) {
655
- try {
656
- const url = new URL(link);
657
- encoded = url.searchParams.get("share") ?? encoded;
658
- }
659
- catch {
660
- return {
661
- ok: false,
662
- error: createError(DelegationErrorCodes.INVALID_TOKEN, "Invalid URL format in sharing link"),
663
- };
664
- }
665
- }
666
- // Remove the schema prefix
667
- if (!encoded.startsWith(BASE64_PREFIX)) {
668
- return {
669
- ok: false,
670
- error: createError(DelegationErrorCodes.INVALID_TOKEN, `Invalid sharing link format. Expected prefix '${BASE64_PREFIX}'`),
671
- };
672
- }
673
- const base64Data = encoded.slice(BASE64_PREFIX.length);
674
- let jsonString;
675
- try {
676
- jsonString = base64UrlDecode(base64Data);
677
- }
678
- catch (err) {
679
- return {
680
- ok: false,
681
- error: createError(DelegationErrorCodes.INVALID_TOKEN, `Failed to decode base64 data: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : undefined),
682
- };
683
- }
684
- let parsed;
685
- try {
686
- parsed = JSON.parse(jsonString);
687
- }
688
- catch (err) {
689
- return {
690
- ok: false,
691
- error: createError(DelegationErrorCodes.INVALID_TOKEN, `Failed to parse share data JSON: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : undefined),
692
- };
693
- }
694
- // Convert delegation expiry to Date before validation if it's a string
695
- // This is needed because JSON.parse doesn't restore Date objects
696
- if (parsed &&
697
- typeof parsed === "object" &&
698
- "delegation" in parsed &&
699
- parsed.delegation &&
700
- typeof parsed.delegation === "object" &&
701
- "expiry" in parsed.delegation &&
702
- typeof parsed.delegation.expiry === "string") {
703
- parsed.delegation.expiry = new Date(parsed.delegation.expiry);
704
- }
705
- // Validate against schema
706
- const validationResult = validateEncodedShareData(parsed);
707
- if (!validationResult.ok) {
708
- return {
709
- ok: false,
710
- error: createError(DelegationErrorCodes.INVALID_TOKEN, validationResult.error.message, undefined, validationResult.error.meta),
711
- };
712
- }
713
- return { ok: true, data: validationResult.data };
714
- }
715
- }
716
- /**
717
- * Create a new SharingService instance.
718
- */
719
- export function createSharingService(config) {
720
- return new SharingService(config);
721
- }
722
- //# sourceMappingURL=SharingService.js.map