@breeztech/breez-sdk-spark 0.15.1 → 0.16.1-dev1
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/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +511 -215
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +567 -414
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/bundler/storage/index.js +205 -15
- package/deno/breez_sdk_spark_wasm.d.ts +511 -215
- package/deno/breez_sdk_spark_wasm.js +567 -414
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/nodejs/breez_sdk_spark_wasm.d.ts +511 -215
- package/nodejs/breez_sdk_spark_wasm.js +578 -421
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/nodejs/index.js +10 -10
- package/nodejs/index.mjs +12 -8
- package/nodejs/mysql-session-store/errors.cjs +13 -0
- package/nodejs/{mysql-session-manager → mysql-session-store}/index.cjs +24 -21
- package/nodejs/{mysql-session-manager → mysql-session-store}/migrations.cjs +17 -11
- package/nodejs/mysql-session-store/package.json +9 -0
- package/nodejs/mysql-storage/index.cjs +229 -111
- package/nodejs/mysql-storage/migrations.cjs +37 -2
- package/nodejs/mysql-token-store/index.cjs +99 -79
- package/nodejs/mysql-token-store/migrations.cjs +59 -2
- package/nodejs/mysql-tree-store/index.cjs +15 -9
- package/nodejs/mysql-tree-store/migrations.cjs +16 -2
- package/nodejs/package.json +2 -2
- package/nodejs/postgres-session-store/errors.cjs +13 -0
- package/nodejs/{postgres-session-manager → postgres-session-store}/index.cjs +23 -23
- package/nodejs/{postgres-session-manager → postgres-session-store}/migrations.cjs +14 -14
- package/nodejs/postgres-session-store/package.json +9 -0
- package/nodejs/postgres-storage/index.cjs +174 -107
- package/nodejs/postgres-storage/migrations.cjs +24 -0
- package/nodejs/postgres-token-store/index.cjs +89 -64
- package/nodejs/postgres-token-store/migrations.cjs +44 -0
- package/nodejs/storage/index.cjs +167 -113
- package/nodejs/storage/migrations.cjs +23 -0
- package/package.json +6 -1
- package/ssr/index.js +52 -28
- package/web/breez_sdk_spark_wasm.d.ts +566 -261
- package/web/breez_sdk_spark_wasm.js +567 -414
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/web/passkey-prf-provider/index.d.ts +203 -0
- package/web/passkey-prf-provider/index.js +733 -0
- package/web/storage/index.js +205 -15
- package/nodejs/mysql-session-manager/errors.cjs +0 -13
- package/nodejs/mysql-session-manager/package.json +0 -9
- package/nodejs/postgres-session-manager/errors.cjs +0 -13
- package/nodejs/postgres-session-manager/package.json +0 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CommonJS implementation for Node.js PostgreSQL Session
|
|
2
|
+
* CommonJS implementation for Node.js PostgreSQL Session Store.
|
|
3
3
|
*
|
|
4
|
-
* Implements the JS-side `
|
|
4
|
+
* Implements the JS-side `SessionStore` interface consumed by the Breez
|
|
5
5
|
* SDK WASM bindings: `getSession(serviceIdentityKey)` returns the cached
|
|
6
6
|
* session for the (tenant, service) pair or `null` when not found, and
|
|
7
7
|
* `setSession(serviceIdentityKey, session)` upserts a session.
|
|
@@ -29,10 +29,10 @@ try {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const {
|
|
33
|
-
const {
|
|
32
|
+
const { SessionStoreError } = require("./errors.cjs");
|
|
33
|
+
const { SessionStoreMigrationManager } = require("./migrations.cjs");
|
|
34
34
|
|
|
35
|
-
class
|
|
35
|
+
class PostgresSessionStore {
|
|
36
36
|
/**
|
|
37
37
|
* @param {import('pg').Pool} pool
|
|
38
38
|
* @param {Buffer|Uint8Array} identity - 33-byte secp256k1 compressed pubkey
|
|
@@ -41,7 +41,7 @@ class PostgresSessionManager {
|
|
|
41
41
|
*/
|
|
42
42
|
constructor(pool, identity, logger = null, runMigration = true) {
|
|
43
43
|
if (!identity || identity.length !== 33) {
|
|
44
|
-
throw new
|
|
44
|
+
throw new SessionStoreError(
|
|
45
45
|
"tenant identity (33-byte secp256k1 pubkey) is required"
|
|
46
46
|
);
|
|
47
47
|
}
|
|
@@ -54,13 +54,13 @@ class PostgresSessionManager {
|
|
|
54
54
|
async initialize() {
|
|
55
55
|
try {
|
|
56
56
|
if (this.runMigration) {
|
|
57
|
-
const migrationManager = new
|
|
57
|
+
const migrationManager = new SessionStoreMigrationManager(this.logger);
|
|
58
58
|
await migrationManager.migrate(this.pool);
|
|
59
59
|
}
|
|
60
60
|
return this;
|
|
61
61
|
} catch (error) {
|
|
62
|
-
throw new
|
|
63
|
-
`Failed to initialize PostgreSQL session
|
|
62
|
+
throw new SessionStoreError(
|
|
63
|
+
`Failed to initialize PostgreSQL session store: ${error.message}`,
|
|
64
64
|
error
|
|
65
65
|
);
|
|
66
66
|
}
|
|
@@ -76,7 +76,7 @@ class PostgresSessionManager {
|
|
|
76
76
|
/**
|
|
77
77
|
* Returns the cached session for the given service identity key, or `null`
|
|
78
78
|
* if no session is cached. The Rust adapter maps `null` to
|
|
79
|
-
* `
|
|
79
|
+
* `SessionStoreError::NotFound`.
|
|
80
80
|
* @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
|
|
81
81
|
* @returns {Promise<{token: string, expiration: number} | null>}
|
|
82
82
|
*/
|
|
@@ -97,7 +97,7 @@ class PostgresSessionManager {
|
|
|
97
97
|
expiration: Number(row.expiration),
|
|
98
98
|
};
|
|
99
99
|
} catch (error) {
|
|
100
|
-
throw new
|
|
100
|
+
throw new SessionStoreError(
|
|
101
101
|
`Failed to read session: ${error.message}`,
|
|
102
102
|
error
|
|
103
103
|
);
|
|
@@ -120,7 +120,7 @@ class PostgresSessionManager {
|
|
|
120
120
|
[this.identity, serviceKey, session.token, session.expiration]
|
|
121
121
|
);
|
|
122
122
|
} catch (error) {
|
|
123
|
-
throw new
|
|
123
|
+
throw new SessionStoreError(
|
|
124
124
|
`Failed to write session: ${error.message}`,
|
|
125
125
|
error
|
|
126
126
|
);
|
|
@@ -130,7 +130,7 @@ class PostgresSessionManager {
|
|
|
130
130
|
|
|
131
131
|
function _decodePubkey(hex) {
|
|
132
132
|
if (typeof hex !== "string" || hex.length !== 66) {
|
|
133
|
-
throw new
|
|
133
|
+
throw new SessionStoreError(
|
|
134
134
|
"service_identity_key must be a 66-character hex-encoded 33-byte pubkey"
|
|
135
135
|
);
|
|
136
136
|
}
|
|
@@ -139,13 +139,13 @@ function _decodePubkey(hex) {
|
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* Convenience factory: creates a pool from a Pool config and returns an
|
|
142
|
-
* initialized `
|
|
143
|
-
* `
|
|
142
|
+
* initialized `PostgresSessionStore`. Most callers should use
|
|
143
|
+
* `createPostgresSessionStoreWithPool` instead so the pool can be shared
|
|
144
144
|
* across stores.
|
|
145
145
|
*/
|
|
146
|
-
async function
|
|
146
|
+
async function createPostgresSessionStore(poolConfig, identity, logger = null) {
|
|
147
147
|
const pool = new pg.Pool(poolConfig);
|
|
148
|
-
const manager = new
|
|
148
|
+
const manager = new PostgresSessionStore(
|
|
149
149
|
pool,
|
|
150
150
|
identity,
|
|
151
151
|
logger,
|
|
@@ -159,13 +159,13 @@ async function createPostgresSessionManager(poolConfig, identity, logger = null)
|
|
|
159
159
|
* Wraps an existing pool — useful when sharing the pool with the storage,
|
|
160
160
|
* tree store, and token store implementations.
|
|
161
161
|
*/
|
|
162
|
-
async function
|
|
162
|
+
async function createPostgresSessionStoreWithPool(
|
|
163
163
|
pool,
|
|
164
164
|
identity,
|
|
165
165
|
logger = null,
|
|
166
166
|
runMigration = true
|
|
167
167
|
) {
|
|
168
|
-
const manager = new
|
|
168
|
+
const manager = new PostgresSessionStore(
|
|
169
169
|
pool,
|
|
170
170
|
identity,
|
|
171
171
|
logger,
|
|
@@ -176,8 +176,8 @@ async function createPostgresSessionManagerWithPool(
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
module.exports = {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
PostgresSessionStore,
|
|
180
|
+
createPostgresSessionStore,
|
|
181
|
+
createPostgresSessionStoreWithPool,
|
|
182
|
+
SessionStoreError,
|
|
183
183
|
};
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Database Migration Manager for Breez SDK PostgreSQL Session
|
|
2
|
+
* Database Migration Manager for Breez SDK PostgreSQL Session Store.
|
|
3
3
|
*
|
|
4
4
|
* Uses a brz_session_schema_migrations table + pg_advisory_xact_lock to safely
|
|
5
5
|
* run migrations from concurrent processes. Mirrors the schema produced by
|
|
6
|
-
* the Rust `
|
|
6
|
+
* the Rust `PostgresSessionStore`.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const {
|
|
9
|
+
const { SessionStoreError } = require("./errors.cjs");
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Advisory lock ID for session-
|
|
12
|
+
* Advisory lock ID for session-store migrations.
|
|
13
13
|
* Uses a different lock ID from the storage / tree store / token store
|
|
14
14
|
* migrations to avoid contention. Derived from ASCII bytes of "SESN"
|
|
15
15
|
* (0x5345534E).
|
|
16
16
|
*/
|
|
17
17
|
const MIGRATION_LOCK_ID = "1397245774"; // 0x5345534E as decimal string
|
|
18
18
|
|
|
19
|
-
class
|
|
19
|
+
class SessionStoreMigrationManager {
|
|
20
20
|
constructor(logger = null) {
|
|
21
21
|
this.logger = logger;
|
|
22
22
|
}
|
|
@@ -51,7 +51,7 @@ class SessionManagerMigrationManager {
|
|
|
51
51
|
if (currentVersion >= migrations.length) {
|
|
52
52
|
this._log(
|
|
53
53
|
"info",
|
|
54
|
-
`Session
|
|
54
|
+
`Session store database is up to date (version ${currentVersion})`
|
|
55
55
|
);
|
|
56
56
|
await client.query("COMMIT");
|
|
57
57
|
return;
|
|
@@ -59,7 +59,7 @@ class SessionManagerMigrationManager {
|
|
|
59
59
|
|
|
60
60
|
this._log(
|
|
61
61
|
"info",
|
|
62
|
-
`Migrating session
|
|
62
|
+
`Migrating session store database from version ${currentVersion} to ${migrations.length}`
|
|
63
63
|
);
|
|
64
64
|
|
|
65
65
|
for (let i = currentVersion; i < migrations.length; i++) {
|
|
@@ -67,7 +67,7 @@ class SessionManagerMigrationManager {
|
|
|
67
67
|
const version = i + 1;
|
|
68
68
|
this._log(
|
|
69
69
|
"debug",
|
|
70
|
-
`Running session
|
|
70
|
+
`Running session store migration ${version}: ${migration.name}`
|
|
71
71
|
);
|
|
72
72
|
|
|
73
73
|
for (const sql of migration.sql) {
|
|
@@ -83,12 +83,12 @@ class SessionManagerMigrationManager {
|
|
|
83
83
|
await client.query("COMMIT");
|
|
84
84
|
this._log(
|
|
85
85
|
"info",
|
|
86
|
-
"Session
|
|
86
|
+
"Session store database migration completed successfully"
|
|
87
87
|
);
|
|
88
88
|
} catch (error) {
|
|
89
89
|
await client.query("ROLLBACK").catch(() => {});
|
|
90
|
-
throw new
|
|
91
|
-
`Session
|
|
90
|
+
throw new SessionStoreError(
|
|
91
|
+
`Session store migration failed: ${error.message}`,
|
|
92
92
|
error
|
|
93
93
|
);
|
|
94
94
|
} finally {
|
|
@@ -137,12 +137,12 @@ class SessionManagerMigrationManager {
|
|
|
137
137
|
if (this.logger && typeof this.logger.log === "function") {
|
|
138
138
|
this.logger.log({ line: message, level });
|
|
139
139
|
} else if (level === "error") {
|
|
140
|
-
console.error(`[
|
|
140
|
+
console.error(`[SessionStoreMigrationManager] ${message}`);
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* Migrations matching the Rust
|
|
145
|
+
* Migrations matching the Rust PostgresSessionStore schema exactly.
|
|
146
146
|
*/
|
|
147
147
|
_getMigrations() {
|
|
148
148
|
return [
|
|
@@ -162,4 +162,4 @@ class SessionManagerMigrationManager {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
module.exports = {
|
|
165
|
+
module.exports = { SessionStoreMigrationManager };
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* CommonJS implementation for Node.js PostgreSQL Storage
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
5
7
|
let pg;
|
|
6
8
|
try {
|
|
7
9
|
const mainModule = require.main;
|
|
@@ -38,7 +40,8 @@ const SELECT_PAYMENT_SQL = `
|
|
|
38
40
|
p.timestamp,
|
|
39
41
|
p.method,
|
|
40
42
|
p.withdraw_tx_id,
|
|
41
|
-
|
|
43
|
+
pd.tx_id AS deposit_tx_id,
|
|
44
|
+
pd.vout AS deposit_vout,
|
|
42
45
|
p.spark,
|
|
43
46
|
l.invoice AS lightning_invoice,
|
|
44
47
|
l.payment_hash AS lightning_payment_hash,
|
|
@@ -66,6 +69,7 @@ const SELECT_PAYMENT_SQL = `
|
|
|
66
69
|
LEFT JOIN brz_payment_details_lightning l ON p.id = l.payment_id AND p.user_id = l.user_id
|
|
67
70
|
LEFT JOIN brz_payment_details_token t ON p.id = t.payment_id AND p.user_id = t.user_id
|
|
68
71
|
LEFT JOIN brz_payment_details_spark s ON p.id = s.payment_id AND p.user_id = s.user_id
|
|
72
|
+
LEFT JOIN brz_payment_details_deposit pd ON p.id = pd.payment_id AND p.user_id = pd.user_id
|
|
69
73
|
LEFT JOIN brz_payment_metadata pm ON p.id = pm.payment_id AND p.user_id = pm.user_id
|
|
70
74
|
LEFT JOIN brz_lnurl_receive_metadata lrm ON l.payment_hash = lrm.payment_hash AND l.user_id = lrm.user_id`;
|
|
71
75
|
|
|
@@ -353,130 +357,192 @@ class PostgresStorage {
|
|
|
353
357
|
}
|
|
354
358
|
}
|
|
355
359
|
|
|
356
|
-
async
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
await this._withTransaction(async (client) => {
|
|
363
|
-
const withdrawTxId =
|
|
364
|
-
payment.details?.type === "withdraw" ? payment.details.txId : null;
|
|
365
|
-
const depositTxId =
|
|
366
|
-
payment.details?.type === "deposit" ? payment.details.txId : null;
|
|
367
|
-
const spark = payment.details?.type === "spark" ? true : null;
|
|
360
|
+
async applyPaymentUpdate(payment) {
|
|
361
|
+
if (!payment) {
|
|
362
|
+
throw new StorageError("Payment cannot be null or undefined");
|
|
363
|
+
}
|
|
368
364
|
|
|
365
|
+
try {
|
|
366
|
+
return await this._withTransaction(async (client) => {
|
|
367
|
+
const lockKey = this._paymentUpdateLockKey(payment.id);
|
|
369
368
|
await client.query(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
ON CONFLICT(user_id, id) DO UPDATE SET
|
|
373
|
-
payment_type=EXCLUDED.payment_type,
|
|
374
|
-
status=EXCLUDED.status,
|
|
375
|
-
amount=EXCLUDED.amount,
|
|
376
|
-
fees=EXCLUDED.fees,
|
|
377
|
-
timestamp=EXCLUDED.timestamp,
|
|
378
|
-
method=EXCLUDED.method,
|
|
379
|
-
withdraw_tx_id=EXCLUDED.withdraw_tx_id,
|
|
380
|
-
deposit_tx_id=EXCLUDED.deposit_tx_id,
|
|
381
|
-
spark=EXCLUDED.spark`,
|
|
382
|
-
[
|
|
383
|
-
this.identity,
|
|
384
|
-
payment.id,
|
|
385
|
-
payment.paymentType,
|
|
386
|
-
payment.status,
|
|
387
|
-
payment.amount.toString(),
|
|
388
|
-
payment.fees.toString(),
|
|
389
|
-
payment.timestamp,
|
|
390
|
-
payment.method ? JSON.stringify(payment.method) : null,
|
|
391
|
-
withdrawTxId,
|
|
392
|
-
depositTxId,
|
|
393
|
-
spark,
|
|
394
|
-
]
|
|
369
|
+
"SELECT pg_advisory_xact_lock($1::bigint)",
|
|
370
|
+
[lockKey]
|
|
395
371
|
);
|
|
396
372
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
payment.details.invoiceDetails
|
|
412
|
-
? JSON.stringify(payment.details.invoiceDetails)
|
|
413
|
-
: null,
|
|
414
|
-
payment.details.htlcDetails
|
|
415
|
-
? JSON.stringify(payment.details.htlcDetails)
|
|
416
|
-
: null,
|
|
417
|
-
]
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (payment.details?.type === "lightning") {
|
|
422
|
-
await client.query(
|
|
423
|
-
`INSERT INTO brz_payment_details_lightning
|
|
424
|
-
(user_id, payment_id, invoice, payment_hash, destination_pubkey, description, preimage, htlc_status, htlc_expiry_time)
|
|
425
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
426
|
-
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
427
|
-
invoice=EXCLUDED.invoice,
|
|
428
|
-
payment_hash=EXCLUDED.payment_hash,
|
|
429
|
-
destination_pubkey=EXCLUDED.destination_pubkey,
|
|
430
|
-
description=EXCLUDED.description,
|
|
431
|
-
preimage=COALESCE(EXCLUDED.preimage, brz_payment_details_lightning.preimage),
|
|
432
|
-
htlc_status=COALESCE(EXCLUDED.htlc_status, brz_payment_details_lightning.htlc_status),
|
|
433
|
-
htlc_expiry_time=COALESCE(EXCLUDED.htlc_expiry_time, brz_payment_details_lightning.htlc_expiry_time)`,
|
|
434
|
-
[
|
|
435
|
-
this.identity,
|
|
436
|
-
payment.id,
|
|
437
|
-
payment.details.invoice,
|
|
438
|
-
payment.details.htlcDetails.paymentHash,
|
|
439
|
-
payment.details.destinationPubkey,
|
|
440
|
-
payment.details.description,
|
|
441
|
-
payment.details.htlcDetails?.preimage,
|
|
442
|
-
payment.details.htlcDetails?.status ?? null,
|
|
443
|
-
payment.details.htlcDetails?.expiryTime ?? 0,
|
|
444
|
-
]
|
|
373
|
+
const { rows } = await client.query(
|
|
374
|
+
"SELECT status FROM brz_payments WHERE user_id = $1 AND id = $2 FOR UPDATE",
|
|
375
|
+
[this.identity, payment.id]
|
|
376
|
+
);
|
|
377
|
+
const stored = rows.length > 0
|
|
378
|
+
? this._normalizePaymentStatus(rows[0].status)
|
|
379
|
+
: null;
|
|
380
|
+
const next = this._normalizePaymentStatus(payment.status);
|
|
381
|
+
|
|
382
|
+
if (stored != null
|
|
383
|
+
&& this._isFinalPaymentStatus(stored)
|
|
384
|
+
&& stored !== next) {
|
|
385
|
+
console.warn(
|
|
386
|
+
`Skipping payment update (would replace terminal status): id=${payment.id} stored=${stored} new=${next}`
|
|
445
387
|
);
|
|
388
|
+
return false;
|
|
446
389
|
}
|
|
447
390
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
VALUES ($1, $2, $3, $4, $5, $6)
|
|
453
|
-
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
454
|
-
metadata=EXCLUDED.metadata,
|
|
455
|
-
tx_hash=EXCLUDED.tx_hash,
|
|
456
|
-
tx_type=EXCLUDED.tx_type,
|
|
457
|
-
invoice_details=COALESCE(EXCLUDED.invoice_details, brz_payment_details_token.invoice_details)`,
|
|
458
|
-
[
|
|
459
|
-
this.identity,
|
|
460
|
-
payment.id,
|
|
461
|
-
JSON.stringify(payment.details.metadata),
|
|
462
|
-
payment.details.txHash,
|
|
463
|
-
payment.details.txType,
|
|
464
|
-
payment.details.invoiceDetails
|
|
465
|
-
? JSON.stringify(payment.details.invoiceDetails)
|
|
466
|
-
: null,
|
|
467
|
-
]
|
|
391
|
+
const sameStatus = stored === next;
|
|
392
|
+
if (sameStatus) {
|
|
393
|
+
console.debug(
|
|
394
|
+
`Skipping redundant payment event: id=${payment.id} status=${next}`
|
|
468
395
|
);
|
|
469
396
|
}
|
|
397
|
+
await this._runPaymentUpsert(client, payment);
|
|
398
|
+
return !sameStatus;
|
|
470
399
|
});
|
|
471
400
|
} catch (error) {
|
|
472
401
|
if (error instanceof StorageError) throw error;
|
|
473
402
|
throw new StorageError(
|
|
474
|
-
`Failed to
|
|
403
|
+
`Failed to apply payment update '${payment.id}': ${error.message}`,
|
|
475
404
|
error
|
|
476
405
|
);
|
|
477
406
|
}
|
|
478
407
|
}
|
|
479
408
|
|
|
409
|
+
_paymentUpdateLockKey(paymentId) {
|
|
410
|
+
return crypto
|
|
411
|
+
.createHash("sha256")
|
|
412
|
+
.update("brz_payment_update")
|
|
413
|
+
.update(this.identity)
|
|
414
|
+
.update(Buffer.from(paymentId))
|
|
415
|
+
.digest()
|
|
416
|
+
.readBigInt64BE(0)
|
|
417
|
+
.toString();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async _runPaymentUpsert(client, payment) {
|
|
421
|
+
const withdrawTxId =
|
|
422
|
+
payment.details?.type === "withdraw" ? payment.details.txId : null;
|
|
423
|
+
const spark = payment.details?.type === "spark" ? true : null;
|
|
424
|
+
|
|
425
|
+
await client.query(
|
|
426
|
+
`INSERT INTO brz_payments (user_id, id, payment_type, status, amount, fees, timestamp, method, withdraw_tx_id, spark)
|
|
427
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
428
|
+
ON CONFLICT(user_id, id) DO UPDATE SET
|
|
429
|
+
payment_type=EXCLUDED.payment_type,
|
|
430
|
+
status=EXCLUDED.status,
|
|
431
|
+
amount=EXCLUDED.amount,
|
|
432
|
+
fees=EXCLUDED.fees,
|
|
433
|
+
timestamp=EXCLUDED.timestamp,
|
|
434
|
+
method=EXCLUDED.method,
|
|
435
|
+
withdraw_tx_id=EXCLUDED.withdraw_tx_id,
|
|
436
|
+
spark=EXCLUDED.spark`,
|
|
437
|
+
[
|
|
438
|
+
this.identity,
|
|
439
|
+
payment.id,
|
|
440
|
+
payment.paymentType,
|
|
441
|
+
payment.status,
|
|
442
|
+
payment.amount.toString(),
|
|
443
|
+
payment.fees.toString(),
|
|
444
|
+
payment.timestamp,
|
|
445
|
+
payment.method ? JSON.stringify(payment.method) : null,
|
|
446
|
+
withdrawTxId,
|
|
447
|
+
spark,
|
|
448
|
+
]
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
if (payment.details?.type === "deposit") {
|
|
452
|
+
await client.query(
|
|
453
|
+
`INSERT INTO brz_payment_details_deposit (user_id, payment_id, tx_id, vout)
|
|
454
|
+
VALUES ($1, $2, $3, $4)
|
|
455
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
456
|
+
tx_id=EXCLUDED.tx_id,
|
|
457
|
+
vout=EXCLUDED.vout`,
|
|
458
|
+
[this.identity, payment.id, payment.details.txId, payment.details.vout]
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (
|
|
463
|
+
payment.details?.type === "spark" &&
|
|
464
|
+
(payment.details.invoiceDetails != null ||
|
|
465
|
+
payment.details.htlcDetails != null)
|
|
466
|
+
) {
|
|
467
|
+
await client.query(
|
|
468
|
+
`INSERT INTO brz_payment_details_spark (user_id, payment_id, invoice_details, htlc_details)
|
|
469
|
+
VALUES ($1, $2, $3, $4)
|
|
470
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
471
|
+
invoice_details=COALESCE(EXCLUDED.invoice_details, brz_payment_details_spark.invoice_details),
|
|
472
|
+
htlc_details=COALESCE(EXCLUDED.htlc_details, brz_payment_details_spark.htlc_details)`,
|
|
473
|
+
[
|
|
474
|
+
this.identity,
|
|
475
|
+
payment.id,
|
|
476
|
+
payment.details.invoiceDetails
|
|
477
|
+
? JSON.stringify(payment.details.invoiceDetails)
|
|
478
|
+
: null,
|
|
479
|
+
payment.details.htlcDetails
|
|
480
|
+
? JSON.stringify(payment.details.htlcDetails)
|
|
481
|
+
: null,
|
|
482
|
+
]
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (payment.details?.type === "lightning") {
|
|
487
|
+
await client.query(
|
|
488
|
+
`INSERT INTO brz_payment_details_lightning
|
|
489
|
+
(user_id, payment_id, invoice, payment_hash, destination_pubkey, description, preimage, htlc_status, htlc_expiry_time)
|
|
490
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
491
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
492
|
+
invoice=EXCLUDED.invoice,
|
|
493
|
+
payment_hash=EXCLUDED.payment_hash,
|
|
494
|
+
destination_pubkey=EXCLUDED.destination_pubkey,
|
|
495
|
+
description=EXCLUDED.description,
|
|
496
|
+
preimage=COALESCE(EXCLUDED.preimage, brz_payment_details_lightning.preimage),
|
|
497
|
+
htlc_status=COALESCE(EXCLUDED.htlc_status, brz_payment_details_lightning.htlc_status),
|
|
498
|
+
htlc_expiry_time=COALESCE(EXCLUDED.htlc_expiry_time, brz_payment_details_lightning.htlc_expiry_time)`,
|
|
499
|
+
[
|
|
500
|
+
this.identity,
|
|
501
|
+
payment.id,
|
|
502
|
+
payment.details.invoice,
|
|
503
|
+
payment.details.htlcDetails.paymentHash,
|
|
504
|
+
payment.details.destinationPubkey,
|
|
505
|
+
payment.details.description,
|
|
506
|
+
payment.details.htlcDetails?.preimage,
|
|
507
|
+
payment.details.htlcDetails?.status ?? null,
|
|
508
|
+
payment.details.htlcDetails?.expiryTime ?? 0,
|
|
509
|
+
]
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (payment.details?.type === "token") {
|
|
514
|
+
await client.query(
|
|
515
|
+
`INSERT INTO brz_payment_details_token
|
|
516
|
+
(user_id, payment_id, metadata, tx_hash, tx_type, invoice_details)
|
|
517
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
518
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
519
|
+
metadata=EXCLUDED.metadata,
|
|
520
|
+
tx_hash=EXCLUDED.tx_hash,
|
|
521
|
+
tx_type=EXCLUDED.tx_type,
|
|
522
|
+
invoice_details=COALESCE(EXCLUDED.invoice_details, brz_payment_details_token.invoice_details)`,
|
|
523
|
+
[
|
|
524
|
+
this.identity,
|
|
525
|
+
payment.id,
|
|
526
|
+
JSON.stringify(payment.details.metadata),
|
|
527
|
+
payment.details.txHash,
|
|
528
|
+
payment.details.txType,
|
|
529
|
+
payment.details.invoiceDetails
|
|
530
|
+
? JSON.stringify(payment.details.invoiceDetails)
|
|
531
|
+
: null,
|
|
532
|
+
]
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
_normalizePaymentStatus(status) {
|
|
538
|
+
return typeof status === "string" ? status.toLowerCase() : status;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
_isFinalPaymentStatus(status) {
|
|
542
|
+
const normalized = this._normalizePaymentStatus(status);
|
|
543
|
+
return normalized === "completed" || normalized === "failed";
|
|
544
|
+
}
|
|
545
|
+
|
|
480
546
|
async getPaymentById(id) {
|
|
481
547
|
try {
|
|
482
548
|
if (!id) {
|
|
@@ -778,6 +844,7 @@ class PostgresStorage {
|
|
|
778
844
|
details = {
|
|
779
845
|
type: "deposit",
|
|
780
846
|
txId: row.deposit_tx_id,
|
|
847
|
+
vout: Number(row.deposit_vout),
|
|
781
848
|
};
|
|
782
849
|
} else if (row.spark) {
|
|
783
850
|
details = {
|
|
@@ -467,6 +467,30 @@ class PostgresMigrationManager {
|
|
|
467
467
|
ON brz_sync_incoming(user_id, revision)`,
|
|
468
468
|
],
|
|
469
469
|
},
|
|
470
|
+
{
|
|
471
|
+
// Move deposit details into their own table so vout can be NOT NULL and
|
|
472
|
+
// the schema matches brz_payment_details_lightning / _token / _spark. We
|
|
473
|
+
// can't safely backfill the new table from the dropped deposit_tx_id
|
|
474
|
+
// column: we never stored the original SSP output_index, and vout=0 is a
|
|
475
|
+
// valid output index — defaulting would silently mislabel. Drop the
|
|
476
|
+
// column and leave the brz_payments row in place. The read path sees an
|
|
477
|
+
// unjoined deposit row as `details: None` until the resync re-fetches the
|
|
478
|
+
// SSP user_request and the upsert inserts the new details row.
|
|
479
|
+
name: "Move deposit details into brz_payment_details_deposit table",
|
|
480
|
+
sql: [
|
|
481
|
+
`CREATE TABLE IF NOT EXISTS brz_payment_details_deposit (
|
|
482
|
+
user_id BYTEA NOT NULL,
|
|
483
|
+
payment_id TEXT NOT NULL,
|
|
484
|
+
tx_id TEXT NOT NULL,
|
|
485
|
+
vout BIGINT NOT NULL,
|
|
486
|
+
PRIMARY KEY (user_id, payment_id)
|
|
487
|
+
)`,
|
|
488
|
+
`ALTER TABLE brz_payments DROP COLUMN IF EXISTS deposit_tx_id`,
|
|
489
|
+
`UPDATE brz_settings
|
|
490
|
+
SET value = jsonb_set(value::jsonb, '{offset}', '0')::text
|
|
491
|
+
WHERE key = 'sync_offset' AND value IS NOT NULL`,
|
|
492
|
+
],
|
|
493
|
+
},
|
|
470
494
|
];
|
|
471
495
|
}
|
|
472
496
|
}
|