@ghostlytools/cli 1.0.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.
- package/README.md +405 -0
- package/SKILL.md +282 -0
- package/dist/assets/argon2id-088uQfnH.js +645 -0
- package/dist/assets/argon2id-088uQfnH.js.map +1 -0
- package/dist/assets/bark_ffi_wasm_bg-Bxru_BVW.wasm +0 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js +6 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -0
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js +6 -0
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -0
- package/dist/assets/bitcoindevkit_bg-CRkZTSbO.wasm +0 -0
- package/dist/assets/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/dist/assets/endpoint-h3cqRyRz.js +185 -0
- package/dist/assets/endpoint-h3cqRyRz.js.map +1 -0
- package/dist/assets/fedimintWorker.mjs +95 -0
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js +6 -0
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -0
- package/dist/assets/fedimint_client_wasm_bg-DeFtkIv6.wasm +0 -0
- package/dist/assets/fileBytes-ByS16quP.js +154 -0
- package/dist/assets/fileBytes-ByS16quP.js.map +1 -0
- package/dist/assets/fileBytesIdb-COzoilo9.js +230 -0
- package/dist/assets/fileBytesIdb-COzoilo9.js.map +1 -0
- package/dist/assets/fileBytesOpfs-B27wAMUh.js +145 -0
- package/dist/assets/fileBytesOpfs-B27wAMUh.js.map +1 -0
- package/dist/assets/ghostly_iroh_web-CPa_YEfb.js +1154 -0
- package/dist/assets/ghostly_iroh_web-CPa_YEfb.js.map +1 -0
- package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
- package/dist/assets/hyperdhtRelay-BKsFpPjr.js +342 -0
- package/dist/assets/hyperdhtRelay-BKsFpPjr.js.map +1 -0
- package/dist/assets/legacy_ciphers-JJOu0JXL.js +5185 -0
- package/dist/assets/legacy_ciphers-JJOu0JXL.js.map +1 -0
- package/dist/assets/mainline-DOncVbJN.js +176 -0
- package/dist/assets/mainline-DOncVbJN.js.map +1 -0
- package/dist/assets/nacl-fast-CDhNCYHs.js +1512 -0
- package/dist/assets/nacl-fast-CDhNCYHs.js.map +1 -0
- package/dist/assets/noble_curves-DDWn5AH0.js +2978 -0
- package/dist/assets/noble_curves-DDWn5AH0.js.map +1 -0
- package/dist/assets/noble_hashes-B7AZyFyS.js +427 -0
- package/dist/assets/noble_hashes-B7AZyFyS.js.map +1 -0
- package/dist/assets/openpgp-BBFr-zHQ.js +16573 -0
- package/dist/assets/openpgp-BBFr-zHQ.js.map +1 -0
- package/dist/assets/server-5T5l8hfy.js +28293 -0
- package/dist/assets/server-5T5l8hfy.js.map +1 -0
- package/dist/assets/sha512-BYId-G-T.js +1034 -0
- package/dist/assets/sha512-BYId-G-T.js.map +1 -0
- package/dist/assets/src-DptDr2et.js +20971 -0
- package/dist/assets/src-DptDr2et.js.map +1 -0
- package/dist/assets/storage-Bya5b2Xt.js +1648 -0
- package/dist/assets/storage-Bya5b2Xt.js.map +1 -0
- package/dist/assets/tree-store-N362nuzZ.js +1031 -0
- package/dist/assets/tree-store-N362nuzZ.js.map +1 -0
- package/dist/assets/unbzip2-stream-B1C-JX9L.js +681 -0
- package/dist/assets/unbzip2-stream-B1C-JX9L.js.map +1 -0
- package/dist/assets/web-OUQl4Q7L.js +3802 -0
- package/dist/assets/web-OUQl4Q7L.js.map +1 -0
- package/dist/ghostly.mjs +17087 -0
- package/dist/ghostly.mjs.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1648 @@
|
|
|
1
|
+
//#region ../../node_modules/@breeztech/breez-sdk-spark/web/storage/index.js
|
|
2
|
+
/**
|
|
3
|
+
* ES6 module for Web IndexedDB Storage Implementation
|
|
4
|
+
* This provides an ES6 interface to IndexedDB storage for web browsers
|
|
5
|
+
*/
|
|
6
|
+
var MigrationManager = class {
|
|
7
|
+
constructor(db, StorageError, logger = null) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.StorageError = StorageError;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
this.migrations = this._getMigrations();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Handle IndexedDB upgrade event - called during database opening
|
|
15
|
+
*/
|
|
16
|
+
handleUpgrade(event, oldVersion, newVersion) {
|
|
17
|
+
const db = event.target.result;
|
|
18
|
+
const transaction = event.target.transaction;
|
|
19
|
+
this._log("info", `Upgrading IndexedDB from version ${oldVersion} to ${newVersion}`);
|
|
20
|
+
try {
|
|
21
|
+
for (let i = oldVersion; i < newVersion; i++) {
|
|
22
|
+
const migration = this.migrations[i];
|
|
23
|
+
if (migration) {
|
|
24
|
+
this._log("debug", `Running migration ${i + 1}: ${migration.name}`);
|
|
25
|
+
migration.upgrade(db, transaction);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
this._log("info", `Database migration completed successfully`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this._log("error", `Migration failed at version ${oldVersion}: ${error.message}`);
|
|
31
|
+
throw new this.StorageError(`Migration failed at version ${oldVersion}: ${error.message}`, error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
_log(level, message) {
|
|
35
|
+
if (this.logger && typeof this.logger.log === "function") this.logger.log({
|
|
36
|
+
line: message,
|
|
37
|
+
level
|
|
38
|
+
});
|
|
39
|
+
else if (level === "error") console.error(`[MigrationManager] ${message}`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Define all database migrations for IndexedDB
|
|
43
|
+
*
|
|
44
|
+
* Each migration is an object with:
|
|
45
|
+
* - name: Description of the migration
|
|
46
|
+
* - upgrade: Function that takes (db, transaction) and creates/modifies object stores
|
|
47
|
+
*/
|
|
48
|
+
_getMigrations() {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
name: "Create initial object stores",
|
|
52
|
+
upgrade: (db) => {
|
|
53
|
+
if (!db.objectStoreNames.contains("settings")) db.createObjectStore("settings", { keyPath: "key" });
|
|
54
|
+
if (!db.objectStoreNames.contains("payments")) {
|
|
55
|
+
const paymentStore = db.createObjectStore("payments", { keyPath: "id" });
|
|
56
|
+
paymentStore.createIndex("timestamp", "timestamp", { unique: false });
|
|
57
|
+
paymentStore.createIndex("paymentType", "paymentType", { unique: false });
|
|
58
|
+
paymentStore.createIndex("status", "status", { unique: false });
|
|
59
|
+
}
|
|
60
|
+
if (!db.objectStoreNames.contains("payment_metadata")) db.createObjectStore("payment_metadata", { keyPath: "paymentId" });
|
|
61
|
+
if (!db.objectStoreNames.contains("unclaimed_deposits")) db.createObjectStore("unclaimed_deposits", { keyPath: ["txid", "vout"] }).createIndex("txid", "txid", { unique: false });
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "Create invoice index",
|
|
66
|
+
upgrade: (db, transaction) => {
|
|
67
|
+
const paymentStore = transaction.objectStore("payments");
|
|
68
|
+
if (!paymentStore.indexNames.contains("invoice")) paymentStore.createIndex("invoice", "details.invoice", { unique: false });
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "Convert amount and fees from Number to BigInt for u128 support",
|
|
73
|
+
upgrade: (db, transaction) => {
|
|
74
|
+
const store = transaction.objectStore("payments");
|
|
75
|
+
const getAllRequest = store.getAll();
|
|
76
|
+
getAllRequest.onsuccess = () => {
|
|
77
|
+
const payments = getAllRequest.result;
|
|
78
|
+
let updated = 0;
|
|
79
|
+
payments.forEach((payment) => {
|
|
80
|
+
let needsUpdate = false;
|
|
81
|
+
if (typeof payment.amount === "number") {
|
|
82
|
+
payment.amount = BigInt(Math.round(payment.amount));
|
|
83
|
+
needsUpdate = true;
|
|
84
|
+
}
|
|
85
|
+
if (typeof payment.fees === "number") {
|
|
86
|
+
payment.fees = BigInt(Math.round(payment.fees));
|
|
87
|
+
needsUpdate = true;
|
|
88
|
+
}
|
|
89
|
+
if (needsUpdate) {
|
|
90
|
+
store.put(payment);
|
|
91
|
+
updated++;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
console.log(`Migrated ${updated} payment records to BigInt format`);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "Add sync tables",
|
|
100
|
+
upgrade: (db, transaction) => {
|
|
101
|
+
if (!db.objectStoreNames.contains("sync_revision")) {
|
|
102
|
+
db.createObjectStore("sync_revision", { keyPath: "id" });
|
|
103
|
+
transaction.objectStore("sync_revision").add({
|
|
104
|
+
id: 1,
|
|
105
|
+
revision: "0"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (!db.objectStoreNames.contains("sync_outgoing")) {
|
|
109
|
+
db.createObjectStore("sync_outgoing", { keyPath: [
|
|
110
|
+
"type",
|
|
111
|
+
"dataId",
|
|
112
|
+
"revision"
|
|
113
|
+
] });
|
|
114
|
+
transaction.objectStore("sync_outgoing").createIndex("revision", "revision");
|
|
115
|
+
}
|
|
116
|
+
if (!db.objectStoreNames.contains("sync_incoming")) {
|
|
117
|
+
db.createObjectStore("sync_incoming", { keyPath: [
|
|
118
|
+
"type",
|
|
119
|
+
"dataId",
|
|
120
|
+
"revision"
|
|
121
|
+
] });
|
|
122
|
+
transaction.objectStore("sync_incoming").createIndex("revision", "revision");
|
|
123
|
+
}
|
|
124
|
+
if (!db.objectStoreNames.contains("sync_state")) db.createObjectStore("sync_state", { keyPath: ["type", "dataId"] });
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "Create lnurl_receive_metadata store",
|
|
129
|
+
upgrade: (db) => {
|
|
130
|
+
if (!db.objectStoreNames.contains("lnurl_receive_metadata")) db.createObjectStore("lnurl_receive_metadata", { keyPath: "paymentHash" });
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "Clear unclaimed deposits for claim_error format change",
|
|
135
|
+
upgrade: (db, transaction) => {
|
|
136
|
+
if (db.objectStoreNames.contains("unclaimed_deposits")) transaction.objectStore("unclaimed_deposits").clear();
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "Clear sync tables for BreezSigner backward compatibility",
|
|
141
|
+
upgrade: (db, transaction) => {
|
|
142
|
+
if (db.objectStoreNames.contains("sync_outgoing")) transaction.objectStore("sync_outgoing").clear();
|
|
143
|
+
if (db.objectStoreNames.contains("sync_incoming")) transaction.objectStore("sync_incoming").clear();
|
|
144
|
+
if (db.objectStoreNames.contains("sync_state")) transaction.objectStore("sync_state").clear();
|
|
145
|
+
if (db.objectStoreNames.contains("sync_revision")) {
|
|
146
|
+
const syncRevision = transaction.objectStore("sync_revision");
|
|
147
|
+
syncRevision.clear();
|
|
148
|
+
syncRevision.put({
|
|
149
|
+
id: 1,
|
|
150
|
+
revision: "0"
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (db.objectStoreNames.contains("settings")) transaction.objectStore("settings").delete("sync_initial_complete");
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "Create parentPaymentId index for related payments lookup",
|
|
158
|
+
upgrade: (db, transaction) => {
|
|
159
|
+
if (db.objectStoreNames.contains("payment_metadata")) {
|
|
160
|
+
const metadataStore = transaction.objectStore("payment_metadata");
|
|
161
|
+
if (!metadataStore.indexNames.contains("parentPaymentId")) metadataStore.createIndex("parentPaymentId", "parentPaymentId", { unique: false });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "Add tx_type to token payments and trigger token re-sync",
|
|
167
|
+
upgrade: (db, transaction) => {
|
|
168
|
+
if (db.objectStoreNames.contains("payments")) {
|
|
169
|
+
const paymentStore = transaction.objectStore("payments");
|
|
170
|
+
const getAllRequest = paymentStore.getAll();
|
|
171
|
+
getAllRequest.onsuccess = () => {
|
|
172
|
+
getAllRequest.result.forEach((payment) => {
|
|
173
|
+
let details = null;
|
|
174
|
+
if (payment.details && typeof payment.details === "string") try {
|
|
175
|
+
details = JSON.parse(payment.details);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
else details = payment.details;
|
|
180
|
+
if (details && details.type === "token" && !details.txType) {
|
|
181
|
+
details.txType = "transfer";
|
|
182
|
+
payment.details = JSON.stringify(details);
|
|
183
|
+
paymentStore.put(payment);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (db.objectStoreNames.contains("settings")) {
|
|
189
|
+
const settingsStore = transaction.objectStore("settings");
|
|
190
|
+
const getRequest = settingsStore.get("sync_offset");
|
|
191
|
+
getRequest.onsuccess = () => {
|
|
192
|
+
const syncCache = getRequest.result;
|
|
193
|
+
if (syncCache && syncCache.value) try {
|
|
194
|
+
const syncInfo = JSON.parse(syncCache.value);
|
|
195
|
+
syncInfo.last_synced_final_token_payment_id = null;
|
|
196
|
+
settingsStore.put({
|
|
197
|
+
key: "sync_offset",
|
|
198
|
+
value: JSON.stringify(syncInfo)
|
|
199
|
+
});
|
|
200
|
+
} catch (e) {}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "Clear sync tables to force re-sync",
|
|
207
|
+
upgrade: (db, transaction) => {
|
|
208
|
+
if (db.objectStoreNames.contains("sync_outgoing")) transaction.objectStore("sync_outgoing").clear();
|
|
209
|
+
if (db.objectStoreNames.contains("sync_incoming")) transaction.objectStore("sync_incoming").clear();
|
|
210
|
+
if (db.objectStoreNames.contains("sync_state")) transaction.objectStore("sync_state").clear();
|
|
211
|
+
if (db.objectStoreNames.contains("sync_revision")) {
|
|
212
|
+
const syncRevision = transaction.objectStore("sync_revision");
|
|
213
|
+
syncRevision.clear();
|
|
214
|
+
syncRevision.put({
|
|
215
|
+
id: 1,
|
|
216
|
+
revision: "0"
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (db.objectStoreNames.contains("settings")) transaction.objectStore("settings").delete("sync_initial_complete");
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "Add preimage to lnurl_receive_metadata for LUD-21 and NIP-57",
|
|
224
|
+
upgrade: (db, transaction) => {
|
|
225
|
+
if (db.objectStoreNames.contains("settings")) transaction.objectStore("settings").delete("lnurl_metadata_updated_after");
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "Backfill htlc_details for existing Lightning payments",
|
|
230
|
+
upgrade: (db, transaction) => {
|
|
231
|
+
if (db.objectStoreNames.contains("payments")) {
|
|
232
|
+
const paymentStore = transaction.objectStore("payments");
|
|
233
|
+
const getAllRequest = paymentStore.getAll();
|
|
234
|
+
getAllRequest.onsuccess = () => {
|
|
235
|
+
getAllRequest.result.forEach((payment) => {
|
|
236
|
+
let details;
|
|
237
|
+
if (typeof payment.details === "string") try {
|
|
238
|
+
details = JSON.parse(payment.details);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
else details = payment.details;
|
|
243
|
+
if (details && details.type === "lightning" && !details.htlcDetails) {
|
|
244
|
+
let htlcStatus;
|
|
245
|
+
if (payment.status === "completed") htlcStatus = "preimageShared";
|
|
246
|
+
else if (payment.status === "pending") htlcStatus = "waitingForPreimage";
|
|
247
|
+
else htlcStatus = "returned";
|
|
248
|
+
details.htlcDetails = {
|
|
249
|
+
paymentHash: details.paymentHash,
|
|
250
|
+
preimage: details.preimage || null,
|
|
251
|
+
expiryTime: 0,
|
|
252
|
+
status: htlcStatus
|
|
253
|
+
};
|
|
254
|
+
payment.details = JSON.stringify(details);
|
|
255
|
+
paymentStore.put(payment);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (db.objectStoreNames.contains("settings")) {
|
|
261
|
+
const settingsStore = transaction.objectStore("settings");
|
|
262
|
+
const getRequest = settingsStore.get("sync_offset");
|
|
263
|
+
getRequest.onsuccess = () => {
|
|
264
|
+
const syncCache = getRequest.result;
|
|
265
|
+
if (syncCache && syncCache.value) try {
|
|
266
|
+
const syncInfo = JSON.parse(syncCache.value);
|
|
267
|
+
syncInfo.offset = 0;
|
|
268
|
+
settingsStore.put({
|
|
269
|
+
key: "sync_offset",
|
|
270
|
+
value: JSON.stringify(syncInfo)
|
|
271
|
+
});
|
|
272
|
+
} catch (e) {}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "Create contacts store",
|
|
279
|
+
upgrade: (db) => {
|
|
280
|
+
if (!db.objectStoreNames.contains("contacts")) {
|
|
281
|
+
const contactsStore = db.createObjectStore("contacts", { keyPath: "id" });
|
|
282
|
+
contactsStore.createIndex("name_identifier", ["name", "paymentIdentifier"], { unique: false });
|
|
283
|
+
contactsStore.createIndex("name", "name", { unique: false });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: "Clear cached lightning address for LnurlInfo schema change",
|
|
289
|
+
upgrade: (db, transaction) => {
|
|
290
|
+
if (db.objectStoreNames.contains("settings")) transaction.objectStore("settings").delete("lightning_address");
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "Drop preimage from lnurl_receive_metadata records",
|
|
295
|
+
upgrade: (db, transaction) => {
|
|
296
|
+
if (db.objectStoreNames.contains("lnurl_receive_metadata")) {
|
|
297
|
+
const request = transaction.objectStore("lnurl_receive_metadata").openCursor();
|
|
298
|
+
request.onsuccess = (event) => {
|
|
299
|
+
const cursor = event.target.result;
|
|
300
|
+
if (cursor) {
|
|
301
|
+
const record = cursor.value;
|
|
302
|
+
if ("preimage" in record) {
|
|
303
|
+
delete record.preimage;
|
|
304
|
+
cursor.update(record);
|
|
305
|
+
}
|
|
306
|
+
cursor.continue();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "Clear cached lightning address for CachedLightningAddress format change",
|
|
314
|
+
upgrade: (db, transaction) => {
|
|
315
|
+
if (db.objectStoreNames.contains("settings")) transaction.objectStore("settings").delete("lightning_address");
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "Create contacts store (recovery for skipped migration)",
|
|
320
|
+
upgrade: (db) => {
|
|
321
|
+
if (!db.objectStoreNames.contains("contacts")) {
|
|
322
|
+
const contactsStore = db.createObjectStore("contacts", { keyPath: "id" });
|
|
323
|
+
contactsStore.createIndex("name_identifier", ["name", "paymentIdentifier"], { unique: false });
|
|
324
|
+
contactsStore.createIndex("name", "name", { unique: false });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "Clear legacy deposit details and reset sync offset for vout",
|
|
330
|
+
upgrade: (db, transaction) => {
|
|
331
|
+
if (db.objectStoreNames.contains("payments")) {
|
|
332
|
+
const cursorRequest = transaction.objectStore("payments").openCursor();
|
|
333
|
+
cursorRequest.onsuccess = (event) => {
|
|
334
|
+
const cursor = event.target.result;
|
|
335
|
+
if (!cursor) return;
|
|
336
|
+
const payment = cursor.value;
|
|
337
|
+
let details = null;
|
|
338
|
+
if (payment.details && typeof payment.details === "string") try {
|
|
339
|
+
details = JSON.parse(payment.details);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
details = null;
|
|
342
|
+
}
|
|
343
|
+
else details = payment.details;
|
|
344
|
+
if (details && details.type === "deposit") {
|
|
345
|
+
payment.details = null;
|
|
346
|
+
cursor.update(payment);
|
|
347
|
+
}
|
|
348
|
+
cursor.continue();
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (db.objectStoreNames.contains("settings")) {
|
|
352
|
+
const settingsStore = transaction.objectStore("settings");
|
|
353
|
+
const getRequest = settingsStore.get("sync_offset");
|
|
354
|
+
getRequest.onsuccess = () => {
|
|
355
|
+
const syncCache = getRequest.result;
|
|
356
|
+
if (syncCache && syncCache.value) try {
|
|
357
|
+
const syncInfo = JSON.parse(syncCache.value);
|
|
358
|
+
syncInfo.offset = 0;
|
|
359
|
+
settingsStore.put({
|
|
360
|
+
key: "sync_offset",
|
|
361
|
+
value: JSON.stringify(syncInfo)
|
|
362
|
+
});
|
|
363
|
+
} catch (e) {}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: "Backfill conversion_info type discriminator for serde tagged enum",
|
|
370
|
+
upgrade: (db, transaction) => {
|
|
371
|
+
if (db.objectStoreNames.contains("payment_metadata")) {
|
|
372
|
+
const request = transaction.objectStore("payment_metadata").openCursor();
|
|
373
|
+
request.onsuccess = (event) => {
|
|
374
|
+
const cursor = event.target.result;
|
|
375
|
+
if (cursor) {
|
|
376
|
+
const record = cursor.value;
|
|
377
|
+
if (record.conversionInfo) try {
|
|
378
|
+
const ci = typeof record.conversionInfo === "string" ? JSON.parse(record.conversionInfo) : record.conversionInfo;
|
|
379
|
+
if (!ci.type) {
|
|
380
|
+
ci.type = "amm";
|
|
381
|
+
record.conversionInfo = JSON.stringify(ci);
|
|
382
|
+
cursor.update(record);
|
|
383
|
+
}
|
|
384
|
+
} catch (e) {}
|
|
385
|
+
cursor.continue();
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: "Create cross_chain_swaps store",
|
|
393
|
+
upgrade: (db) => {
|
|
394
|
+
if (!db.objectStoreNames.contains("cross_chain_swaps")) db.createObjectStore("cross_chain_swaps", { keyPath: ["provider", "id"] });
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: "Clear declined instant claim status after its shape changed",
|
|
399
|
+
upgrade: (db, transaction) => {
|
|
400
|
+
if (!db.objectStoreNames.contains("unclaimed_deposits")) return;
|
|
401
|
+
const cursorRequest = transaction.objectStore("unclaimed_deposits").openCursor();
|
|
402
|
+
cursorRequest.onsuccess = (event) => {
|
|
403
|
+
const cursor = event.target.result;
|
|
404
|
+
if (!cursor) return;
|
|
405
|
+
const deposit = cursor.value;
|
|
406
|
+
if (deposit.instantClaimStatus && String(deposit.instantClaimStatus).toLowerCase().includes("declined")) {
|
|
407
|
+
deposit.instantClaimStatus = null;
|
|
408
|
+
cursor.update(deposit);
|
|
409
|
+
}
|
|
410
|
+
cursor.continue();
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "Create watched_deposit_addresses store",
|
|
416
|
+
upgrade: (db) => {
|
|
417
|
+
if (!db.objectStoreNames.contains("watched_deposit_addresses")) db.createObjectStore("watched_deposit_addresses", { keyPath: "address" });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
var StorageError = class StorageError extends Error {
|
|
424
|
+
constructor(message, cause = null) {
|
|
425
|
+
super(message);
|
|
426
|
+
this.name = "StorageError";
|
|
427
|
+
this.cause = cause;
|
|
428
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, StorageError);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
var IndexedDBStorage = class {
|
|
432
|
+
constructor(dbName = "BreezSDK", logger = null) {
|
|
433
|
+
this.dbName = dbName;
|
|
434
|
+
this.db = null;
|
|
435
|
+
this.migrationManager = null;
|
|
436
|
+
this.logger = logger;
|
|
437
|
+
this.dbVersion = 22;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Initialize the storage - must be called before using other methods
|
|
441
|
+
*/
|
|
442
|
+
async initialize() {
|
|
443
|
+
if (this.db) return this;
|
|
444
|
+
if (typeof indexedDB === "undefined") throw new StorageError("IndexedDB is not available in this environment");
|
|
445
|
+
const migrationCount = new MigrationManager(null, StorageError).migrations.length;
|
|
446
|
+
if (this.dbVersion !== migrationCount) throw new StorageError(`dbVersion (${this.dbVersion}) must equal the migration count (${migrationCount}). Migrations are append-only: append a new migration and bump dbVersion to match.`);
|
|
447
|
+
return new Promise((resolve, reject) => {
|
|
448
|
+
const request = indexedDB.open(this.dbName, this.dbVersion);
|
|
449
|
+
request.onerror = () => {
|
|
450
|
+
reject(new StorageError(`Failed to open IndexedDB: ${request.error?.message || "Unknown error"}`, request.error));
|
|
451
|
+
};
|
|
452
|
+
request.onsuccess = () => {
|
|
453
|
+
this.db = request.result;
|
|
454
|
+
this.migrationManager = new MigrationManager(this.db, StorageError, this.logger);
|
|
455
|
+
this.db.onversionchange = () => {
|
|
456
|
+
this.db.close();
|
|
457
|
+
this.db = null;
|
|
458
|
+
};
|
|
459
|
+
resolve(this);
|
|
460
|
+
};
|
|
461
|
+
request.onupgradeneeded = (event) => {
|
|
462
|
+
this.db = event.target.result;
|
|
463
|
+
this.migrationManager = new MigrationManager(this.db, StorageError, this.logger);
|
|
464
|
+
try {
|
|
465
|
+
this.migrationManager.handleUpgrade(event, event.oldVersion, event.newVersion);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
reject(error);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Close the database connection
|
|
474
|
+
*/
|
|
475
|
+
close() {
|
|
476
|
+
if (this.db) {
|
|
477
|
+
this.db.close();
|
|
478
|
+
this.db = null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async getCachedItem(key) {
|
|
482
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
const request = this.db.transaction("settings", "readonly").objectStore("settings").get(key);
|
|
485
|
+
request.onsuccess = () => {
|
|
486
|
+
const result = request.result;
|
|
487
|
+
resolve(result ? result.value : null);
|
|
488
|
+
};
|
|
489
|
+
request.onerror = () => {
|
|
490
|
+
reject(new StorageError(`Failed to get cached item '${key}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
491
|
+
};
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
async setCachedItem(key, value) {
|
|
495
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
496
|
+
return new Promise((resolve, reject) => {
|
|
497
|
+
const request = this.db.transaction("settings", "readwrite").objectStore("settings").put({
|
|
498
|
+
key,
|
|
499
|
+
value
|
|
500
|
+
});
|
|
501
|
+
request.onsuccess = () => resolve();
|
|
502
|
+
request.onerror = () => {
|
|
503
|
+
reject(new StorageError(`Failed to set cached item '${key}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
async deleteCachedItem(key) {
|
|
508
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
509
|
+
return new Promise((resolve, reject) => {
|
|
510
|
+
const request = this.db.transaction("settings", "readwrite").objectStore("settings").delete(key);
|
|
511
|
+
request.onsuccess = () => resolve();
|
|
512
|
+
request.onerror = () => {
|
|
513
|
+
reject(new StorageError(`Failed to delete cached item '${key}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
514
|
+
};
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Gets the set of payment IDs that are related payments (have a parentPaymentId).
|
|
519
|
+
* Uses the parentPaymentId index for efficient lookup.
|
|
520
|
+
* @param {IDBObjectStore} metadataStore - The payment_metadata object store
|
|
521
|
+
* @returns {Promise<Set<string>>} Set of payment IDs that are related payments
|
|
522
|
+
*/
|
|
523
|
+
_getRelatedPaymentIds(metadataStore) {
|
|
524
|
+
return new Promise((resolve) => {
|
|
525
|
+
const relatedPaymentIds = /* @__PURE__ */ new Set();
|
|
526
|
+
if (!metadataStore.indexNames.contains("parentPaymentId")) {
|
|
527
|
+
const cursorRequest = metadataStore.openCursor();
|
|
528
|
+
cursorRequest.onsuccess = (event) => {
|
|
529
|
+
const cursor = event.target.result;
|
|
530
|
+
if (cursor) {
|
|
531
|
+
if (cursor.value.parentPaymentId) relatedPaymentIds.add(cursor.value.paymentId);
|
|
532
|
+
cursor.continue();
|
|
533
|
+
} else resolve(relatedPaymentIds);
|
|
534
|
+
};
|
|
535
|
+
cursorRequest.onerror = () => resolve(/* @__PURE__ */ new Set());
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const cursorRequest = metadataStore.index("parentPaymentId").openCursor();
|
|
539
|
+
cursorRequest.onsuccess = (event) => {
|
|
540
|
+
const cursor = event.target.result;
|
|
541
|
+
if (cursor) {
|
|
542
|
+
if (cursor.value.parentPaymentId) relatedPaymentIds.add(cursor.value.paymentId);
|
|
543
|
+
cursor.continue();
|
|
544
|
+
} else resolve(relatedPaymentIds);
|
|
545
|
+
};
|
|
546
|
+
cursorRequest.onerror = () => {
|
|
547
|
+
resolve(/* @__PURE__ */ new Set());
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
async listPayments(request) {
|
|
552
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
553
|
+
const actualOffset = request.offset !== null ? request.offset : 0;
|
|
554
|
+
const actualLimit = request.limit !== null ? request.limit : 4294967295;
|
|
555
|
+
const transaction = this.db.transaction([
|
|
556
|
+
"payments",
|
|
557
|
+
"payment_metadata",
|
|
558
|
+
"lnurl_receive_metadata"
|
|
559
|
+
], "readonly");
|
|
560
|
+
const paymentStore = transaction.objectStore("payments");
|
|
561
|
+
const metadataStore = transaction.objectStore("payment_metadata");
|
|
562
|
+
const lnurlReceiveMetadataStore = transaction.objectStore("lnurl_receive_metadata");
|
|
563
|
+
const relatedPaymentIds = await this._getRelatedPaymentIds(metadataStore);
|
|
564
|
+
return new Promise((resolve, reject) => {
|
|
565
|
+
const payments = [];
|
|
566
|
+
let count = 0;
|
|
567
|
+
let skipped = 0;
|
|
568
|
+
const cursorDirection = request.sortAscending ? "next" : "prev";
|
|
569
|
+
const cursorRequest = paymentStore.index("timestamp").openCursor(null, cursorDirection);
|
|
570
|
+
cursorRequest.onsuccess = (event) => {
|
|
571
|
+
const cursor = event.target.result;
|
|
572
|
+
if (!cursor || count >= actualLimit) {
|
|
573
|
+
resolve(payments);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const payment = cursor.value;
|
|
577
|
+
if (relatedPaymentIds.has(payment.id)) {
|
|
578
|
+
cursor.continue();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (skipped < actualOffset) {
|
|
582
|
+
skipped++;
|
|
583
|
+
cursor.continue();
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const metadataRequest = metadataStore.get(payment.id);
|
|
587
|
+
metadataRequest.onsuccess = () => {
|
|
588
|
+
const metadata = metadataRequest.result;
|
|
589
|
+
const paymentWithMetadata = this._mergePaymentMetadata(payment, metadata);
|
|
590
|
+
this._fetchLnurlReceiveMetadata(paymentWithMetadata, lnurlReceiveMetadataStore).then((mergedPayment) => {
|
|
591
|
+
if (!this._matchesFilters(mergedPayment, request)) {
|
|
592
|
+
cursor.continue();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
payments.push(mergedPayment);
|
|
596
|
+
count++;
|
|
597
|
+
cursor.continue();
|
|
598
|
+
}).catch(() => {
|
|
599
|
+
if (!this._matchesFilters(paymentWithMetadata, request)) {
|
|
600
|
+
cursor.continue();
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
payments.push(paymentWithMetadata);
|
|
604
|
+
count++;
|
|
605
|
+
cursor.continue();
|
|
606
|
+
});
|
|
607
|
+
};
|
|
608
|
+
metadataRequest.onerror = () => {
|
|
609
|
+
if (this._matchesFilters(payment, request)) {
|
|
610
|
+
payments.push(payment);
|
|
611
|
+
count++;
|
|
612
|
+
}
|
|
613
|
+
cursor.continue();
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
cursorRequest.onerror = () => {
|
|
617
|
+
reject(new StorageError(`Failed to list payments (request: ${JSON.stringify(request)}: ${cursorRequest.error?.message || "Unknown error"}`, cursorRequest.error));
|
|
618
|
+
};
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
async applyPaymentUpdate(payment) {
|
|
622
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
623
|
+
return new Promise((resolve, reject) => {
|
|
624
|
+
let shouldEmit = false;
|
|
625
|
+
let settled = false;
|
|
626
|
+
const transaction = this.db.transaction("payments", "readwrite");
|
|
627
|
+
const store = transaction.objectStore("payments");
|
|
628
|
+
const rejectOnce = (message, error) => {
|
|
629
|
+
if (settled) return;
|
|
630
|
+
settled = true;
|
|
631
|
+
reject(new StorageError(message, error));
|
|
632
|
+
};
|
|
633
|
+
transaction.oncomplete = () => {
|
|
634
|
+
if (settled) return;
|
|
635
|
+
settled = true;
|
|
636
|
+
resolve(shouldEmit);
|
|
637
|
+
};
|
|
638
|
+
transaction.onerror = () => {
|
|
639
|
+
rejectOnce(`Failed to apply payment update '${payment.id}': ${transaction.error?.message || "Unknown error"}`, transaction.error);
|
|
640
|
+
};
|
|
641
|
+
transaction.onabort = () => {
|
|
642
|
+
rejectOnce(`Payment update transaction aborted for '${payment.id}': ${transaction.error?.message || "Unknown error"}`, transaction.error);
|
|
643
|
+
};
|
|
644
|
+
const getRequest = store.get(payment.id);
|
|
645
|
+
getRequest.onsuccess = () => {
|
|
646
|
+
const storedPayment = getRequest.result;
|
|
647
|
+
const stored = storedPayment ? this._normalizePaymentStatus(storedPayment.status) : null;
|
|
648
|
+
const next = this._normalizePaymentStatus(payment.status);
|
|
649
|
+
let shouldPersist;
|
|
650
|
+
if (stored == null) {
|
|
651
|
+
shouldPersist = true;
|
|
652
|
+
shouldEmit = true;
|
|
653
|
+
} else if (stored === next) {
|
|
654
|
+
console.debug(`Skipping redundant payment event: id=${payment.id} status=${next}`);
|
|
655
|
+
shouldPersist = true;
|
|
656
|
+
shouldEmit = false;
|
|
657
|
+
} else if (this._isFinalPaymentStatus(stored)) {
|
|
658
|
+
console.warn(`Skipping payment update (would replace terminal status): id=${payment.id} stored=${stored} new=${next}`);
|
|
659
|
+
shouldPersist = false;
|
|
660
|
+
shouldEmit = false;
|
|
661
|
+
} else {
|
|
662
|
+
shouldPersist = true;
|
|
663
|
+
shouldEmit = true;
|
|
664
|
+
}
|
|
665
|
+
if (!shouldPersist) return;
|
|
666
|
+
const putRequest = store.put(this._paymentToStore(payment));
|
|
667
|
+
putRequest.onerror = () => {
|
|
668
|
+
rejectOnce(`Failed to persist payment update '${payment.id}': ${putRequest.error?.message || "Unknown error"}`, putRequest.error);
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
getRequest.onerror = () => {
|
|
672
|
+
rejectOnce(`Failed to read payment '${payment.id}' before update: ${getRequest.error?.message || "Unknown error"}`, getRequest.error);
|
|
673
|
+
};
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
async getPaymentById(id) {
|
|
677
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
678
|
+
return new Promise((resolve, reject) => {
|
|
679
|
+
const transaction = this.db.transaction([
|
|
680
|
+
"payments",
|
|
681
|
+
"payment_metadata",
|
|
682
|
+
"lnurl_receive_metadata"
|
|
683
|
+
], "readonly");
|
|
684
|
+
const paymentStore = transaction.objectStore("payments");
|
|
685
|
+
const metadataStore = transaction.objectStore("payment_metadata");
|
|
686
|
+
const lnurlReceiveMetadataStore = transaction.objectStore("lnurl_receive_metadata");
|
|
687
|
+
const paymentRequest = paymentStore.get(id);
|
|
688
|
+
paymentRequest.onsuccess = () => {
|
|
689
|
+
const payment = paymentRequest.result;
|
|
690
|
+
if (!payment) {
|
|
691
|
+
reject(new StorageError(`Payment with id '${id}' not found`));
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const metadataRequest = metadataStore.get(id);
|
|
695
|
+
metadataRequest.onsuccess = () => {
|
|
696
|
+
const metadata = metadataRequest.result;
|
|
697
|
+
const paymentWithMetadata = this._mergePaymentMetadata(payment, metadata);
|
|
698
|
+
this._fetchLnurlReceiveMetadata(paymentWithMetadata, lnurlReceiveMetadataStore).then(resolve).catch(() => {
|
|
699
|
+
resolve(paymentWithMetadata);
|
|
700
|
+
});
|
|
701
|
+
};
|
|
702
|
+
metadataRequest.onerror = () => {
|
|
703
|
+
resolve(payment);
|
|
704
|
+
};
|
|
705
|
+
};
|
|
706
|
+
paymentRequest.onerror = () => {
|
|
707
|
+
reject(new StorageError(`Failed to get payment by id '${id}': ${paymentRequest.error?.message || "Unknown error"}`, paymentRequest.error));
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
async getPaymentByInvoice(invoice) {
|
|
712
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
713
|
+
return new Promise((resolve, reject) => {
|
|
714
|
+
const transaction = this.db.transaction([
|
|
715
|
+
"payments",
|
|
716
|
+
"payment_metadata",
|
|
717
|
+
"lnurl_receive_metadata"
|
|
718
|
+
], "readonly");
|
|
719
|
+
const invoiceIndex = transaction.objectStore("payments").index("invoice");
|
|
720
|
+
const metadataStore = transaction.objectStore("payment_metadata");
|
|
721
|
+
const lnurlReceiveMetadataStore = transaction.objectStore("lnurl_receive_metadata");
|
|
722
|
+
const paymentRequest = invoiceIndex.get(invoice);
|
|
723
|
+
paymentRequest.onsuccess = () => {
|
|
724
|
+
const payment = paymentRequest.result;
|
|
725
|
+
if (!payment) {
|
|
726
|
+
resolve(null);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const metadataRequest = metadataStore.get(payment.id);
|
|
730
|
+
metadataRequest.onsuccess = () => {
|
|
731
|
+
const metadata = metadataRequest.result;
|
|
732
|
+
const paymentWithMetadata = this._mergePaymentMetadata(payment, metadata);
|
|
733
|
+
this._fetchLnurlReceiveMetadata(paymentWithMetadata, lnurlReceiveMetadataStore).then(resolve).catch(() => {
|
|
734
|
+
resolve(paymentWithMetadata);
|
|
735
|
+
});
|
|
736
|
+
};
|
|
737
|
+
metadataRequest.onerror = () => {
|
|
738
|
+
resolve(payment);
|
|
739
|
+
};
|
|
740
|
+
};
|
|
741
|
+
paymentRequest.onerror = () => {
|
|
742
|
+
reject(new StorageError(`Failed to get payment by invoice '${invoice}': ${paymentRequest.error?.message || "Unknown error"}`, paymentRequest.error));
|
|
743
|
+
};
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Checks if any related payments exist (payments with a parentPaymentId).
|
|
748
|
+
* Uses the parentPaymentId index for efficient lookup.
|
|
749
|
+
* @param {IDBObjectStore} metadataStore - The payment_metadata object store
|
|
750
|
+
* @returns {Promise<boolean>} True if any related payments exist
|
|
751
|
+
*/
|
|
752
|
+
_hasRelatedPayments(metadataStore) {
|
|
753
|
+
return new Promise((resolve) => {
|
|
754
|
+
if (!metadataStore.indexNames.contains("parentPaymentId")) {
|
|
755
|
+
const cursorRequest = metadataStore.openCursor();
|
|
756
|
+
cursorRequest.onsuccess = (event) => {
|
|
757
|
+
const cursor = event.target.result;
|
|
758
|
+
if (cursor) {
|
|
759
|
+
if (cursor.value.parentPaymentId) {
|
|
760
|
+
resolve(true);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
cursor.continue();
|
|
764
|
+
} else resolve(false);
|
|
765
|
+
};
|
|
766
|
+
cursorRequest.onerror = () => resolve(true);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const cursorRequest = metadataStore.index("parentPaymentId").openCursor();
|
|
770
|
+
cursorRequest.onsuccess = (event) => {
|
|
771
|
+
const cursor = event.target.result;
|
|
772
|
+
if (cursor && cursor.value.parentPaymentId) resolve(true);
|
|
773
|
+
else if (cursor) cursor.continue();
|
|
774
|
+
else resolve(false);
|
|
775
|
+
};
|
|
776
|
+
cursorRequest.onerror = () => {
|
|
777
|
+
resolve(true);
|
|
778
|
+
};
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Gets payments that have any of the specified parent payment IDs.
|
|
783
|
+
* @param {string[]} parentPaymentIds - Array of parent payment IDs
|
|
784
|
+
* @returns {Promise<Object>} Map of parentPaymentId -> array of RelatedPayment objects
|
|
785
|
+
*/
|
|
786
|
+
async getPaymentsByParentIds(parentPaymentIds) {
|
|
787
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
788
|
+
if (!parentPaymentIds || parentPaymentIds.length === 0) return {};
|
|
789
|
+
const transaction = this.db.transaction([
|
|
790
|
+
"payments",
|
|
791
|
+
"payment_metadata",
|
|
792
|
+
"lnurl_receive_metadata"
|
|
793
|
+
], "readonly");
|
|
794
|
+
const metadataStore = transaction.objectStore("payment_metadata");
|
|
795
|
+
if (!await this._hasRelatedPayments(metadataStore)) return {};
|
|
796
|
+
const parentIdSet = new Set(parentPaymentIds);
|
|
797
|
+
const paymentStore = transaction.objectStore("payments");
|
|
798
|
+
const lnurlReceiveMetadataStore = transaction.objectStore("lnurl_receive_metadata");
|
|
799
|
+
return new Promise((resolve, reject) => {
|
|
800
|
+
const result = {};
|
|
801
|
+
const fetchedMetadata = [];
|
|
802
|
+
const cursorRequest = metadataStore.openCursor();
|
|
803
|
+
cursorRequest.onsuccess = (event) => {
|
|
804
|
+
const cursor = event.target.result;
|
|
805
|
+
if (!cursor) {
|
|
806
|
+
if (fetchedMetadata.length === 0) {
|
|
807
|
+
resolve(result);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
let processed = 0;
|
|
811
|
+
for (const metadata of fetchedMetadata) {
|
|
812
|
+
const parentId = metadata.parentPaymentId;
|
|
813
|
+
const paymentRequest = paymentStore.get(metadata.paymentId);
|
|
814
|
+
paymentRequest.onsuccess = () => {
|
|
815
|
+
const payment = paymentRequest.result;
|
|
816
|
+
if (payment) {
|
|
817
|
+
const paymentWithMetadata = this._mergePaymentMetadata(payment, metadata);
|
|
818
|
+
if (!result[parentId]) result[parentId] = [];
|
|
819
|
+
this._fetchLnurlReceiveMetadata(paymentWithMetadata, lnurlReceiveMetadataStore).then((mergedPayment) => {
|
|
820
|
+
result[parentId].push(mergedPayment);
|
|
821
|
+
}).catch(() => {
|
|
822
|
+
result[parentId].push(paymentWithMetadata);
|
|
823
|
+
}).finally(() => {
|
|
824
|
+
processed++;
|
|
825
|
+
if (processed === fetchedMetadata.length) {
|
|
826
|
+
for (const parentId of Object.keys(result)) result[parentId].sort((a, b) => a.timestamp - b.timestamp);
|
|
827
|
+
resolve(result);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
} else {
|
|
831
|
+
processed++;
|
|
832
|
+
if (processed === fetchedMetadata.length) resolve(result);
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
paymentRequest.onerror = () => {
|
|
836
|
+
processed++;
|
|
837
|
+
if (processed === fetchedMetadata.length) resolve(result);
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const metadata = cursor.value;
|
|
843
|
+
if (metadata.parentPaymentId && parentIdSet.has(metadata.parentPaymentId)) fetchedMetadata.push(metadata);
|
|
844
|
+
cursor.continue();
|
|
845
|
+
};
|
|
846
|
+
cursorRequest.onerror = () => {
|
|
847
|
+
reject(new StorageError(`Failed to get payments by parent ids: ${cursorRequest.error?.message || "Unknown error"}`, cursorRequest.error));
|
|
848
|
+
};
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
async insertPaymentMetadata(paymentId, metadata) {
|
|
852
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
853
|
+
return new Promise((resolve, reject) => {
|
|
854
|
+
const store = this.db.transaction("payment_metadata", "readwrite").objectStore("payment_metadata");
|
|
855
|
+
const getRequest = store.get(paymentId);
|
|
856
|
+
getRequest.onsuccess = () => {
|
|
857
|
+
const existing = getRequest.result || {};
|
|
858
|
+
const metadataToStore = {
|
|
859
|
+
paymentId,
|
|
860
|
+
parentPaymentId: metadata.parentPaymentId ?? existing.parentPaymentId ?? null,
|
|
861
|
+
lnurlPayInfo: metadata.lnurlPayInfo ? JSON.stringify(metadata.lnurlPayInfo) : existing.lnurlPayInfo ?? null,
|
|
862
|
+
lnurlWithdrawInfo: metadata.lnurlWithdrawInfo ? JSON.stringify(metadata.lnurlWithdrawInfo) : existing.lnurlWithdrawInfo ?? null,
|
|
863
|
+
lnurlDescription: metadata.lnurlDescription ?? existing.lnurlDescription ?? null,
|
|
864
|
+
conversionInfo: metadata.conversionInfo ? JSON.stringify(metadata.conversionInfo) : existing.conversionInfo ?? null,
|
|
865
|
+
conversionStatus: metadata.conversionStatus ?? existing.conversionStatus ?? null
|
|
866
|
+
};
|
|
867
|
+
const putRequest = store.put(metadataToStore);
|
|
868
|
+
putRequest.onsuccess = () => resolve();
|
|
869
|
+
putRequest.onerror = () => {
|
|
870
|
+
reject(new StorageError(`Failed to set payment metadata for '${paymentId}': ${putRequest.error?.message || "Unknown error"}`, putRequest.error));
|
|
871
|
+
};
|
|
872
|
+
};
|
|
873
|
+
getRequest.onerror = () => {
|
|
874
|
+
reject(new StorageError(`Failed to get existing payment metadata for '${paymentId}': ${getRequest.error?.message || "Unknown error"}`, getRequest.error));
|
|
875
|
+
};
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
async addDeposit(txid, vout, amountSats, isMature) {
|
|
879
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
880
|
+
return new Promise((resolve, reject) => {
|
|
881
|
+
const store = this.db.transaction("unclaimed_deposits", "readwrite").objectStore("unclaimed_deposits");
|
|
882
|
+
const getRequest = store.get([txid, vout]);
|
|
883
|
+
getRequest.onsuccess = () => {
|
|
884
|
+
const existing = getRequest.result;
|
|
885
|
+
const depositToStore = {
|
|
886
|
+
txid,
|
|
887
|
+
vout,
|
|
888
|
+
amountSats,
|
|
889
|
+
isMature: isMature ?? true,
|
|
890
|
+
claimError: existing?.claimError ?? null,
|
|
891
|
+
refundTx: existing?.refundTx ?? null,
|
|
892
|
+
refundTxId: existing?.refundTxId ?? null,
|
|
893
|
+
instantClaimStatus: existing?.instantClaimStatus ?? null,
|
|
894
|
+
refundState: existing?.refundState ?? null,
|
|
895
|
+
maxClaimFee: existing?.maxClaimFee ?? null
|
|
896
|
+
};
|
|
897
|
+
const putRequest = store.put(depositToStore);
|
|
898
|
+
putRequest.onsuccess = () => resolve();
|
|
899
|
+
putRequest.onerror = () => {
|
|
900
|
+
reject(new StorageError(`Failed to add deposit '${txid}:${vout}': ${putRequest.error?.message || "Unknown error"}`, putRequest.error));
|
|
901
|
+
};
|
|
902
|
+
};
|
|
903
|
+
getRequest.onerror = () => {
|
|
904
|
+
reject(new StorageError(`Failed to add deposit '${txid}:${vout}': ${getRequest.error?.message || "Unknown error"}`, getRequest.error));
|
|
905
|
+
};
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
async deleteDeposit(txid, vout) {
|
|
909
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
910
|
+
return new Promise((resolve, reject) => {
|
|
911
|
+
const request = this.db.transaction("unclaimed_deposits", "readwrite").objectStore("unclaimed_deposits").delete([txid, vout]);
|
|
912
|
+
request.onsuccess = () => resolve();
|
|
913
|
+
request.onerror = () => {
|
|
914
|
+
reject(new StorageError(`Failed to delete deposit '${txid}:${vout}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
915
|
+
};
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
async listDeposits() {
|
|
919
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
920
|
+
return new Promise((resolve, reject) => {
|
|
921
|
+
const request = this.db.transaction("unclaimed_deposits", "readonly").objectStore("unclaimed_deposits").getAll();
|
|
922
|
+
request.onsuccess = () => {
|
|
923
|
+
resolve(request.result.map((row) => ({
|
|
924
|
+
txid: row.txid,
|
|
925
|
+
vout: row.vout,
|
|
926
|
+
amountSats: row.amountSats,
|
|
927
|
+
isMature: row.isMature ?? true,
|
|
928
|
+
claimError: row.claimError ? JSON.parse(row.claimError) : null,
|
|
929
|
+
refundTx: row.refundTx,
|
|
930
|
+
refundTxId: row.refundTxId,
|
|
931
|
+
instantClaimStatus: row.instantClaimStatus ? JSON.parse(row.instantClaimStatus) : null,
|
|
932
|
+
refundState: row.refundState ? JSON.parse(row.refundState) : null,
|
|
933
|
+
maxClaimFee: row.maxClaimFee ? JSON.parse(row.maxClaimFee) : null
|
|
934
|
+
})));
|
|
935
|
+
};
|
|
936
|
+
request.onerror = () => {
|
|
937
|
+
reject(new StorageError(`Failed to list deposits: ${request.error?.message || "Unknown error"}`, request.error));
|
|
938
|
+
};
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
async updateDeposit(txid, vout, payload) {
|
|
942
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
943
|
+
return new Promise((resolve, reject) => {
|
|
944
|
+
const store = this.db.transaction("unclaimed_deposits", "readwrite").objectStore("unclaimed_deposits");
|
|
945
|
+
const getRequest = store.get([txid, vout]);
|
|
946
|
+
getRequest.onsuccess = () => {
|
|
947
|
+
const existingDeposit = getRequest.result;
|
|
948
|
+
if (!existingDeposit) {
|
|
949
|
+
resolve();
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
let updatedDeposit = { ...existingDeposit };
|
|
953
|
+
if (payload.type === "claimError") updatedDeposit.claimError = JSON.stringify(payload.error);
|
|
954
|
+
else if (payload.type === "refund") {
|
|
955
|
+
updatedDeposit.refundTx = payload.refundTx;
|
|
956
|
+
updatedDeposit.refundTxId = payload.refundTxid;
|
|
957
|
+
updatedDeposit.refundState = JSON.stringify(payload.state);
|
|
958
|
+
updatedDeposit.claimError = null;
|
|
959
|
+
} else if (payload.type === "instantClaim") updatedDeposit.instantClaimStatus = JSON.stringify(payload.status);
|
|
960
|
+
else if (payload.type === "refundBroadcastState") {
|
|
961
|
+
if (existingDeposit.refundTxId === payload.refundTxid) updatedDeposit.refundState = JSON.stringify(payload.state);
|
|
962
|
+
} else if (payload.type === "maxClaimFee") updatedDeposit.maxClaimFee = payload.maxFee ? JSON.stringify(payload.maxFee) : null;
|
|
963
|
+
else {
|
|
964
|
+
reject(new StorageError(`Unknown payload type: ${payload.type}`));
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const putRequest = store.put(updatedDeposit);
|
|
968
|
+
putRequest.onsuccess = () => resolve();
|
|
969
|
+
putRequest.onerror = () => {
|
|
970
|
+
reject(new StorageError(`Failed to update deposit '${txid}:${vout}': ${putRequest.error?.message || "Unknown error"}`, putRequest.error));
|
|
971
|
+
};
|
|
972
|
+
};
|
|
973
|
+
getRequest.onerror = () => {
|
|
974
|
+
reject(new StorageError(`Failed to get deposit '${txid}:${vout}' for update: ${getRequest.error?.message || "Unknown error"}`, getRequest.error));
|
|
975
|
+
};
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
async listWatchedDepositAddresses() {
|
|
979
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
980
|
+
return new Promise((resolve, reject) => {
|
|
981
|
+
const request = this.db.transaction("watched_deposit_addresses", "readonly").objectStore("watched_deposit_addresses").getAll();
|
|
982
|
+
request.onsuccess = () => {
|
|
983
|
+
const watched = (request.result || []).map((entry) => ({
|
|
984
|
+
address: entry.address,
|
|
985
|
+
issuedAt: entry.issuedAt,
|
|
986
|
+
seen: Boolean(entry.seen)
|
|
987
|
+
}));
|
|
988
|
+
watched.sort((a, b) => Number(b.issuedAt) - Number(a.issuedAt));
|
|
989
|
+
resolve(watched);
|
|
990
|
+
};
|
|
991
|
+
request.onerror = () => reject(new StorageError(`Failed to list watched deposit addresses: ${request.error}`));
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
async updateWatchedDepositAddress(address, payload) {
|
|
995
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
996
|
+
return new Promise((resolve, reject) => {
|
|
997
|
+
const store = this.db.transaction("watched_deposit_addresses", "readwrite").objectStore("watched_deposit_addresses");
|
|
998
|
+
if (payload.type === "watch") {
|
|
999
|
+
const request = store.put({
|
|
1000
|
+
address,
|
|
1001
|
+
issuedAt: payload.issuedAt,
|
|
1002
|
+
seen: false
|
|
1003
|
+
});
|
|
1004
|
+
request.onsuccess = () => resolve();
|
|
1005
|
+
request.onerror = () => reject(new StorageError(`Failed to watch deposit address '${address}': ${request.error}`));
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (payload.type === "unwatch") {
|
|
1009
|
+
const getRequest = store.get(address);
|
|
1010
|
+
getRequest.onsuccess = () => {
|
|
1011
|
+
const existing = getRequest.result;
|
|
1012
|
+
if (!existing || String(existing.issuedAt) !== String(payload.issuedAt)) {
|
|
1013
|
+
resolve();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const request = store.delete(address);
|
|
1017
|
+
request.onsuccess = () => resolve();
|
|
1018
|
+
request.onerror = () => reject(new StorageError(`Failed to unwatch deposit address '${address}': ${request.error}`));
|
|
1019
|
+
};
|
|
1020
|
+
getRequest.onerror = () => reject(new StorageError(`Failed to read watched deposit address '${address}': ${getRequest.error}`));
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (payload.type === "seen") {
|
|
1024
|
+
const getRequest = store.get(address);
|
|
1025
|
+
getRequest.onsuccess = () => {
|
|
1026
|
+
const existing = getRequest.result;
|
|
1027
|
+
if (!existing) {
|
|
1028
|
+
resolve();
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
const request = store.put({
|
|
1032
|
+
...existing,
|
|
1033
|
+
seen: true
|
|
1034
|
+
});
|
|
1035
|
+
request.onsuccess = () => resolve();
|
|
1036
|
+
request.onerror = () => reject(new StorageError(`Failed to mark deposit address '${address}' seen: ${request.error}`));
|
|
1037
|
+
};
|
|
1038
|
+
getRequest.onerror = () => reject(new StorageError(`Failed to read watched deposit address '${address}': ${getRequest.error}`));
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
reject(new StorageError(`Unknown payload type: ${payload.type}`));
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
async setLnurlMetadata(metadata) {
|
|
1045
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1046
|
+
return new Promise((resolve, reject) => {
|
|
1047
|
+
const store = this.db.transaction("lnurl_receive_metadata", "readwrite").objectStore("lnurl_receive_metadata");
|
|
1048
|
+
let completed = 0;
|
|
1049
|
+
const total = metadata.length;
|
|
1050
|
+
if (total === 0) {
|
|
1051
|
+
resolve();
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
for (const item of metadata) {
|
|
1055
|
+
const request = store.put({
|
|
1056
|
+
paymentHash: item.paymentHash,
|
|
1057
|
+
nostrZapRequest: item.nostrZapRequest || null,
|
|
1058
|
+
nostrZapReceipt: item.nostrZapReceipt || null,
|
|
1059
|
+
senderComment: item.senderComment || null
|
|
1060
|
+
});
|
|
1061
|
+
request.onsuccess = () => {
|
|
1062
|
+
completed++;
|
|
1063
|
+
if (completed === total) resolve();
|
|
1064
|
+
};
|
|
1065
|
+
request.onerror = () => {
|
|
1066
|
+
reject(new StorageError(`Failed to add lnurl metadata for payment hash '${item.paymentHash}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
async syncAddOutgoingChange(record) {
|
|
1072
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1073
|
+
return new Promise((resolve, reject) => {
|
|
1074
|
+
const transaction = this.db.transaction(["sync_outgoing"], "readwrite");
|
|
1075
|
+
const outgoingStore = transaction.objectStore("sync_outgoing");
|
|
1076
|
+
const getAllOutgoingRequest = outgoingStore.getAll();
|
|
1077
|
+
getAllOutgoingRequest.onsuccess = () => {
|
|
1078
|
+
const records = getAllOutgoingRequest.result;
|
|
1079
|
+
let maxOutgoingRevision = BigInt(0);
|
|
1080
|
+
for (const storeRecord of records) {
|
|
1081
|
+
const rev = BigInt(storeRecord.record.localRevision ?? storeRecord.record.revision);
|
|
1082
|
+
if (rev > maxOutgoingRevision) maxOutgoingRevision = rev;
|
|
1083
|
+
}
|
|
1084
|
+
const nextRevision = maxOutgoingRevision + BigInt(1);
|
|
1085
|
+
const storeRecord = {
|
|
1086
|
+
type: record.id.type,
|
|
1087
|
+
dataId: record.id.dataId,
|
|
1088
|
+
revision: Number(nextRevision),
|
|
1089
|
+
record: {
|
|
1090
|
+
...record,
|
|
1091
|
+
localRevision: nextRevision
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
const addRequest = outgoingStore.add(storeRecord);
|
|
1095
|
+
addRequest.onsuccess = () => {
|
|
1096
|
+
transaction.oncomplete = () => {
|
|
1097
|
+
resolve(nextRevision);
|
|
1098
|
+
};
|
|
1099
|
+
};
|
|
1100
|
+
addRequest.onerror = (event) => {
|
|
1101
|
+
reject(new StorageError(`Failed to add outgoing change: ${event.target.error.message}`));
|
|
1102
|
+
};
|
|
1103
|
+
};
|
|
1104
|
+
getAllOutgoingRequest.onerror = (event) => {
|
|
1105
|
+
reject(new StorageError(`Failed to get outgoing records: ${event.target.error.message}`));
|
|
1106
|
+
};
|
|
1107
|
+
transaction.onerror = (event) => {
|
|
1108
|
+
reject(new StorageError(`Transaction failed: ${event.target.error.message}`));
|
|
1109
|
+
};
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
async syncCompleteOutgoingSync(record, localRevision) {
|
|
1113
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1114
|
+
return new Promise((resolve, reject) => {
|
|
1115
|
+
const transaction = this.db.transaction([
|
|
1116
|
+
"sync_outgoing",
|
|
1117
|
+
"sync_state",
|
|
1118
|
+
"sync_revision"
|
|
1119
|
+
], "readwrite");
|
|
1120
|
+
const outgoingStore = transaction.objectStore("sync_outgoing");
|
|
1121
|
+
const stateStore = transaction.objectStore("sync_state");
|
|
1122
|
+
const revisionStore = transaction.objectStore("sync_revision");
|
|
1123
|
+
const deleteRequest = outgoingStore.delete([
|
|
1124
|
+
record.id.type,
|
|
1125
|
+
record.id.dataId,
|
|
1126
|
+
Number(localRevision)
|
|
1127
|
+
]);
|
|
1128
|
+
deleteRequest.onsuccess = () => {
|
|
1129
|
+
const stateRecord = {
|
|
1130
|
+
type: record.id.type,
|
|
1131
|
+
dataId: record.id.dataId,
|
|
1132
|
+
record
|
|
1133
|
+
};
|
|
1134
|
+
stateStore.put(stateRecord);
|
|
1135
|
+
const getRevisionRequest = revisionStore.get(1);
|
|
1136
|
+
getRevisionRequest.onsuccess = () => {
|
|
1137
|
+
const current = getRevisionRequest.result || {
|
|
1138
|
+
id: 1,
|
|
1139
|
+
revision: "0"
|
|
1140
|
+
};
|
|
1141
|
+
const currentRevision = BigInt(current.revision);
|
|
1142
|
+
const recordRevision = BigInt(record.revision);
|
|
1143
|
+
if (recordRevision > currentRevision) revisionStore.put({
|
|
1144
|
+
id: 1,
|
|
1145
|
+
revision: recordRevision.toString()
|
|
1146
|
+
});
|
|
1147
|
+
resolve();
|
|
1148
|
+
};
|
|
1149
|
+
getRevisionRequest.onerror = (event) => {
|
|
1150
|
+
reject(new StorageError(`Failed to update sync revision: ${event.target.error.message}`));
|
|
1151
|
+
};
|
|
1152
|
+
};
|
|
1153
|
+
deleteRequest.onerror = (event) => {
|
|
1154
|
+
reject(new StorageError(`Failed to complete outgoing sync: ${event.target.error.message}`));
|
|
1155
|
+
};
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
async syncGetPendingOutgoingChanges(limit) {
|
|
1159
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1160
|
+
return new Promise((resolve, reject) => {
|
|
1161
|
+
const transaction = this.db.transaction(["sync_outgoing", "sync_state"], "readonly");
|
|
1162
|
+
const outgoingStore = transaction.objectStore("sync_outgoing");
|
|
1163
|
+
const stateStore = transaction.objectStore("sync_state");
|
|
1164
|
+
const request = outgoingStore.index("revision").openCursor(null, "next");
|
|
1165
|
+
const changes = [];
|
|
1166
|
+
let count = 0;
|
|
1167
|
+
request.onsuccess = (event) => {
|
|
1168
|
+
const cursor = event.target.result;
|
|
1169
|
+
if (cursor && count < limit) {
|
|
1170
|
+
const storeRecord = cursor.value;
|
|
1171
|
+
const change = {
|
|
1172
|
+
...storeRecord.record,
|
|
1173
|
+
localRevision: storeRecord.record.localRevision ?? storeRecord.record.revision
|
|
1174
|
+
};
|
|
1175
|
+
const stateRequest = stateStore.get([storeRecord.type, storeRecord.dataId]);
|
|
1176
|
+
stateRequest.onsuccess = () => {
|
|
1177
|
+
const stateRecord = stateRequest.result;
|
|
1178
|
+
const parent = stateRecord ? stateRecord.record : null;
|
|
1179
|
+
changes.push({
|
|
1180
|
+
change,
|
|
1181
|
+
parent
|
|
1182
|
+
});
|
|
1183
|
+
count++;
|
|
1184
|
+
cursor.continue();
|
|
1185
|
+
};
|
|
1186
|
+
stateRequest.onerror = () => {
|
|
1187
|
+
changes.push({
|
|
1188
|
+
change,
|
|
1189
|
+
parent: null
|
|
1190
|
+
});
|
|
1191
|
+
count++;
|
|
1192
|
+
cursor.continue();
|
|
1193
|
+
};
|
|
1194
|
+
} else resolve(changes);
|
|
1195
|
+
};
|
|
1196
|
+
request.onerror = (event) => {
|
|
1197
|
+
reject(new StorageError(`Failed to get pending outgoing changes: ${event.target.error.message}`));
|
|
1198
|
+
};
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
async syncGetLastRevision() {
|
|
1202
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1203
|
+
return new Promise((resolve, reject) => {
|
|
1204
|
+
const request = this.db.transaction("sync_revision", "readonly").objectStore("sync_revision").get(1);
|
|
1205
|
+
request.onsuccess = () => {
|
|
1206
|
+
const result = request.result || {
|
|
1207
|
+
id: 1,
|
|
1208
|
+
revision: "0"
|
|
1209
|
+
};
|
|
1210
|
+
resolve(BigInt(result.revision));
|
|
1211
|
+
};
|
|
1212
|
+
request.onerror = (event) => {
|
|
1213
|
+
reject(new StorageError(`Failed to get last revision: ${event.target.error.message}`));
|
|
1214
|
+
};
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
async syncInsertIncomingRecords(records) {
|
|
1218
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1219
|
+
return new Promise((resolve, reject) => {
|
|
1220
|
+
const store = this.db.transaction(["sync_incoming"], "readwrite").objectStore("sync_incoming");
|
|
1221
|
+
let recordsProcessed = 0;
|
|
1222
|
+
for (const record of records) {
|
|
1223
|
+
const storeRecord = {
|
|
1224
|
+
type: record.id.type,
|
|
1225
|
+
dataId: record.id.dataId,
|
|
1226
|
+
revision: Number(record.revision),
|
|
1227
|
+
record
|
|
1228
|
+
};
|
|
1229
|
+
const request = store.put(storeRecord);
|
|
1230
|
+
request.onsuccess = () => {
|
|
1231
|
+
recordsProcessed++;
|
|
1232
|
+
if (recordsProcessed === records.length) resolve();
|
|
1233
|
+
};
|
|
1234
|
+
request.onerror = (event) => {
|
|
1235
|
+
reject(new StorageError(`Failed to insert incoming record: ${event.target.error.message}`));
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
if (records.length === 0) resolve();
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
async syncDeleteIncomingRecord(record) {
|
|
1242
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1243
|
+
return new Promise((resolve, reject) => {
|
|
1244
|
+
const store = this.db.transaction(["sync_incoming"], "readwrite").objectStore("sync_incoming");
|
|
1245
|
+
const key = [
|
|
1246
|
+
record.id.type,
|
|
1247
|
+
record.id.dataId,
|
|
1248
|
+
Number(record.revision)
|
|
1249
|
+
];
|
|
1250
|
+
const request = store.delete(key);
|
|
1251
|
+
request.onsuccess = () => {
|
|
1252
|
+
resolve();
|
|
1253
|
+
};
|
|
1254
|
+
request.onerror = (event) => {
|
|
1255
|
+
reject(new StorageError(`Failed to delete incoming record: ${event.target.error.message}`));
|
|
1256
|
+
};
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
async syncGetIncomingRecords(limit) {
|
|
1260
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1261
|
+
return new Promise((resolve, reject) => {
|
|
1262
|
+
const transaction = this.db.transaction(["sync_incoming", "sync_state"], "readonly");
|
|
1263
|
+
const incomingStore = transaction.objectStore("sync_incoming");
|
|
1264
|
+
const stateStore = transaction.objectStore("sync_state");
|
|
1265
|
+
const request = incomingStore.index("revision").openCursor(null, "next");
|
|
1266
|
+
const records = [];
|
|
1267
|
+
let count = 0;
|
|
1268
|
+
request.onsuccess = (event) => {
|
|
1269
|
+
const cursor = event.target.result;
|
|
1270
|
+
if (cursor && count < limit) {
|
|
1271
|
+
const storeRecord = cursor.value;
|
|
1272
|
+
const newState = storeRecord.record;
|
|
1273
|
+
const stateRequest = stateStore.get([storeRecord.type, storeRecord.dataId]);
|
|
1274
|
+
stateRequest.onsuccess = () => {
|
|
1275
|
+
const stateRecord = stateRequest.result;
|
|
1276
|
+
const oldState = stateRecord ? stateRecord.record : null;
|
|
1277
|
+
records.push({
|
|
1278
|
+
newState,
|
|
1279
|
+
oldState
|
|
1280
|
+
});
|
|
1281
|
+
count++;
|
|
1282
|
+
cursor.continue();
|
|
1283
|
+
};
|
|
1284
|
+
stateRequest.onerror = () => {
|
|
1285
|
+
records.push({
|
|
1286
|
+
newState,
|
|
1287
|
+
oldState: null
|
|
1288
|
+
});
|
|
1289
|
+
count++;
|
|
1290
|
+
cursor.continue();
|
|
1291
|
+
};
|
|
1292
|
+
} else resolve(records);
|
|
1293
|
+
};
|
|
1294
|
+
request.onerror = (event) => {
|
|
1295
|
+
reject(new StorageError(`Failed to get incoming records: ${event.target.error.message}`));
|
|
1296
|
+
};
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
async syncGetLatestOutgoingChange() {
|
|
1300
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1301
|
+
return new Promise((resolve, reject) => {
|
|
1302
|
+
const transaction = this.db.transaction(["sync_outgoing", "sync_state"], "readonly");
|
|
1303
|
+
const outgoingStore = transaction.objectStore("sync_outgoing");
|
|
1304
|
+
const stateStore = transaction.objectStore("sync_state");
|
|
1305
|
+
const request = outgoingStore.index("revision").openCursor(null, "prev");
|
|
1306
|
+
request.onsuccess = (event) => {
|
|
1307
|
+
const cursor = event.target.result;
|
|
1308
|
+
if (cursor) {
|
|
1309
|
+
const storeRecord = cursor.value;
|
|
1310
|
+
const change = {
|
|
1311
|
+
...storeRecord.record,
|
|
1312
|
+
localRevision: storeRecord.record.localRevision ?? storeRecord.record.revision
|
|
1313
|
+
};
|
|
1314
|
+
const stateRequest = stateStore.get([storeRecord.type, storeRecord.dataId]);
|
|
1315
|
+
stateRequest.onsuccess = () => {
|
|
1316
|
+
const stateRecord = stateRequest.result;
|
|
1317
|
+
const parent = stateRecord ? stateRecord.record : null;
|
|
1318
|
+
resolve({
|
|
1319
|
+
change,
|
|
1320
|
+
parent
|
|
1321
|
+
});
|
|
1322
|
+
};
|
|
1323
|
+
stateRequest.onerror = () => {
|
|
1324
|
+
resolve({
|
|
1325
|
+
change,
|
|
1326
|
+
parent: null
|
|
1327
|
+
});
|
|
1328
|
+
};
|
|
1329
|
+
} else resolve(null);
|
|
1330
|
+
};
|
|
1331
|
+
request.onerror = (event) => {
|
|
1332
|
+
reject(new StorageError(`Failed to get latest outgoing change: ${event.target.error.message}`));
|
|
1333
|
+
};
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
async syncUpdateRecordFromIncoming(record) {
|
|
1337
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1338
|
+
return new Promise((resolve, reject) => {
|
|
1339
|
+
const transaction = this.db.transaction(["sync_state", "sync_revision"], "readwrite");
|
|
1340
|
+
const stateStore = transaction.objectStore("sync_state");
|
|
1341
|
+
const revisionStore = transaction.objectStore("sync_revision");
|
|
1342
|
+
const storeRecord = {
|
|
1343
|
+
type: record.id.type,
|
|
1344
|
+
dataId: record.id.dataId,
|
|
1345
|
+
record
|
|
1346
|
+
};
|
|
1347
|
+
const request = stateStore.put(storeRecord);
|
|
1348
|
+
request.onsuccess = () => {
|
|
1349
|
+
const getRevisionRequest = revisionStore.get(1);
|
|
1350
|
+
getRevisionRequest.onsuccess = () => {
|
|
1351
|
+
const current = getRevisionRequest.result || {
|
|
1352
|
+
id: 1,
|
|
1353
|
+
revision: "0"
|
|
1354
|
+
};
|
|
1355
|
+
const currentRevision = BigInt(current.revision);
|
|
1356
|
+
const incomingRevision = BigInt(record.revision);
|
|
1357
|
+
if (incomingRevision > currentRevision) revisionStore.put({
|
|
1358
|
+
id: 1,
|
|
1359
|
+
revision: incomingRevision.toString()
|
|
1360
|
+
});
|
|
1361
|
+
resolve();
|
|
1362
|
+
};
|
|
1363
|
+
getRevisionRequest.onerror = (event) => {
|
|
1364
|
+
reject(new StorageError(`Failed to update sync revision: ${event.target.error.message}`));
|
|
1365
|
+
};
|
|
1366
|
+
};
|
|
1367
|
+
request.onerror = (event) => {
|
|
1368
|
+
reject(new StorageError(`Failed to update record from incoming: ${event.target.error.message}`));
|
|
1369
|
+
};
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
async listContacts(request) {
|
|
1373
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1374
|
+
const actualOffset = request.offset !== null && request.offset !== void 0 ? request.offset : 0;
|
|
1375
|
+
const actualLimit = request.limit !== null && request.limit !== void 0 ? request.limit : 4294967295;
|
|
1376
|
+
return new Promise((resolve, reject) => {
|
|
1377
|
+
const nameIndex = this.db.transaction("contacts", "readonly").objectStore("contacts").index("name");
|
|
1378
|
+
const contacts = [];
|
|
1379
|
+
let count = 0;
|
|
1380
|
+
let skipped = 0;
|
|
1381
|
+
const cursorRequest = nameIndex.openCursor();
|
|
1382
|
+
cursorRequest.onsuccess = (event) => {
|
|
1383
|
+
const cursor = event.target.result;
|
|
1384
|
+
if (!cursor || count >= actualLimit) {
|
|
1385
|
+
resolve(contacts);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (skipped < actualOffset) {
|
|
1389
|
+
skipped++;
|
|
1390
|
+
cursor.continue();
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
contacts.push(cursor.value);
|
|
1394
|
+
count++;
|
|
1395
|
+
cursor.continue();
|
|
1396
|
+
};
|
|
1397
|
+
cursorRequest.onerror = () => {
|
|
1398
|
+
reject(new StorageError(`Failed to list contacts: ${cursorRequest.error?.message || "Unknown error"}`, cursorRequest.error));
|
|
1399
|
+
};
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
async getContact(id) {
|
|
1403
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1404
|
+
return new Promise((resolve, reject) => {
|
|
1405
|
+
const request = this.db.transaction("contacts", "readonly").objectStore("contacts").get(id);
|
|
1406
|
+
request.onsuccess = () => {
|
|
1407
|
+
resolve(request.result || null);
|
|
1408
|
+
};
|
|
1409
|
+
request.onerror = () => {
|
|
1410
|
+
reject(new StorageError(`Failed to get contact '${id}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
1411
|
+
};
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
async insertContact(contact) {
|
|
1415
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1416
|
+
return new Promise((resolve, reject) => {
|
|
1417
|
+
const store = this.db.transaction("contacts", "readwrite").objectStore("contacts");
|
|
1418
|
+
const getRequest = store.get(contact.id);
|
|
1419
|
+
getRequest.onsuccess = () => {
|
|
1420
|
+
const existingById = getRequest.result;
|
|
1421
|
+
const toStore = existingById ? {
|
|
1422
|
+
...contact,
|
|
1423
|
+
createdAt: existingById.createdAt
|
|
1424
|
+
} : contact;
|
|
1425
|
+
const putRequest = store.put(toStore);
|
|
1426
|
+
putRequest.onsuccess = () => resolve();
|
|
1427
|
+
putRequest.onerror = () => {
|
|
1428
|
+
reject(new StorageError(`Inserting contact failed: ${putRequest.error?.name || "Unknown error"} - ${putRequest.error?.message || ""}`, putRequest.error));
|
|
1429
|
+
};
|
|
1430
|
+
};
|
|
1431
|
+
getRequest.onerror = () => {
|
|
1432
|
+
reject(new StorageError(`Failed to check existing contact: ${getRequest.error?.message || "Unknown error"}`, getRequest.error));
|
|
1433
|
+
};
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
async deleteContact(id) {
|
|
1437
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1438
|
+
return new Promise((resolve, reject) => {
|
|
1439
|
+
const request = this.db.transaction("contacts", "readwrite").objectStore("contacts").delete(id);
|
|
1440
|
+
request.onsuccess = () => resolve();
|
|
1441
|
+
request.onerror = () => {
|
|
1442
|
+
reject(new StorageError(`Failed to delete contact '${id}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
1443
|
+
};
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
async setCrossChainSwap(swap) {
|
|
1447
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1448
|
+
return new Promise((resolve, reject) => {
|
|
1449
|
+
const request = this.db.transaction("cross_chain_swaps", "readwrite").objectStore("cross_chain_swaps").put(swap);
|
|
1450
|
+
request.onsuccess = () => resolve();
|
|
1451
|
+
request.onerror = () => {
|
|
1452
|
+
reject(new StorageError(`Failed to set cross-chain swap '${swap.provider}:${swap.id}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
1453
|
+
};
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
async getCrossChainSwap(provider, id) {
|
|
1457
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1458
|
+
return new Promise((resolve, reject) => {
|
|
1459
|
+
const request = this.db.transaction("cross_chain_swaps", "readonly").objectStore("cross_chain_swaps").get([provider, id]);
|
|
1460
|
+
request.onsuccess = () => resolve(request.result || null);
|
|
1461
|
+
request.onerror = () => {
|
|
1462
|
+
reject(new StorageError(`Failed to get cross-chain swap '${provider}:${id}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
1463
|
+
};
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
async listActiveCrossChainSwaps(provider) {
|
|
1467
|
+
if (!this.db) throw new StorageError("Database not initialized");
|
|
1468
|
+
return new Promise((resolve, reject) => {
|
|
1469
|
+
const request = this.db.transaction("cross_chain_swaps", "readonly").objectStore("cross_chain_swaps").getAll();
|
|
1470
|
+
request.onsuccess = () => {
|
|
1471
|
+
resolve((request.result || []).filter((swap) => swap.provider === provider && !swap.isTerminal));
|
|
1472
|
+
};
|
|
1473
|
+
request.onerror = () => {
|
|
1474
|
+
reject(new StorageError(`Failed to list active cross-chain swaps for '${provider}': ${request.error?.message || "Unknown error"}`, request.error));
|
|
1475
|
+
};
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
_paymentToStore(payment) {
|
|
1479
|
+
return {
|
|
1480
|
+
...payment,
|
|
1481
|
+
details: payment.details ? JSON.stringify(payment.details) : null,
|
|
1482
|
+
method: payment.method ? JSON.stringify(payment.method) : null
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
_normalizePaymentStatus(status) {
|
|
1486
|
+
return typeof status === "string" ? status.toLowerCase() : status;
|
|
1487
|
+
}
|
|
1488
|
+
_isFinalPaymentStatus(status) {
|
|
1489
|
+
const normalized = this._normalizePaymentStatus(status);
|
|
1490
|
+
return normalized === "completed" || normalized === "failed";
|
|
1491
|
+
}
|
|
1492
|
+
_matchesFilters(payment, request) {
|
|
1493
|
+
if (request.typeFilter && request.typeFilter.length > 0) {
|
|
1494
|
+
if (!request.typeFilter.includes(payment.paymentType)) return false;
|
|
1495
|
+
}
|
|
1496
|
+
if (request.statusFilter && request.statusFilter.length > 0) {
|
|
1497
|
+
if (!request.statusFilter.includes(payment.status)) return false;
|
|
1498
|
+
}
|
|
1499
|
+
if (request.fromTimestamp !== null && request.fromTimestamp !== void 0) {
|
|
1500
|
+
if (payment.timestamp < request.fromTimestamp) return false;
|
|
1501
|
+
}
|
|
1502
|
+
if (request.toTimestamp !== null && request.toTimestamp !== void 0) {
|
|
1503
|
+
if (payment.timestamp >= request.toTimestamp) return false;
|
|
1504
|
+
}
|
|
1505
|
+
if (request.paymentDetailsFilter && request.paymentDetailsFilter.length > 0) {
|
|
1506
|
+
let details = null;
|
|
1507
|
+
if (payment.details && typeof payment.details === "string") try {
|
|
1508
|
+
details = JSON.parse(payment.details);
|
|
1509
|
+
} catch (e) {
|
|
1510
|
+
details = null;
|
|
1511
|
+
}
|
|
1512
|
+
else details = payment.details;
|
|
1513
|
+
if (!details) return false;
|
|
1514
|
+
let paymentDetailsFilterMatches = false;
|
|
1515
|
+
for (const paymentDetailsFilter of request.paymentDetailsFilter) {
|
|
1516
|
+
if (details.type !== paymentDetailsFilter.type) continue;
|
|
1517
|
+
if ((paymentDetailsFilter.type === "spark" || paymentDetailsFilter.type === "lightning") && paymentDetailsFilter.htlcStatus != null && paymentDetailsFilter.htlcStatus.length > 0) {
|
|
1518
|
+
if (details.type !== paymentDetailsFilter.type || !details.htlcDetails || !paymentDetailsFilter.htlcStatus.includes(details.htlcDetails.status)) continue;
|
|
1519
|
+
}
|
|
1520
|
+
if ((paymentDetailsFilter.type === "spark" || paymentDetailsFilter.type === "token" || paymentDetailsFilter.type === "lightning") && paymentDetailsFilter.conversionFilter != null) {
|
|
1521
|
+
if (details.type !== paymentDetailsFilter.type || !details.conversionInfo) continue;
|
|
1522
|
+
const ci = details.conversionInfo;
|
|
1523
|
+
if (paymentDetailsFilter.conversionFilter === "ammRefundNeeded") {
|
|
1524
|
+
if (ci.type !== "amm" || ci.status !== "refundNeeded") continue;
|
|
1525
|
+
} else if (paymentDetailsFilter.conversionFilter === "orchestraPending") {
|
|
1526
|
+
if (ci.type !== "orchestra" || [
|
|
1527
|
+
"completed",
|
|
1528
|
+
"failed",
|
|
1529
|
+
"refunded"
|
|
1530
|
+
].includes(ci.status)) continue;
|
|
1531
|
+
} else if (paymentDetailsFilter.conversionFilter === "boltzPending") {
|
|
1532
|
+
if (ci.type !== "boltz" || [
|
|
1533
|
+
"completed",
|
|
1534
|
+
"failed",
|
|
1535
|
+
"refunded"
|
|
1536
|
+
].includes(ci.status)) continue;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (paymentDetailsFilter.type === "token" && paymentDetailsFilter.txHash != null) {
|
|
1540
|
+
if (details.type !== "token" || details.txHash !== paymentDetailsFilter.txHash) continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (paymentDetailsFilter.type === "token" && paymentDetailsFilter.txType != null) {
|
|
1543
|
+
if (details.type !== "token" || details.txType !== paymentDetailsFilter.txType) continue;
|
|
1544
|
+
}
|
|
1545
|
+
paymentDetailsFilterMatches = true;
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
if (!paymentDetailsFilterMatches) return false;
|
|
1549
|
+
}
|
|
1550
|
+
if (request.assetFilter) {
|
|
1551
|
+
const assetFilter = request.assetFilter;
|
|
1552
|
+
let details = null;
|
|
1553
|
+
if (payment.details && typeof payment.details === "string") try {
|
|
1554
|
+
details = JSON.parse(payment.details);
|
|
1555
|
+
} catch (e) {
|
|
1556
|
+
details = null;
|
|
1557
|
+
}
|
|
1558
|
+
else details = payment.details;
|
|
1559
|
+
if (!details) return false;
|
|
1560
|
+
if (assetFilter.type === "bitcoin" && details.type === "token") return false;
|
|
1561
|
+
if (assetFilter.type === "token") {
|
|
1562
|
+
if (details.type !== "token") return false;
|
|
1563
|
+
if (assetFilter.tokenIdentifier) {
|
|
1564
|
+
if (!details.metadata || details.metadata.identifier !== assetFilter.tokenIdentifier) return false;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
_mergePaymentMetadata(payment, metadata) {
|
|
1571
|
+
let details = null;
|
|
1572
|
+
if (payment.details) try {
|
|
1573
|
+
details = JSON.parse(payment.details);
|
|
1574
|
+
} catch (e) {
|
|
1575
|
+
throw new StorageError(`Failed to parse payment details JSON for payment ${payment.id}: ${e.message}`, e);
|
|
1576
|
+
}
|
|
1577
|
+
let method = null;
|
|
1578
|
+
if (payment.method) try {
|
|
1579
|
+
method = JSON.parse(payment.method);
|
|
1580
|
+
} catch (e) {
|
|
1581
|
+
throw new StorageError(`Failed to parse payment method JSON for payment ${payment.id}: ${e.message}`, e);
|
|
1582
|
+
}
|
|
1583
|
+
if (details && details.type === "lightning" && !details.htlcDetails) throw new StorageError(`htlc_details is required for Lightning payment ${payment.id}`);
|
|
1584
|
+
if (metadata && details) {
|
|
1585
|
+
if (details.type == "lightning") {
|
|
1586
|
+
if (metadata.lnurlDescription && !details.description) details.description = metadata.lnurlDescription;
|
|
1587
|
+
if (metadata.lnurlPayInfo) try {
|
|
1588
|
+
details.lnurlPayInfo = JSON.parse(metadata.lnurlPayInfo);
|
|
1589
|
+
} catch (e) {
|
|
1590
|
+
throw new StorageError(`Failed to parse lnurlPayInfo JSON for payment ${payment.id}: ${e.message}`, e);
|
|
1591
|
+
}
|
|
1592
|
+
if (metadata.lnurlWithdrawInfo) try {
|
|
1593
|
+
details.lnurlWithdrawInfo = JSON.parse(metadata.lnurlWithdrawInfo);
|
|
1594
|
+
} catch (e) {
|
|
1595
|
+
throw new StorageError(`Failed to parse lnurlWithdrawInfo JSON for payment ${payment.id}: ${e.message}`, e);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if ((details.type == "lightning" || details.type == "spark" || details.type == "token") && metadata.conversionInfo) try {
|
|
1599
|
+
details.conversionInfo = JSON.parse(metadata.conversionInfo);
|
|
1600
|
+
} catch (e) {
|
|
1601
|
+
throw new StorageError(`Failed to parse conversionInfo JSON for payment ${payment.id}: ${e.message}`, e);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return {
|
|
1605
|
+
id: payment.id,
|
|
1606
|
+
paymentType: payment.paymentType,
|
|
1607
|
+
status: payment.status,
|
|
1608
|
+
amount: payment.amount,
|
|
1609
|
+
fees: payment.fees,
|
|
1610
|
+
timestamp: payment.timestamp,
|
|
1611
|
+
method,
|
|
1612
|
+
details,
|
|
1613
|
+
conversionDetails: metadata?.conversionStatus ? {
|
|
1614
|
+
status: metadata.conversionStatus,
|
|
1615
|
+
from: null,
|
|
1616
|
+
to: null
|
|
1617
|
+
} : null
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
_fetchLnurlReceiveMetadata(payment, lnurlReceiveMetadataStore) {
|
|
1621
|
+
if (!payment.details || payment.details.type !== "lightning" || !payment.details.htlcDetails?.paymentHash) return Promise.resolve(payment);
|
|
1622
|
+
if (!lnurlReceiveMetadataStore) return Promise.resolve(payment);
|
|
1623
|
+
return new Promise((resolve, reject) => {
|
|
1624
|
+
const lnurlReceiveRequest = lnurlReceiveMetadataStore.get(payment.details.htlcDetails.paymentHash);
|
|
1625
|
+
lnurlReceiveRequest.onsuccess = () => {
|
|
1626
|
+
const lnurlReceiveMetadata = lnurlReceiveRequest.result;
|
|
1627
|
+
if (lnurlReceiveMetadata) payment.details.lnurlReceiveMetadata = {
|
|
1628
|
+
nostrZapRequest: lnurlReceiveMetadata.nostrZapRequest || null,
|
|
1629
|
+
nostrZapReceipt: lnurlReceiveMetadata.nostrZapReceipt || null,
|
|
1630
|
+
senderComment: lnurlReceiveMetadata.senderComment || null
|
|
1631
|
+
};
|
|
1632
|
+
resolve(payment);
|
|
1633
|
+
};
|
|
1634
|
+
lnurlReceiveRequest.onerror = () => {
|
|
1635
|
+
reject(/* @__PURE__ */ new Error("Failed to fetch lnurl receive metadata"));
|
|
1636
|
+
};
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
async function createDefaultStorage(dbName = "BreezSdkSpark", logger = null) {
|
|
1641
|
+
const storage = new IndexedDBStorage(dbName, logger);
|
|
1642
|
+
await storage.initialize();
|
|
1643
|
+
return storage;
|
|
1644
|
+
}
|
|
1645
|
+
//#endregion
|
|
1646
|
+
export { createDefaultStorage };
|
|
1647
|
+
|
|
1648
|
+
//# sourceMappingURL=storage-Bya5b2Xt.js.map
|