@snaha/swarm-id 0.0.1

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 (223) hide show
  1. package/README.md +431 -0
  2. package/dist/chunk/bmt.d.ts +17 -0
  3. package/dist/chunk/bmt.d.ts.map +1 -0
  4. package/dist/chunk/cac.d.ts +18 -0
  5. package/dist/chunk/cac.d.ts.map +1 -0
  6. package/dist/chunk/constants.d.ts +10 -0
  7. package/dist/chunk/constants.d.ts.map +1 -0
  8. package/dist/chunk/encrypted-cac.d.ts +48 -0
  9. package/dist/chunk/encrypted-cac.d.ts.map +1 -0
  10. package/dist/chunk/encryption.d.ts +86 -0
  11. package/dist/chunk/encryption.d.ts.map +1 -0
  12. package/dist/chunk/index.d.ts +6 -0
  13. package/dist/chunk/index.d.ts.map +1 -0
  14. package/dist/index.d.ts +46 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/proxy/act/act.d.ts +78 -0
  17. package/dist/proxy/act/act.d.ts.map +1 -0
  18. package/dist/proxy/act/crypto.d.ts +44 -0
  19. package/dist/proxy/act/crypto.d.ts.map +1 -0
  20. package/dist/proxy/act/grantee-list.d.ts +82 -0
  21. package/dist/proxy/act/grantee-list.d.ts.map +1 -0
  22. package/dist/proxy/act/history.d.ts +183 -0
  23. package/dist/proxy/act/history.d.ts.map +1 -0
  24. package/dist/proxy/act/index.d.ts +104 -0
  25. package/dist/proxy/act/index.d.ts.map +1 -0
  26. package/dist/proxy/chunking-encrypted.d.ts +14 -0
  27. package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
  28. package/dist/proxy/chunking.d.ts +15 -0
  29. package/dist/proxy/chunking.d.ts.map +1 -0
  30. package/dist/proxy/download-data.d.ts +16 -0
  31. package/dist/proxy/download-data.d.ts.map +1 -0
  32. package/dist/proxy/feed-manifest.d.ts +62 -0
  33. package/dist/proxy/feed-manifest.d.ts.map +1 -0
  34. package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
  35. package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
  36. package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
  37. package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
  38. package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
  39. package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
  40. package/dist/proxy/feeds/epochs/index.d.ts +35 -0
  41. package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
  42. package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
  43. package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
  44. package/dist/proxy/feeds/epochs/types.d.ts +109 -0
  45. package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
  46. package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
  47. package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
  48. package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
  49. package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
  50. package/dist/proxy/feeds/index.d.ts +5 -0
  51. package/dist/proxy/feeds/index.d.ts.map +1 -0
  52. package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
  53. package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
  54. package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
  55. package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
  56. package/dist/proxy/feeds/sequence/index.d.ts +23 -0
  57. package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
  58. package/dist/proxy/feeds/sequence/types.d.ts +80 -0
  59. package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
  60. package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
  61. package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
  62. package/dist/proxy/index.d.ts +6 -0
  63. package/dist/proxy/index.d.ts.map +1 -0
  64. package/dist/proxy/manifest-builder.d.ts +183 -0
  65. package/dist/proxy/manifest-builder.d.ts.map +1 -0
  66. package/dist/proxy/mantaray-encrypted.d.ts +27 -0
  67. package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
  68. package/dist/proxy/mantaray.d.ts +26 -0
  69. package/dist/proxy/mantaray.d.ts.map +1 -0
  70. package/dist/proxy/types.d.ts +29 -0
  71. package/dist/proxy/types.d.ts.map +1 -0
  72. package/dist/proxy/upload-data.d.ts +17 -0
  73. package/dist/proxy/upload-data.d.ts.map +1 -0
  74. package/dist/proxy/upload-encrypted-data.d.ts +103 -0
  75. package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
  76. package/dist/schemas.d.ts +240 -0
  77. package/dist/schemas.d.ts.map +1 -0
  78. package/dist/storage/debounced-uploader.d.ts +62 -0
  79. package/dist/storage/debounced-uploader.d.ts.map +1 -0
  80. package/dist/storage/utilization-store.d.ts +108 -0
  81. package/dist/storage/utilization-store.d.ts.map +1 -0
  82. package/dist/swarm-id-auth.d.ts +74 -0
  83. package/dist/swarm-id-auth.d.ts.map +1 -0
  84. package/dist/swarm-id-auth.js +2 -0
  85. package/dist/swarm-id-auth.js.map +1 -0
  86. package/dist/swarm-id-client.d.ts +878 -0
  87. package/dist/swarm-id-client.d.ts.map +1 -0
  88. package/dist/swarm-id-client.js +2 -0
  89. package/dist/swarm-id-client.js.map +1 -0
  90. package/dist/swarm-id-proxy.d.ts +236 -0
  91. package/dist/swarm-id-proxy.d.ts.map +1 -0
  92. package/dist/swarm-id-proxy.js +2 -0
  93. package/dist/swarm-id-proxy.js.map +1 -0
  94. package/dist/swarm-id.esm.js +2 -0
  95. package/dist/swarm-id.esm.js.map +1 -0
  96. package/dist/swarm-id.umd.js +2 -0
  97. package/dist/swarm-id.umd.js.map +1 -0
  98. package/dist/sync/index.d.ts +9 -0
  99. package/dist/sync/index.d.ts.map +1 -0
  100. package/dist/sync/key-derivation.d.ts +25 -0
  101. package/dist/sync/key-derivation.d.ts.map +1 -0
  102. package/dist/sync/restore-account.d.ts +28 -0
  103. package/dist/sync/restore-account.d.ts.map +1 -0
  104. package/dist/sync/serialization.d.ts +16 -0
  105. package/dist/sync/serialization.d.ts.map +1 -0
  106. package/dist/sync/store-interfaces.d.ts +53 -0
  107. package/dist/sync/store-interfaces.d.ts.map +1 -0
  108. package/dist/sync/sync-account.d.ts +44 -0
  109. package/dist/sync/sync-account.d.ts.map +1 -0
  110. package/dist/sync/types.d.ts +13 -0
  111. package/dist/sync/types.d.ts.map +1 -0
  112. package/dist/test-fixtures.d.ts +17 -0
  113. package/dist/test-fixtures.d.ts.map +1 -0
  114. package/dist/types-BD_VkNn0.js +2 -0
  115. package/dist/types-BD_VkNn0.js.map +1 -0
  116. package/dist/types-lJCaT-50.js +2 -0
  117. package/dist/types-lJCaT-50.js.map +1 -0
  118. package/dist/types.d.ts +2157 -0
  119. package/dist/types.d.ts.map +1 -0
  120. package/dist/utils/account-payload.d.ts +94 -0
  121. package/dist/utils/account-payload.d.ts.map +1 -0
  122. package/dist/utils/account-state-snapshot.d.ts +38 -0
  123. package/dist/utils/account-state-snapshot.d.ts.map +1 -0
  124. package/dist/utils/backup-encryption.d.ts +127 -0
  125. package/dist/utils/backup-encryption.d.ts.map +1 -0
  126. package/dist/utils/batch-utilization.d.ts +432 -0
  127. package/dist/utils/batch-utilization.d.ts.map +1 -0
  128. package/dist/utils/constants.d.ts +11 -0
  129. package/dist/utils/constants.d.ts.map +1 -0
  130. package/dist/utils/hex.d.ts +17 -0
  131. package/dist/utils/hex.d.ts.map +1 -0
  132. package/dist/utils/key-derivation.d.ts +92 -0
  133. package/dist/utils/key-derivation.d.ts.map +1 -0
  134. package/dist/utils/storage-managers.d.ts +65 -0
  135. package/dist/utils/storage-managers.d.ts.map +1 -0
  136. package/dist/utils/swarm-id-export.d.ts +24 -0
  137. package/dist/utils/swarm-id-export.d.ts.map +1 -0
  138. package/dist/utils/ttl.d.ts +49 -0
  139. package/dist/utils/ttl.d.ts.map +1 -0
  140. package/dist/utils/url.d.ts +41 -0
  141. package/dist/utils/url.d.ts.map +1 -0
  142. package/dist/utils/versioned-storage.d.ts +131 -0
  143. package/dist/utils/versioned-storage.d.ts.map +1 -0
  144. package/package.json +78 -0
  145. package/src/chunk/bmt.test.ts +217 -0
  146. package/src/chunk/bmt.ts +57 -0
  147. package/src/chunk/cac.test.ts +214 -0
  148. package/src/chunk/cac.ts +65 -0
  149. package/src/chunk/constants.ts +18 -0
  150. package/src/chunk/encrypted-cac.test.ts +385 -0
  151. package/src/chunk/encrypted-cac.ts +131 -0
  152. package/src/chunk/encryption.test.ts +352 -0
  153. package/src/chunk/encryption.ts +300 -0
  154. package/src/chunk/index.ts +47 -0
  155. package/src/index.ts +430 -0
  156. package/src/proxy/act/act.test.ts +278 -0
  157. package/src/proxy/act/act.ts +158 -0
  158. package/src/proxy/act/bee-compat.test.ts +948 -0
  159. package/src/proxy/act/crypto.test.ts +436 -0
  160. package/src/proxy/act/crypto.ts +376 -0
  161. package/src/proxy/act/grantee-list.test.ts +393 -0
  162. package/src/proxy/act/grantee-list.ts +239 -0
  163. package/src/proxy/act/history.test.ts +360 -0
  164. package/src/proxy/act/history.ts +413 -0
  165. package/src/proxy/act/index.test.ts +748 -0
  166. package/src/proxy/act/index.ts +853 -0
  167. package/src/proxy/chunking-encrypted.ts +95 -0
  168. package/src/proxy/chunking.ts +65 -0
  169. package/src/proxy/download-data.ts +448 -0
  170. package/src/proxy/feed-manifest.ts +174 -0
  171. package/src/proxy/feeds/epochs/async-finder.ts +372 -0
  172. package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
  173. package/src/proxy/feeds/epochs/epoch.ts +181 -0
  174. package/src/proxy/feeds/epochs/finder.ts +282 -0
  175. package/src/proxy/feeds/epochs/index.ts +73 -0
  176. package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
  177. package/src/proxy/feeds/epochs/test-utils.ts +274 -0
  178. package/src/proxy/feeds/epochs/types.ts +128 -0
  179. package/src/proxy/feeds/epochs/updater.ts +192 -0
  180. package/src/proxy/feeds/epochs/utils.ts +62 -0
  181. package/src/proxy/feeds/index.ts +5 -0
  182. package/src/proxy/feeds/sequence/async-finder.ts +31 -0
  183. package/src/proxy/feeds/sequence/finder.ts +73 -0
  184. package/src/proxy/feeds/sequence/index.ts +54 -0
  185. package/src/proxy/feeds/sequence/integration.test.ts +966 -0
  186. package/src/proxy/feeds/sequence/types.ts +103 -0
  187. package/src/proxy/feeds/sequence/updater.ts +71 -0
  188. package/src/proxy/index.ts +5 -0
  189. package/src/proxy/manifest-builder.test.ts +427 -0
  190. package/src/proxy/manifest-builder.ts +679 -0
  191. package/src/proxy/mantaray-encrypted.ts +78 -0
  192. package/src/proxy/mantaray.ts +104 -0
  193. package/src/proxy/types.ts +32 -0
  194. package/src/proxy/upload-data.ts +189 -0
  195. package/src/proxy/upload-encrypted-data.ts +658 -0
  196. package/src/schemas.ts +299 -0
  197. package/src/storage/debounced-uploader.ts +192 -0
  198. package/src/storage/utilization-store.ts +397 -0
  199. package/src/swarm-id-client.test.ts +99 -0
  200. package/src/swarm-id-client.ts +3095 -0
  201. package/src/swarm-id-proxy.ts +3891 -0
  202. package/src/sync/index.ts +28 -0
  203. package/src/sync/restore-account.ts +90 -0
  204. package/src/sync/serialization.ts +39 -0
  205. package/src/sync/store-interfaces.ts +62 -0
  206. package/src/sync/sync-account.test.ts +302 -0
  207. package/src/sync/sync-account.ts +396 -0
  208. package/src/sync/types.ts +11 -0
  209. package/src/test-fixtures.ts +109 -0
  210. package/src/types.ts +1651 -0
  211. package/src/utils/account-state-snapshot.test.ts +595 -0
  212. package/src/utils/account-state-snapshot.ts +94 -0
  213. package/src/utils/backup-encryption.test.ts +442 -0
  214. package/src/utils/backup-encryption.ts +352 -0
  215. package/src/utils/batch-utilization.ts +1309 -0
  216. package/src/utils/constants.ts +20 -0
  217. package/src/utils/hex.ts +27 -0
  218. package/src/utils/key-derivation.ts +197 -0
  219. package/src/utils/storage-managers.ts +365 -0
  220. package/src/utils/ttl.ts +129 -0
  221. package/src/utils/url.test.ts +136 -0
  222. package/src/utils/url.ts +71 -0
  223. package/src/utils/versioned-storage.ts +323 -0
