@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.
- package/README.md +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- package/src/utils/versioned-storage.ts +323 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB Cache for Batch Utilization Chunks
|
|
3
|
+
*
|
|
4
|
+
* Provides fast local caching of 4KB utilization chunks to avoid
|
|
5
|
+
* repeated Swarm downloads. Each chunk is 4096 bytes containing
|
|
6
|
+
* 1024 bucket counters (uint32).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Binary } from "cafe-utility"
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cache entry for a single utilization chunk
|
|
17
|
+
*/
|
|
18
|
+
export interface ChunkCacheEntry {
|
|
19
|
+
/** Batch ID (hex string) */
|
|
20
|
+
batchId: string
|
|
21
|
+
|
|
22
|
+
/** Chunk index (0-63) */
|
|
23
|
+
chunkIndex: number
|
|
24
|
+
|
|
25
|
+
/** Serialized chunk data (4KB) */
|
|
26
|
+
data: Uint8Array
|
|
27
|
+
|
|
28
|
+
/** Content hash for change detection */
|
|
29
|
+
contentHash: string
|
|
30
|
+
|
|
31
|
+
/** SOC reference if uploaded to Swarm */
|
|
32
|
+
socReference?: string
|
|
33
|
+
|
|
34
|
+
/** Last access timestamp (for eviction) */
|
|
35
|
+
lastAccess: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Metadata for a batch's utilization state
|
|
40
|
+
*/
|
|
41
|
+
export interface BatchMetadata {
|
|
42
|
+
batchId: string
|
|
43
|
+
lastSync: number
|
|
44
|
+
chunkCount: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// IndexedDB Cache Manager
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
const DB_NAME = "swarm-utilization-store"
|
|
52
|
+
const DB_VERSION = 1
|
|
53
|
+
const CHUNKS_STORE = "chunks"
|
|
54
|
+
const METADATA_STORE = "metadata"
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Manages IndexedDB cache for batch utilization chunks
|
|
58
|
+
*/
|
|
59
|
+
export class UtilizationStoreDB {
|
|
60
|
+
private db: IDBDatabase | undefined
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Open the IndexedDB database
|
|
64
|
+
*/
|
|
65
|
+
async open(): Promise<void> {
|
|
66
|
+
if (this.db) return
|
|
67
|
+
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
|
70
|
+
|
|
71
|
+
request.onerror = () => {
|
|
72
|
+
reject(new Error(`Failed to open IndexedDB: ${request.error}`))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
request.onsuccess = () => {
|
|
76
|
+
this.db = request.result
|
|
77
|
+
resolve()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
request.onupgradeneeded = (event) => {
|
|
81
|
+
const db = (event.target as IDBOpenDBRequest).result
|
|
82
|
+
|
|
83
|
+
// Create chunks store with compound key
|
|
84
|
+
if (!db.objectStoreNames.contains(CHUNKS_STORE)) {
|
|
85
|
+
const chunksStore = db.createObjectStore(CHUNKS_STORE, {
|
|
86
|
+
keyPath: ["batchId", "chunkIndex"],
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Index for querying by batchId
|
|
90
|
+
chunksStore.createIndex("batchId", "batchId", { unique: false })
|
|
91
|
+
|
|
92
|
+
// Index for eviction by lastAccess
|
|
93
|
+
chunksStore.createIndex("lastAccess", "lastAccess", {
|
|
94
|
+
unique: false,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Create metadata store
|
|
99
|
+
if (!db.objectStoreNames.contains(METADATA_STORE)) {
|
|
100
|
+
db.createObjectStore(METADATA_STORE, { keyPath: "batchId" })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get a chunk from cache
|
|
108
|
+
* @param batchId - Batch ID (hex string)
|
|
109
|
+
* @param chunkIndex - Chunk index (0-63)
|
|
110
|
+
* @returns Cache entry or undefined if not found
|
|
111
|
+
*/
|
|
112
|
+
async getChunk(
|
|
113
|
+
batchId: string,
|
|
114
|
+
chunkIndex: number,
|
|
115
|
+
): Promise<ChunkCacheEntry | undefined> {
|
|
116
|
+
await this.open()
|
|
117
|
+
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const transaction = this.db!.transaction([CHUNKS_STORE], "readonly")
|
|
120
|
+
const store = transaction.objectStore(CHUNKS_STORE)
|
|
121
|
+
const request = store.get([batchId, chunkIndex])
|
|
122
|
+
|
|
123
|
+
request.onsuccess = () => {
|
|
124
|
+
const entry = request.result as ChunkCacheEntry | undefined
|
|
125
|
+
|
|
126
|
+
if (entry) {
|
|
127
|
+
// Update lastAccess asynchronously (don't wait)
|
|
128
|
+
this.touchChunk(batchId, chunkIndex).catch((err) => {
|
|
129
|
+
console.warn("[UtilizationStore] Failed to update lastAccess:", err)
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
resolve(entry)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
request.onerror = () => {
|
|
137
|
+
reject(new Error(`Failed to get chunk: ${request.error}`))
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Store a chunk in cache
|
|
144
|
+
* @param entry - Cache entry to store
|
|
145
|
+
*/
|
|
146
|
+
async putChunk(entry: ChunkCacheEntry): Promise<void> {
|
|
147
|
+
await this.open()
|
|
148
|
+
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
const transaction = this.db!.transaction([CHUNKS_STORE], "readwrite")
|
|
151
|
+
const store = transaction.objectStore(CHUNKS_STORE)
|
|
152
|
+
|
|
153
|
+
// Update lastAccess before storing
|
|
154
|
+
const entryWithAccess = {
|
|
155
|
+
...entry,
|
|
156
|
+
lastAccess: Date.now(),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const request = store.put(entryWithAccess)
|
|
160
|
+
|
|
161
|
+
request.onsuccess = () => resolve()
|
|
162
|
+
request.onerror = () => {
|
|
163
|
+
reject(new Error(`Failed to put chunk: ${request.error}`))
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get all chunks for a batch
|
|
170
|
+
* @param batchId - Batch ID (hex string)
|
|
171
|
+
* @returns Array of cache entries (sorted by chunkIndex)
|
|
172
|
+
*/
|
|
173
|
+
async getAllChunks(batchId: string): Promise<ChunkCacheEntry[]> {
|
|
174
|
+
await this.open()
|
|
175
|
+
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
const transaction = this.db!.transaction([CHUNKS_STORE], "readonly")
|
|
178
|
+
const store = transaction.objectStore(CHUNKS_STORE)
|
|
179
|
+
const index = store.index("batchId")
|
|
180
|
+
const request = index.getAll(batchId)
|
|
181
|
+
|
|
182
|
+
request.onsuccess = () => {
|
|
183
|
+
const entries = request.result as ChunkCacheEntry[]
|
|
184
|
+
// Sort by chunkIndex for predictable order
|
|
185
|
+
entries.sort((a, b) => a.chunkIndex - b.chunkIndex)
|
|
186
|
+
resolve(entries)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
request.onerror = () => {
|
|
190
|
+
reject(new Error(`Failed to get all chunks: ${request.error}`))
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Clear all chunks for a batch
|
|
197
|
+
* @param batchId - Batch ID (hex string)
|
|
198
|
+
*/
|
|
199
|
+
async clearBatch(batchId: string): Promise<void> {
|
|
200
|
+
await this.open()
|
|
201
|
+
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
const transaction = this.db!.transaction(
|
|
204
|
+
[CHUNKS_STORE, METADATA_STORE],
|
|
205
|
+
"readwrite",
|
|
206
|
+
)
|
|
207
|
+
const chunksStore = transaction.objectStore(CHUNKS_STORE)
|
|
208
|
+
const metadataStore = transaction.objectStore(METADATA_STORE)
|
|
209
|
+
|
|
210
|
+
// Delete all chunks for this batch
|
|
211
|
+
const chunksIndex = chunksStore.index("batchId")
|
|
212
|
+
const chunksRequest = chunksIndex.openCursor(batchId)
|
|
213
|
+
|
|
214
|
+
chunksRequest.onsuccess = () => {
|
|
215
|
+
const cursor = chunksRequest.result
|
|
216
|
+
if (cursor) {
|
|
217
|
+
cursor.delete()
|
|
218
|
+
cursor.continue()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Delete metadata
|
|
223
|
+
metadataStore.delete(batchId)
|
|
224
|
+
|
|
225
|
+
transaction.oncomplete = () => resolve()
|
|
226
|
+
transaction.onerror = () => {
|
|
227
|
+
reject(new Error(`Failed to clear batch: ${transaction.error}`))
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Update lastAccess timestamp for a chunk
|
|
234
|
+
* @param batchId - Batch ID (hex string)
|
|
235
|
+
* @param chunkIndex - Chunk index (0-63)
|
|
236
|
+
*/
|
|
237
|
+
private async touchChunk(batchId: string, chunkIndex: number): Promise<void> {
|
|
238
|
+
await this.open()
|
|
239
|
+
|
|
240
|
+
return new Promise((resolve, reject) => {
|
|
241
|
+
const transaction = this.db!.transaction([CHUNKS_STORE], "readwrite")
|
|
242
|
+
const store = transaction.objectStore(CHUNKS_STORE)
|
|
243
|
+
const request = store.get([batchId, chunkIndex])
|
|
244
|
+
|
|
245
|
+
request.onsuccess = () => {
|
|
246
|
+
const entry = request.result as ChunkCacheEntry | undefined
|
|
247
|
+
if (entry) {
|
|
248
|
+
entry.lastAccess = Date.now()
|
|
249
|
+
store.put(entry)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
transaction.oncomplete = () => resolve()
|
|
254
|
+
transaction.onerror = () => {
|
|
255
|
+
reject(new Error(`Failed to touch chunk: ${transaction.error}`))
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get batch metadata
|
|
262
|
+
* @param batchId - Batch ID (hex string)
|
|
263
|
+
* @returns Metadata or undefined if not found
|
|
264
|
+
*/
|
|
265
|
+
async getMetadata(batchId: string): Promise<BatchMetadata | undefined> {
|
|
266
|
+
await this.open()
|
|
267
|
+
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
const transaction = this.db!.transaction([METADATA_STORE], "readonly")
|
|
270
|
+
const store = transaction.objectStore(METADATA_STORE)
|
|
271
|
+
const request = store.get(batchId)
|
|
272
|
+
|
|
273
|
+
request.onsuccess = () =>
|
|
274
|
+
resolve(request.result as BatchMetadata | undefined)
|
|
275
|
+
request.onerror = () => {
|
|
276
|
+
reject(new Error(`Failed to get metadata: ${request.error}`))
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Update batch metadata
|
|
283
|
+
* @param metadata - Metadata to store
|
|
284
|
+
*/
|
|
285
|
+
async putMetadata(metadata: BatchMetadata): Promise<void> {
|
|
286
|
+
await this.open()
|
|
287
|
+
|
|
288
|
+
return new Promise((resolve, reject) => {
|
|
289
|
+
const transaction = this.db!.transaction([METADATA_STORE], "readwrite")
|
|
290
|
+
const store = transaction.objectStore(METADATA_STORE)
|
|
291
|
+
const request = store.put(metadata)
|
|
292
|
+
|
|
293
|
+
request.onsuccess = () => resolve()
|
|
294
|
+
request.onerror = () => {
|
|
295
|
+
reject(new Error(`Failed to put metadata: ${request.error}`))
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Close the database connection
|
|
302
|
+
*/
|
|
303
|
+
close(): void {
|
|
304
|
+
if (this.db) {
|
|
305
|
+
this.db.close()
|
|
306
|
+
this.db = undefined
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Cache Eviction
|
|
313
|
+
// ============================================================================
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Policy for cache eviction
|
|
317
|
+
*/
|
|
318
|
+
export interface CacheEvictionPolicy {
|
|
319
|
+
/** Maximum age in milliseconds (default: 7 days) */
|
|
320
|
+
maxAge?: number
|
|
321
|
+
|
|
322
|
+
/** Maximum number of chunks to keep (default: 640 = 10 batches) */
|
|
323
|
+
maxChunks?: number
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Evict old cache entries based on policy
|
|
328
|
+
* @param cache - Cache database instance
|
|
329
|
+
* @param policy - Eviction policy
|
|
330
|
+
*/
|
|
331
|
+
export async function evictOldEntries(
|
|
332
|
+
cache: UtilizationStoreDB,
|
|
333
|
+
policy: CacheEvictionPolicy = {},
|
|
334
|
+
): Promise<void> {
|
|
335
|
+
const maxAge = policy.maxAge ?? 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
336
|
+
const maxChunks = policy.maxChunks ?? 640 // 10 batches × 64 chunks
|
|
337
|
+
|
|
338
|
+
await cache.open()
|
|
339
|
+
|
|
340
|
+
const db = (cache as never)["db"] as IDBDatabase
|
|
341
|
+
|
|
342
|
+
return new Promise((resolve, reject) => {
|
|
343
|
+
const transaction = db.transaction([CHUNKS_STORE], "readwrite")
|
|
344
|
+
const store = transaction.objectStore(CHUNKS_STORE)
|
|
345
|
+
const index = store.index("lastAccess")
|
|
346
|
+
|
|
347
|
+
const oldestAllowed = Date.now() - maxAge
|
|
348
|
+
const entries: Array<{ key: IDBValidKey; lastAccess: number }> = []
|
|
349
|
+
|
|
350
|
+
// Collect all entries with their lastAccess times
|
|
351
|
+
const request = index.openCursor()
|
|
352
|
+
|
|
353
|
+
request.onsuccess = () => {
|
|
354
|
+
const cursor = request.result
|
|
355
|
+
if (cursor) {
|
|
356
|
+
const entry = cursor.value as ChunkCacheEntry
|
|
357
|
+
entries.push({
|
|
358
|
+
key: cursor.primaryKey,
|
|
359
|
+
lastAccess: entry.lastAccess,
|
|
360
|
+
})
|
|
361
|
+
cursor.continue()
|
|
362
|
+
} else {
|
|
363
|
+
// All entries collected, now evict
|
|
364
|
+
// 1. Delete entries older than maxAge
|
|
365
|
+
for (const entry of entries) {
|
|
366
|
+
if (entry.lastAccess < oldestAllowed) {
|
|
367
|
+
store.delete(entry.key)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 2. If still over maxChunks, delete oldest entries
|
|
372
|
+
if (entries.length > maxChunks) {
|
|
373
|
+
entries.sort((a, b) => a.lastAccess - b.lastAccess)
|
|
374
|
+
const toDelete = entries.slice(0, entries.length - maxChunks)
|
|
375
|
+
for (const entry of toDelete) {
|
|
376
|
+
store.delete(entry.key)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
transaction.oncomplete = () => resolve()
|
|
383
|
+
transaction.onerror = () => {
|
|
384
|
+
reject(new Error(`Failed to evict entries: ${transaction.error}`))
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Calculate content hash for chunk data
|
|
391
|
+
* @param data - Chunk data (4KB)
|
|
392
|
+
* @returns Hex string hash
|
|
393
|
+
*/
|
|
394
|
+
export function calculateContentHash(data: Uint8Array): string {
|
|
395
|
+
const hash = Binary.keccak256(data)
|
|
396
|
+
return Binary.uint8ArrayToHex(hash)
|
|
397
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest"
|
|
2
|
+
import { SwarmIdClient } from "./swarm-id-client"
|
|
3
|
+
|
|
4
|
+
describe("SwarmIdClient connect()", () => {
|
|
5
|
+
let client: SwarmIdClient
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// Mock window object and its properties
|
|
9
|
+
const mockWindow = {
|
|
10
|
+
addEventListener: vi.fn(),
|
|
11
|
+
removeEventListener: vi.fn(),
|
|
12
|
+
parent: { postMessage: vi.fn() },
|
|
13
|
+
location: { origin: "https://localhost" },
|
|
14
|
+
open: vi.fn(),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
vi.stubGlobal("window", mockWindow)
|
|
18
|
+
vi.stubGlobal("document", {
|
|
19
|
+
createElement: vi.fn().mockReturnValue({
|
|
20
|
+
style: {},
|
|
21
|
+
onload: null,
|
|
22
|
+
onerror: null,
|
|
23
|
+
src: "",
|
|
24
|
+
contentWindow: { postMessage: vi.fn() },
|
|
25
|
+
}),
|
|
26
|
+
body: {
|
|
27
|
+
appendChild: vi.fn(),
|
|
28
|
+
removeChild: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
client = new SwarmIdClient({
|
|
33
|
+
iframeOrigin: "https://swarm-id.example.com",
|
|
34
|
+
metadata: {
|
|
35
|
+
name: "Test App",
|
|
36
|
+
description: "A test application",
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("should send connect message to proxy", async () => {
|
|
42
|
+
vi.spyOn(client, "ensureReady").mockImplementation(() => {})
|
|
43
|
+
const sendRequestSpy = vi
|
|
44
|
+
.spyOn(client as never, "sendRequest")
|
|
45
|
+
.mockResolvedValue({
|
|
46
|
+
type: "connectResponse",
|
|
47
|
+
requestId: "test",
|
|
48
|
+
success: true,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
await client.connect()
|
|
52
|
+
|
|
53
|
+
expect(sendRequestSpy).toHaveBeenCalledWith(
|
|
54
|
+
expect.objectContaining({
|
|
55
|
+
type: "connect",
|
|
56
|
+
agent: undefined,
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it("should send agent flag to proxy when agent option is true", async () => {
|
|
62
|
+
vi.spyOn(client, "ensureReady").mockImplementation(() => {})
|
|
63
|
+
const sendRequestSpy = vi
|
|
64
|
+
.spyOn(client as never, "sendRequest")
|
|
65
|
+
.mockResolvedValue({
|
|
66
|
+
type: "connectResponse",
|
|
67
|
+
requestId: "test",
|
|
68
|
+
success: true,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await client.connect({ agent: true })
|
|
72
|
+
|
|
73
|
+
expect(sendRequestSpy).toHaveBeenCalledWith(
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
type: "connect",
|
|
76
|
+
agent: true,
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("should throw when popup fails to open", async () => {
|
|
82
|
+
vi.spyOn(client, "ensureReady").mockImplementation(() => {})
|
|
83
|
+
vi.spyOn(client as never, "sendRequest").mockResolvedValue({
|
|
84
|
+
type: "connectResponse",
|
|
85
|
+
requestId: "test",
|
|
86
|
+
success: false,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await expect(client.connect()).rejects.toThrow(
|
|
90
|
+
"Failed to open authentication popup",
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("should throw error if client is not initialized", async () => {
|
|
95
|
+
await expect(client.connect()).rejects.toThrow(
|
|
96
|
+
"SwarmIdClient not initialized. Call initialize() first.",
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
})
|