package/README.md ADDED
@@ -0,0 +1,431 @@
1
+ # Swarm ID Client Library
2
+
3
+ A TypeScript library for integrating Swarm ID authentication and Bee API operations into dApps.
4
+
5
+ ## Overview
6
+
7
+ The Swarm ID library provides a secure, iframe-based authentication system for Swarm applications. It consists of three main components:
8
+
9
+ 1. **SwarmIdClient** - For parent windows/dApps to interact with the authentication system
10
+ 2. **SwarmIdProxy** - Runs in the iframe, handles authentication and proxies Bee API calls
11
+ 3. **SwarmIdAuth** - Runs in the popup window for user authentication
12
+
13
+ ## Installation
14
+
15
+ From the monorepo root:
16
+
17
+ ```bash
18
+ pnpm install
19
+ ```
20
+
21
+ Or directly in the lib folder:
22
+
23
+ ```bash
24
+ cd lib
25
+ pnpm install
26
+ ```
27
+
28
+ ## Building
29
+
30
+ From the monorepo root:
31
+
32
+ ```bash
33
+ # Build the library
34
+ pnpm build
35
+
36
+ # Watch mode
37
+ pnpm build:watch
38
+
39
+ # Lint code
40
+ pnpm lint
41
+
42
+ # Auto-fix linting issues
43
+ pnpm lint:fix
44
+ ```
45
+
46
+ Or directly in the lib folder:
47
+
48
+ ```bash
49
+ cd lib
50
+
51
+ # Build once
52
+ pnpm build
53
+
54
+ # Watch mode
55
+ pnpm build:watch
56
+
57
+ # Lint code
58
+ pnpm lint
59
+
60
+ # Auto-fix linting issues
61
+ pnpm lint:fix
62
+ ```
63
+
64
+ ## Project Structure
65
+
66
+ ```
67
+ lib/
68
+ ├── index.ts # Main library exports
69
+ ├── types.ts # Zod schemas & TypeScript interfaces
70
+ ├── swarm-id-client.ts # Client for parent windows
71
+ ├── swarm-id-proxy.ts # Proxy logic for iframe
72
+ ├── swarm-id-auth.ts # Auth popup logic
73
+ ├── utils/
74
+ │ ├── key-derivation.ts # Cryptographic utilities
75
+ │ ├── backup-encryption.ts # AES-GCM-256 encryption for .swarmid files
76
+ │ └── account-state-snapshot.ts # Shared serialization for export & sync
77
+ ├── sync/
78
+ │ └── restore-account.ts # Swarm-based account restoration
79
+ └── tsconfig.json # TypeScript configuration
80
+
81
+ dist/ # Built output (generated)
82
+ ├── swarm-id.esm.js # ESM bundle
83
+ ├── swarm-id.umd.js # UMD bundle
84
+ ├── swarm-id-client.js # Individual module (ESM)
85
+ ├── swarm-id-proxy.js # Individual module (ESM)
86
+ ├── swarm-id-auth.js # Individual module (ESM)
87
+ └── *.d.ts # TypeScript declarations
88
+ ```
89
+
90
+ ## Usage
91
+
92
+ ### In a Parent dApp
93
+
94
+ ```typescript
95
+ import { SwarmIdClient } from "swarm-id"
96
+
97
+ // Initialize the client
98
+ const client = new SwarmIdClient({
99
+ iframeOrigin: "https://swarm-id.local:8081",
100
+ beeApiUrl: "http://localhost:1633",
101
+ timeout: 30000,
102
+ onAuthChange: (authenticated) => {
103
+ console.log("Auth status changed:", authenticated)
104
+ },
105
+ })
106
+
107
+ // Initialize and wait for ready
108
+ await client.initialize()
109
+
110
+ // Get the auth iframe (positioned fixed in bottom-right corner)
111
+ const iframe = client.getAuthIframe()
112
+
113
+ // Check auth status
114
+ const status = await client.checkAuthStatus()
115
+ console.log("Authenticated:", status.authenticated)
116
+
117
+ // Upload data
118
+ const result = await client.uploadData(
119
+ "your-postage-batch-id",
120
+ new Uint8Array([1, 2, 3, 4, 5]),
121
+ { pin: true },
122
+ )
123
+ console.log("Uploaded:", result.reference)
124
+
125
+ // Download data
126
+ const data = await client.downloadData(result.reference)
127
+ console.log("Downloaded:", data)
128
+
129
+ // Upload file
130
+ const file = new File(["Hello World"], "hello.txt")
131
+ const fileResult = await client.uploadFile("your-postage-batch-id", file)
132
+ console.log("File uploaded:", fileResult.reference)
133
+
134
+ // Download file
135
+ const downloadedFile = await client.downloadFile(fileResult.reference)
136
+ console.log("File name:", downloadedFile.name)
137
+ console.log("File data:", downloadedFile.data)
138
+
139
+ // Cleanup
140
+ client.destroy()
141
+ ```
142
+
143
+ ### In the Iframe (SvelteKit `/proxy` route)
144
+
145
+ ```typescript
146
+ import { initProxy } from "swarm-id/proxy"
147
+
148
+ const proxy = initProxy({
149
+ beeApiUrl: "http://localhost:1633",
150
+ })
151
+ ```
152
+
153
+ ### In the Auth Popup (SvelteKit `/connect` route)
154
+
155
+ ```typescript
156
+ import { initAuth } from "swarm-id/auth"
157
+
158
+ try {
159
+ const auth = await initAuth()
160
+
161
+ // Display app origin
162
+ console.log("App requesting access:", auth.getAppOrigin())
163
+
164
+ // Check if user has master key
165
+ if (!auth.hasMasterKey()) {
166
+ // Setup new identity
167
+ const masterKey = await auth.setupNewIdentity()
168
+ console.log("New identity created")
169
+ }
170
+
171
+ // Authenticate
172
+ await auth.authenticate()
173
+ console.log("Authentication successful")
174
+
175
+ // Close popup
176
+ auth.close(1500)
177
+ } catch (error) {
178
+ console.error("Auth failed:", error)
179
+ }
180
+ ```
181
+
182
+ ## API Reference
183
+
184
+ ### SwarmIdClient
185
+
186
+ #### Constructor
187
+
188
+ ```typescript
189
+ new SwarmIdClient(options: ClientOptions)
190
+ ```
191
+
192
+ Options:
193
+
194
+ - `iframeOrigin` (string, required) - Origin of the Swarm ID iframe
195
+ - `beeApiUrl` (string, optional) - Bee node API URL (default: 'http://localhost:1633')
196
+ - `timeout` (number, optional) - Request timeout in ms (default: 30000)
197
+ - `onAuthChange` (function, optional) - Callback for auth status changes
198
+
199
+ #### Methods
200
+
201
+ **Authentication**
202
+
203
+ - `initialize()` - Initialize the client and embed iframe
204
+ - `getAuthIframe()` - Get the auth iframe element
205
+ - `checkAuthStatus()` - Check authentication status
206
+ - `connect()` - Open authentication popup programmatically (see [Browser Compatibility](#browser-compatibility-and-storage-access))
207
+ - `disconnect()` - Disconnect and clear authentication data
208
+ - `getConnectionInfo()` - Get connection info including upload capability
209
+
210
+ **Data Operations**
211
+
212
+ - `uploadData(batchId, data, options?)` - Upload raw data
213
+ - `downloadData(reference, options?)` - Download raw data
214
+
215
+ **File Operations**
216
+
217
+ - `uploadFile(batchId, file, name?, options?)` - Upload file
218
+ - `downloadFile(reference, path?, options?)` - Download file
219
+
220
+ **Chunk Operations**
221
+
222
+ - `uploadChunk(batchId, data, options?)` - Upload chunk
223
+ - `downloadChunk(reference, options?)` - Download chunk
224
+
225
+ **Postage Operations**
226
+
227
+ - `createPostageBatch(amount, depth, options?)` - Create postage batch
228
+ - `getPostageBatch(batchId)` - Get postage batch info
229
+
230
+ **Cleanup**
231
+
232
+ - `destroy()` - Destroy client and clean up resources
233
+
234
+ ### SwarmIdProxy
235
+
236
+ #### Constructor
237
+
238
+ ```typescript
239
+ new SwarmIdProxy(options: ProxyOptions)
240
+ ```
241
+
242
+ Options:
243
+
244
+ - `beeApiUrl` (string, required) - Bee node API URL
245
+
246
+ ### SwarmIdAuth
247
+
248
+ #### Constructor
249
+
250
+ ```typescript
251
+ new SwarmIdAuth(options?: AuthOptions)
252
+ ```
253
+
254
+ Options:
255
+
256
+ - `masterKeyStorageKey` (string, optional) - LocalStorage key for master key (default: 'swarm-master-key')
257
+
258
+ #### Methods
259
+
260
+ - `initialize()` - Initialize auth popup
261
+ - `getAppOrigin()` - Get the requesting app's origin
262
+ - `getMasterKey()` - Get the master key (truncated for display)
263
+ - `hasMasterKey()` - Check if master key exists
264
+ - `setupNewIdentity()` - Generate and store new master key
265
+ - `authenticate()` - Authenticate and send secret to iframe
266
+ - `close(delay?)` - Close popup window (default delay: 1500ms)
267
+
268
+ ## Message Protocol
269
+
270
+ The library uses postMessage for secure cross-origin communication with Zod schema validation.
271
+
272
+ ### Parent → Iframe Messages
273
+
274
+ - `parentIdentify` - Identify parent to iframe
275
+ - `checkAuth` - Check authentication status
276
+ - `requestAuth` - Request authentication (open popup)
277
+ - `uploadData` - Upload data request
278
+ - `downloadData` - Download data request
279
+ - (and other Bee API operations)
280
+
281
+ ### Iframe → Parent Messages
282
+
283
+ - `proxyReady` - Iframe is ready
284
+ - `authStatusResponse` - Auth status response
285
+ - `authSuccess` - Authentication succeeded
286
+ - `uploadDataResponse` - Upload response with reference
287
+ - `downloadDataResponse` - Download response with data
288
+ - `error` - Error message
289
+
290
+ ### Popup → Iframe Messages
291
+
292
+ - `setSecret` - Send derived secret to iframe
293
+
294
+ ## Import & Export
295
+
296
+ Accounts can be exported to encrypted `.swarmid` backup files and restored via import or from Swarm.
297
+
298
+ ### .swarmid File Format
299
+
300
+ Each file contains a plaintext JSON header (account metadata, type-specific fields) plus an AES-GCM-256 encrypted payload containing the account state snapshot.
301
+
302
+ ```typescript
303
+ import {
304
+ createEncryptedExport,
305
+ decryptEncryptedExport,
306
+ parseEncryptedExportHeader,
307
+ } from "@snaha/swarm-id"
308
+
309
+ // Export (no re-auth needed — uses stored swarmEncryptionKey)
310
+ const exported = await createEncryptedExport(
311
+ account,
312
+ identities,
313
+ connectedApps,
314
+ postageStamps,
315
+ swarmEncryptionKeyHex,
316
+ )
317
+
318
+ // Read header metadata without decrypting
319
+ const header = parseEncryptedExportHeader(fileData)
320
+
321
+ // Import (requires auth to re-derive key)
322
+ const result = await decryptEncryptedExport(fileData, swarmEncryptionKeyHex)
323
+ if (result.success) {
324
+ console.log(result.data) // AccountStateSnapshot
325
+ }
326
+ ```
327
+
328
+ ### Account State Snapshots
329
+
330
+ The `serializeAccountStateSnapshot()` and `deserializeAccountStateSnapshot()` functions provide shared serialization used by both file export and Swarm sync. Connected app secrets are included in snapshots so that backups preserve app connections without requiring re-authentication.
331
+
332
+ ### Swarm Sync & Restore
333
+
334
+ When a passkey auth succeeds on a new device with no local account, `restoreAccountFromSwarm()` derives the necessary keys, finds the epoch feed in Swarm, downloads and decrypts the latest account snapshot.
335
+
336
+ ```typescript
337
+ import { restoreAccountFromSwarm } from "@snaha/swarm-id"
338
+
339
+ const result = await restoreAccountFromSwarm(
340
+ bee,
341
+ masterKey,
342
+ ethereumAddress,
343
+ credentialId,
344
+ )
345
+ if (result) {
346
+ console.log(result.snapshot) // AccountStateSnapshot
347
+ }
348
+ ```
349
+
350
+ ## Security Features
351
+
352
+ - **Origin Validation** - All postMessage calls validate sender origin
353
+ - **Parent Origin Locking** - Parent can only identify itself once
354
+ - **HMAC-SHA256 Key Derivation** - App-specific secrets derived from master key
355
+ - **Partitioned Storage** - Secrets isolated per (iframe-origin, parent-origin) pair
356
+ - **Master Key Protection** - Master key never leaves first-party context
357
+ - **Type-safe Messages** - Zod schema validation on all messages
358
+ - **Backup Encryption** - AES-GCM-256 with random IV for .swarmid files
359
+ - **appSecret Exclusion** - App secrets are never included in exports or sync snapshots
360
+
361
+ ## Browser Compatibility and Storage Access
362
+
363
+ There are two ways to trigger authentication:
364
+
365
+ 1. **Iframe button** - User clicks the button rendered inside the iframe (`getAuthIframe()`)
366
+ 2. **Custom button** - App calls `client.connect()` from its own button
367
+
368
+ ### Storage Access API
369
+
370
+ The iframe needs access to shared localStorage to read accounts, identities, and postage stamps. Browsers may partition iframe storage, requiring the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API) to access unpartitioned storage. This API **requires a user gesture inside the iframe**.
371
+
372
+ ### Environment Compatibility
373
+
374
+ | Environment | Iframe button | Custom button | Notes |
375
+ | ------------------------------------- | ------------- | ------------------- | ---------------------------------------------------------------------------- |
376
+ | **Production (Chrome/Firefox)** | Yes | Yes | Secure context, storage not partitioned |
377
+ | **Localhost (Chrome, Firefox, etc.)** | Yes | After iframe button | Iframe button requests Storage Access first |
378
+ | **Safari (any)** | Yes | Yes | Download-only mode: auth works, uploads disabled (ITP storage partitioning) |
379
+ | **Safari private mode** | Yes | Yes | Download-only mode; sessions are ephemeral (lost when private window closes) |
380
+
381
+ ### How It Works
382
+
383
+ **Production (Chrome/Firefox)**: In a secure context (HTTPS), browsers don't partition iframe storage, so both authentication methods work immediately.
384
+
385
+ **Localhost (Chrome/Firefox)**: Browsers partition iframe storage, requiring the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API). The iframe button triggers a user gesture inside the iframe, allowing it to request Storage Access. Once granted, the custom button also works.
386
+
387
+ **Safari**: Safari's Intelligent Tracking Prevention (ITP) partitions all storage for third-party iframes, preventing access to signing keys and postage stamps. Safari operates in download-only mode: authentication and downloads work, but uploads are not available. See [#167](https://github.com/snaha/swarm-id/issues/167) for details.
388
+
389
+ **Safari private mode**: Download-only mode applies; sessions are also ephemeral — all storage is cleared when the private window closes.
390
+
391
+ ### Recommendation
392
+
393
+ - **Production (Chrome/Firefox)**: Either button works immediately
394
+ - **Development**: Use Chrome/Firefox with iframe button first, then custom button works
395
+ - **Safari**: Download-only mode (auth works, uploads disabled); private mode sessions are ephemeral
396
+
397
+ ## Development
398
+
399
+ ### Code Style
400
+
401
+ - Use `undefined` instead of `null`
402
+ - No semicolons
403
+ - TypeScript strict mode
404
+ - ESLint with @typescript-eslint
405
+
406
+ ### Testing
407
+
408
+ ```bash
409
+ # Run linter
410
+ pnpm lint
411
+
412
+ # Auto-fix issues
413
+ pnpm lint:fix
414
+ ```
415
+
416
+ ## TODO
417
+
418
+ - [ ] Integrate real Bee API calls (currently simulated)
419
+ - [ ] Add unit tests
420
+ - [ ] Add integration tests
421
+ - [ ] Update HTML files to use library
422
+ - [ ] Browser compatibility testing
423
+ - [ ] Add error recovery and retry logic
424
+ - [ ] Add request cancellation support
425
+ - [ ] Add progress callbacks for uploads/downloads
426
+ - [ ] Document security model
427
+ - [ ] Publish to npm
428
+
429
+ ## License
430
+
431
+ ISC
@@ -0,0 +1,17 @@
1
+ import { Reference } from "@ethersphere/bee-js";
2
+ /**
3
+ * Calculate a Binary Merkle Tree hash for a chunk
4
+ *
5
+ * The BMT chunk address is the hash of the 8 byte span and the root
6
+ * hash of a binary Merkle tree (BMT) built on the 32-byte segments
7
+ * of the underlying data.
8
+ *
9
+ * If the chunk content is less than 4k, the hash is calculated as
10
+ * if the chunk was padded with all zeros up to 4096 bytes.
11
+ *
12
+ * @param chunkContent Chunk data including span and payload as well
13
+ *
14
+ * @returns the keccak256 hash in a byte array
15
+ */
16
+ export declare function calculateChunkAddress(chunkContent: Uint8Array): Reference;
17
+ //# sourceMappingURL=bmt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bmt.d.ts","sourceRoot":"","sources":["../../src/chunk/bmt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAQ,MAAM,qBAAqB,CAAA;AAKrD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,UAAU,GAAG,SAAS,CAOzE"}
@@ -0,0 +1,18 @@
1
+ import { Bytes, Reference, Span } from "@ethersphere/bee-js";
2
+ /**
3
+ * Content addressed chunk interface
4
+ */
5
+ export interface ContentAddressedChunk {
6
+ readonly data: Uint8Array;
7
+ readonly span: Span;
8
+ readonly payload: Bytes;
9
+ readonly address: Reference;
10
+ }
11
+ /**
12
+ * Creates a content addressed chunk from payload data
13
+ *
14
+ * @param payloadBytes the data to be stored in the chunk (1-4096 bytes)
15
+ * @param spanOverride optional span value to use instead of payload length
16
+ */
17
+ export declare function makeContentAddressedChunk(payloadBytes: Uint8Array | string, spanOverride?: Span | bigint): ContentAddressedChunk;
18
+ //# sourceMappingURL=cac.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cac.d.ts","sourceRoot":"","sources":["../../src/chunk/cac.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAM5D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,UAAU,GAAG,MAAM,EACjC,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,GAC3B,qBAAqB,CAoCvB"}
@@ -0,0 +1,10 @@
1
+ export declare const MIN_PAYLOAD_SIZE = 1;
2
+ export declare const MAX_PAYLOAD_SIZE = 4096;
3
+ export declare const SPAN_SIZE = 8;
4
+ export declare const UNENCRYPTED_REF_SIZE = 32;
5
+ export declare const ENCRYPTED_REF_SIZE = 64;
6
+ export declare const IDENTIFIER_SIZE = 32;
7
+ export declare const SIGNATURE_SIZE = 65;
8
+ export declare const SOC_HEADER_SIZE: number;
9
+ export declare const DEFAULT_DOWNLOAD_CONCURRENCY = 64;
10
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/chunk/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AAGpC,eAAO,MAAM,SAAS,IAAI,CAAA;AAG1B,eAAO,MAAM,oBAAoB,KAAK,CAAA;AACtC,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAGpC,eAAO,MAAM,eAAe,KAAK,CAAA;AACjC,eAAO,MAAM,cAAc,KAAK,CAAA;AAChC,eAAO,MAAM,eAAe,QAAmC,CAAA;AAG/D,eAAO,MAAM,4BAA4B,KAAK,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { Bytes, Reference, Span } from "@ethersphere/bee-js";
2
+ import { type Key } from "./encryption";
3
+ /**
4
+ * Encrypted chunk interface
5
+ *
6
+ * The reference includes both the chunk address and the encryption key (64 bytes total)
7
+ */
8
+ export interface EncryptedChunk {
9
+ readonly data: Uint8Array;
10
+ readonly encryptionKey: Key;
11
+ readonly span: Span;
12
+ readonly payload: Bytes;
13
+ readonly address: Reference;
14
+ readonly reference: Reference;
15
+ }
16
+ /**
17
+ * Creates an encrypted content addressed chunk
18
+ *
19
+ * Process:
20
+ * 1. Create chunk with span + payload
21
+ * 2. Encrypt the chunk data
22
+ * 3. Calculate BMT hash on encrypted data
23
+ * 4. Return reference = address + encryption key (64 bytes)
24
+ *
25
+ * @param payloadBytes the data to be stored in the chunk
26
+ * @param encryptionKey optional encryption key (if not provided, a random key will be generated)
27
+ */
28
+ export declare function makeEncryptedContentAddressedChunk(payloadBytes: Uint8Array | string, encryptionKey?: Key): EncryptedChunk;
29
+ /**
30
+ * Decrypts an encrypted chunk given the encryption key
31
+ *
32
+ * @param encryptedChunkData The encrypted chunk data (span + payload)
33
+ * @param encryptionKey The 32-byte encryption key
34
+ */
35
+ export declare function decryptEncryptedChunk(encryptedChunkData: Uint8Array, encryptionKey: Key): Uint8Array;
36
+ /**
37
+ * Extracts encryption key from a 64-byte encrypted reference
38
+ *
39
+ * @param reference 64-byte reference (address + key)
40
+ */
41
+ export declare function extractEncryptionKey(reference: Reference): Key;
42
+ /**
43
+ * Extracts the chunk address from a 64-byte encrypted reference
44
+ *
45
+ * @param reference 64-byte reference (address + key)
46
+ */
47
+ export declare function extractChunkAddress(reference: Reference): Reference;
48
+ //# sourceMappingURL=encrypted-cac.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypted-cac.d.ts","sourceRoot":"","sources":["../../src/chunk/encrypted-cac.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAG5D,OAAO,EAAuC,KAAK,GAAG,EAAE,MAAM,cAAc,CAAA;AAI5E;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,GAAG,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kCAAkC,CAChD,YAAY,EAAE,UAAU,GAAG,MAAM,EACjC,aAAa,CAAC,EAAE,GAAG,GAClB,cAAc,CA4ChB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,kBAAkB,EAAE,UAAU,EAC9B,aAAa,EAAE,GAAG,GACjB,UAAU,CAEZ;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,GAAG,CAS9D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CASnE"}
@@ -0,0 +1,86 @@
1
+ export declare const KEY_LENGTH = 32;
2
+ export declare const REFERENCE_SIZE = 64;
3
+ export type Key = Uint8Array;
4
+ export interface Encrypter {
5
+ key(): Key;
6
+ encrypt(data: Uint8Array): Uint8Array;
7
+ }
8
+ export interface Decrypter {
9
+ key(): Key;
10
+ decrypt(data: Uint8Array): Uint8Array;
11
+ }
12
+ export interface EncryptionInterface extends Encrypter, Decrypter {
13
+ reset(): void;
14
+ }
15
+ /**
16
+ * Core encryption class implementing CTR-mode encryption with Keccak256
17
+ * This matches the Go implementation in bee/pkg/encryption/encryption.go
18
+ */
19
+ export declare class Encryption implements EncryptionInterface {
20
+ private readonly encryptionKey;
21
+ private readonly keyLen;
22
+ private readonly padding;
23
+ private index;
24
+ private readonly initCtr;
25
+ constructor(key: Key, padding: number, initCtr: number);
26
+ key(): Key;
27
+ /**
28
+ * Encrypts data with optional padding
29
+ */
30
+ encrypt(data: Uint8Array): Uint8Array;
31
+ /**
32
+ * Decrypts data (caller must know original length if padding was used)
33
+ */
34
+ decrypt(data: Uint8Array): Uint8Array;
35
+ /**
36
+ * Resets the counter - only safe to call after encryption/decryption is completed
37
+ */
38
+ reset(): void;
39
+ /**
40
+ * Transforms data by splitting into key-length segments and encrypting sequentially
41
+ */
42
+ private transform;
43
+ /**
44
+ * Segment-wise transformation using XOR with Keccak256-derived keys
45
+ * Matches the Go implementation's Transcrypt function
46
+ */
47
+ private transcrypt;
48
+ }
49
+ /**
50
+ * Generates a cryptographically secure random key
51
+ */
52
+ export declare function generateRandomKey(length?: number): Key;
53
+ /**
54
+ * Creates encryption interface for chunk span (first 8 bytes)
55
+ */
56
+ export declare function newSpanEncryption(key: Key): EncryptionInterface;
57
+ /**
58
+ * Creates encryption interface for chunk data
59
+ */
60
+ export declare function newDataEncryption(key: Key): EncryptionInterface;
61
+ export interface ChunkEncrypter {
62
+ encryptChunk(chunkData: Uint8Array, key?: Key): {
63
+ key: Key;
64
+ encryptedSpan: Uint8Array;
65
+ encryptedData: Uint8Array;
66
+ };
67
+ }
68
+ /**
69
+ * Default chunk encrypter implementation
70
+ */
71
+ export declare class DefaultChunkEncrypter implements ChunkEncrypter {
72
+ encryptChunk(chunkData: Uint8Array, key?: Key): {
73
+ key: Key;
74
+ encryptedSpan: Uint8Array;
75
+ encryptedData: Uint8Array;
76
+ };
77
+ }
78
+ /**
79
+ * Creates a new chunk encrypter
80
+ */
81
+ export declare function newChunkEncrypter(): ChunkEncrypter;
82
+ /**
83
+ * Decrypts encrypted chunk data using the provided encryption key
84
+ */
85
+ export declare function decryptChunkData(key: Key, encryptedChunkData: Uint8Array): Uint8Array;
86
+ //# sourceMappingURL=encryption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/chunk/encryption.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,eAAO,MAAM,cAAc,KAAK,CAAA;AAEhC,MAAM,MAAM,GAAG,GAAG,UAAU,CAAA;AAE5B,MAAM,WAAW,SAAS;IACxB,GAAG,IAAI,GAAG,CAAA;IACV,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAA;CACtC;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,IAAI,GAAG,CAAA;IACV,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAA;CACtC;AAED,MAAM,WAAW,mBAAoB,SAAQ,SAAS,EAAE,SAAS;IAC/D,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;GAGG;AACH,qBAAa,UAAW,YAAW,mBAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAK;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;gBAEpB,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAQtD,GAAG,IAAI,GAAG;IAIV;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU;IAoBrC;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU;IAerC;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,CAAC,SAAS;IAoBjB;;;OAGG;IACH,OAAO,CAAC,UAAU;CA8BnB;AAgDD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,MAAmB,GAAG,GAAG,CAKlE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,mBAAmB,CAK/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,mBAAmB,CAI/D;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,CACV,SAAS,EAAE,UAAU,EACrB,GAAG,CAAC,EAAE,GAAG,GACR;QACD,GAAG,EAAE,GAAG,CAAA;QACR,aAAa,EAAE,UAAU,CAAA;QACzB,aAAa,EAAE,UAAU,CAAA;KAC1B,CAAA;CACF;AAED;;GAEG;AACH,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,YAAY,CACV,SAAS,EAAE,UAAU,EACrB,GAAG,CAAC,EAAE,GAAG,GACR;QACD,GAAG,EAAE,GAAG,CAAA;QACR,aAAa,EAAE,UAAU,CAAA;QACzB,aAAa,EAAE,UAAU,CAAA;KAC1B;CAiBF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAElD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,kBAAkB,EAAE,UAAU,GAC7B,UAAU,CAeZ"}
@@ -0,0 +1,6 @@
1
+ export { MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE, SPAN_SIZE, UNENCRYPTED_REF_SIZE, ENCRYPTED_REF_SIZE, IDENTIFIER_SIZE, SIGNATURE_SIZE, SOC_HEADER_SIZE, DEFAULT_DOWNLOAD_CONCURRENCY, } from "./constants";
2
+ export { type Key, type ChunkEncrypter, type Encrypter, type Decrypter, type EncryptionInterface, KEY_LENGTH, REFERENCE_SIZE, newChunkEncrypter, decryptChunkData, generateRandomKey, newSpanEncryption, newDataEncryption, Encryption, DefaultChunkEncrypter, } from "./encryption";
3
+ export { calculateChunkAddress } from "./bmt";
4
+ export { type ContentAddressedChunk, makeContentAddressedChunk } from "./cac";
5
+ export { type EncryptedChunk, makeEncryptedContentAddressedChunk, decryptEncryptedChunk, extractEncryptionKey, extractChunkAddress, } from "./encrypted-cac";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chunk/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,EACT,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,eAAe,EACf,4BAA4B,GAC7B,MAAM,aAAa,CAAA;AAGpB,OAAO,EACL,KAAK,GAAG,EACR,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,qBAAqB,GACtB,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAA;AAG7C,OAAO,EAAE,KAAK,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,OAAO,CAAA;AAG7E,OAAO,EACL,KAAK,cAAc,EACnB,kCAAkC,EAClC,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,iBAAiB,CAAA"}