@breeztech/breez-sdk-spark 0.23.1 → 0.24.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/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +75 -6
- package/bundler/breez_sdk_spark_wasm_bg.js +33 -19
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +5 -3
- package/bundler/storage/index.js +34 -3
- package/deno/breez_sdk_spark_wasm.d.ts +75 -6
- package/deno/breez_sdk_spark_wasm.js +33 -19
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +5 -3
- package/nodejs/breez_sdk_spark_wasm.d.ts +75 -6
- package/nodejs/breez_sdk_spark_wasm.js +33 -19
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +5 -3
- package/nodejs/mysql-storage/index.cjs +133 -5
- package/nodejs/mysql-storage/migrations.cjs +16 -0
- package/nodejs/mysql-token-store/index.cjs +108 -1
- package/nodejs/mysql-tree-store/index.cjs +108 -1
- package/nodejs/postgres-storage/index.cjs +135 -6
- package/nodejs/postgres-storage/migrations.cjs +16 -0
- package/nodejs/postgres-token-store/index.cjs +109 -1
- package/nodejs/postgres-tree-store/index.cjs +109 -1
- package/nodejs/storage/index.cjs +19 -4
- package/nodejs/storage/migrations.cjs +14 -0
- package/package.json +1 -1
- package/web/breez_sdk_spark_wasm.d.ts +80 -9
- package/web/breez_sdk_spark_wasm.js +33 -19
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +5 -3
- package/web/storage/index.js +34 -3
|
@@ -730,7 +730,7 @@ class PostgresStorage {
|
|
|
730
730
|
async listDeposits() {
|
|
731
731
|
try {
|
|
732
732
|
const result = await this.pool.query(
|
|
733
|
-
"SELECT txid, vout, amount_sats, is_mature, claim_error, refund_tx, refund_tx_id, instant_claim_status FROM brz_unclaimed_deposits WHERE user_id = $1",
|
|
733
|
+
"SELECT txid, vout, amount_sats, is_mature, claim_error, refund_tx, refund_tx_id, instant_claim_status, refund_state FROM brz_unclaimed_deposits WHERE user_id = $1",
|
|
734
734
|
[this.identity]
|
|
735
735
|
);
|
|
736
736
|
|
|
@@ -743,6 +743,7 @@ class PostgresStorage {
|
|
|
743
743
|
refundTx: row.refund_tx,
|
|
744
744
|
refundTxId: row.refund_tx_id,
|
|
745
745
|
instantClaimStatus: row.instant_claim_status || null,
|
|
746
|
+
refundState: row.refund_state || null,
|
|
746
747
|
}));
|
|
747
748
|
} catch (error) {
|
|
748
749
|
throw new StorageError(
|
|
@@ -757,16 +758,23 @@ class PostgresStorage {
|
|
|
757
758
|
if (payload.type === "claimError") {
|
|
758
759
|
await this.pool.query(
|
|
759
760
|
`UPDATE brz_unclaimed_deposits
|
|
760
|
-
SET claim_error = $1
|
|
761
|
+
SET claim_error = $1
|
|
761
762
|
WHERE user_id = $2 AND txid = $3 AND vout = $4`,
|
|
762
763
|
[JSON.stringify(payload.error), this.identity, txid, vout]
|
|
763
764
|
);
|
|
764
765
|
} else if (payload.type === "refund") {
|
|
765
766
|
await this.pool.query(
|
|
766
767
|
`UPDATE brz_unclaimed_deposits
|
|
767
|
-
SET refund_tx = $1, refund_tx_id = $2, claim_error = NULL
|
|
768
|
-
WHERE user_id = $
|
|
769
|
-
[
|
|
768
|
+
SET refund_tx = $1, refund_tx_id = $2, refund_state = $3, claim_error = NULL
|
|
769
|
+
WHERE user_id = $4 AND txid = $5 AND vout = $6`,
|
|
770
|
+
[
|
|
771
|
+
payload.refundTx,
|
|
772
|
+
payload.refundTxid,
|
|
773
|
+
JSON.stringify(payload.state),
|
|
774
|
+
this.identity,
|
|
775
|
+
txid,
|
|
776
|
+
vout,
|
|
777
|
+
]
|
|
770
778
|
);
|
|
771
779
|
} else if (payload.type === "instantClaim") {
|
|
772
780
|
await this.pool.query(
|
|
@@ -775,6 +783,19 @@ class PostgresStorage {
|
|
|
775
783
|
WHERE user_id = $2 AND txid = $3 AND vout = $4`,
|
|
776
784
|
[JSON.stringify(payload.status), this.identity, txid, vout]
|
|
777
785
|
);
|
|
786
|
+
} else if (payload.type === "refundBroadcastState") {
|
|
787
|
+
await this.pool.query(
|
|
788
|
+
`UPDATE brz_unclaimed_deposits
|
|
789
|
+
SET refund_state = $1
|
|
790
|
+
WHERE user_id = $2 AND txid = $3 AND vout = $4 AND refund_tx_id = $5`,
|
|
791
|
+
[
|
|
792
|
+
JSON.stringify(payload.state),
|
|
793
|
+
this.identity,
|
|
794
|
+
txid,
|
|
795
|
+
vout,
|
|
796
|
+
payload.refundTxid,
|
|
797
|
+
]
|
|
798
|
+
);
|
|
778
799
|
} else {
|
|
779
800
|
throw new StorageError(`Unknown payload type: ${payload.type}`);
|
|
780
801
|
}
|
|
@@ -1606,6 +1627,112 @@ async function createPostgresStorage(config, identity, logger = null) {
|
|
|
1606
1627
|
);
|
|
1607
1628
|
}
|
|
1608
1629
|
|
|
1630
|
+
/**
|
|
1631
|
+
* Translates the `sslmode` URI parameter into pg's `ssl` option.
|
|
1632
|
+
* The pinned `pg` maps `require` to verified TLS but warns that pg v9 reverts
|
|
1633
|
+
* it to libpq semantics (no verification); handling `sslmode` here keeps the
|
|
1634
|
+
* documented guarantees stable across driver versions. The parameter (and the
|
|
1635
|
+
* `ssl`/`sslcert`/`sslkey`/`sslrootcert` parameters folded into the option)
|
|
1636
|
+
* are stripped from the URI because a connection string's ssl settings
|
|
1637
|
+
* override the explicit option.
|
|
1638
|
+
*
|
|
1639
|
+
* Spellings mirror the Rust SDK where pg has an equivalent:
|
|
1640
|
+
* - absent or `disable`: no TLS
|
|
1641
|
+
* - `prefer` / `require` / `verify-full`: TLS with certificate chain and
|
|
1642
|
+
* hostname verification (pg cannot fall back to plaintext, so `prefer`
|
|
1643
|
+
* behaves like `require`)
|
|
1644
|
+
* - `verify-ca`: chain verification only, hostname not checked
|
|
1645
|
+
* - `no-verify`: TLS without certificate verification (explicit opt-in)
|
|
1646
|
+
*
|
|
1647
|
+
* An unrecognized value throws rather than silently changing the TLS level.
|
|
1648
|
+
* Pin a private CA with `sslrootcert=<path>` (required for `verify-ca`); for
|
|
1649
|
+
* the hostname-verified modes, adding the CA to Node's trust store
|
|
1650
|
+
* (e.g. NODE_EXTRA_CA_CERTS) also works, though it extends the store rather
|
|
1651
|
+
* than pinning.
|
|
1652
|
+
*/
|
|
1653
|
+
function extractSslMode(connectionString) {
|
|
1654
|
+
if (
|
|
1655
|
+
!connectionString.startsWith("postgres://") &&
|
|
1656
|
+
!connectionString.startsWith("postgresql://")
|
|
1657
|
+
) {
|
|
1658
|
+
return { connectionString, ssl: undefined };
|
|
1659
|
+
}
|
|
1660
|
+
const queryIndex = connectionString.indexOf("?");
|
|
1661
|
+
if (queryIndex === -1) {
|
|
1662
|
+
return { connectionString, ssl: undefined };
|
|
1663
|
+
}
|
|
1664
|
+
const params = connectionString.slice(queryIndex + 1).split("&");
|
|
1665
|
+
if (!params.some((param) => param.startsWith("sslmode="))) {
|
|
1666
|
+
return { connectionString, ssl: undefined };
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
const base = connectionString.slice(0, queryIndex);
|
|
1670
|
+
const retained = [];
|
|
1671
|
+
const files = {};
|
|
1672
|
+
let sslMode;
|
|
1673
|
+
for (const param of params) {
|
|
1674
|
+
const eq = param.indexOf("=");
|
|
1675
|
+
const key = eq === -1 ? param : param.slice(0, eq);
|
|
1676
|
+
const value = eq === -1 ? "" : decodeURIComponent(param.slice(eq + 1));
|
|
1677
|
+
if (key === "sslmode") {
|
|
1678
|
+
sslMode = value;
|
|
1679
|
+
} else if (key === "sslcert" || key === "sslkey" || key === "sslrootcert") {
|
|
1680
|
+
files[key] = value;
|
|
1681
|
+
} else if (key === "ssl") {
|
|
1682
|
+
// consumed: sslmode governs TLS when present
|
|
1683
|
+
} else {
|
|
1684
|
+
retained.push(param);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
const rebuilt = retained.length ? `${base}?${retained.join("&")}` : base;
|
|
1688
|
+
return { connectionString: rebuilt, ssl: sslOptionForMode(sslMode, files) };
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function sslOptionForMode(mode, files) {
|
|
1692
|
+
let ssl;
|
|
1693
|
+
switch (mode) {
|
|
1694
|
+
case "disable":
|
|
1695
|
+
return false;
|
|
1696
|
+
case "prefer":
|
|
1697
|
+
case "require":
|
|
1698
|
+
case "verify-full":
|
|
1699
|
+
ssl = { rejectUnauthorized: true };
|
|
1700
|
+
break;
|
|
1701
|
+
case "verify-ca":
|
|
1702
|
+
// Without a pinned CA, chain-only verification accepts a certificate
|
|
1703
|
+
// from any trusted CA for any host, which authenticates nothing.
|
|
1704
|
+
if (!files.sslrootcert) {
|
|
1705
|
+
throw new StorageError(
|
|
1706
|
+
"sslmode=verify-ca requires sslrootcert=<path>; supply the CA to " +
|
|
1707
|
+
"pin, or use sslmode=require / verify-full for " +
|
|
1708
|
+
"hostname-verified TLS"
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
ssl = { rejectUnauthorized: true, checkServerIdentity: () => undefined };
|
|
1712
|
+
break;
|
|
1713
|
+
case "no-verify":
|
|
1714
|
+
ssl = { rejectUnauthorized: false };
|
|
1715
|
+
break;
|
|
1716
|
+
default:
|
|
1717
|
+
throw new StorageError(
|
|
1718
|
+
`Unrecognized sslmode value \`${mode}\`; expected one of: ` +
|
|
1719
|
+
"disable, prefer, require, verify-ca, verify-full, no-verify"
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
const fs =
|
|
1723
|
+
files.sslcert || files.sslkey || files.sslrootcert ? require("fs") : null;
|
|
1724
|
+
if (files.sslcert) {
|
|
1725
|
+
ssl.cert = fs.readFileSync(files.sslcert).toString();
|
|
1726
|
+
}
|
|
1727
|
+
if (files.sslkey) {
|
|
1728
|
+
ssl.key = fs.readFileSync(files.sslkey).toString();
|
|
1729
|
+
}
|
|
1730
|
+
if (files.sslrootcert) {
|
|
1731
|
+
ssl.ca = fs.readFileSync(files.sslrootcert).toString();
|
|
1732
|
+
}
|
|
1733
|
+
return ssl;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1609
1736
|
/**
|
|
1610
1737
|
* Create a pg.Pool from a config object.
|
|
1611
1738
|
* The returned pool can be shared across multiple store implementations.
|
|
@@ -1614,8 +1741,10 @@ async function createPostgresStorage(config, identity, logger = null) {
|
|
|
1614
1741
|
* @returns {pg.Pool}
|
|
1615
1742
|
*/
|
|
1616
1743
|
function createPostgresPool(config) {
|
|
1744
|
+
const { connectionString, ssl } = extractSslMode(config.connectionString);
|
|
1617
1745
|
return new pg.Pool({
|
|
1618
|
-
connectionString
|
|
1746
|
+
connectionString,
|
|
1747
|
+
...(ssl !== undefined && { ssl }),
|
|
1619
1748
|
max: config.maxPoolSize,
|
|
1620
1749
|
connectionTimeoutMillis: config.createTimeoutSecs * 1000,
|
|
1621
1750
|
idleTimeoutMillis: config.recycleTimeoutSecs * 1000,
|
|
@@ -524,6 +524,22 @@ class PostgresMigrationManager {
|
|
|
524
524
|
`ALTER TABLE brz_unclaimed_deposits ADD COLUMN instant_claim_status JSONB`,
|
|
525
525
|
],
|
|
526
526
|
},
|
|
527
|
+
{
|
|
528
|
+
// Only the Declined shape changed, and clearing it costs at most one
|
|
529
|
+
// extra quote. Submitted rows are left alone: their shape is unchanged
|
|
530
|
+
// and they are the only guard against re-claiming a settling deposit.
|
|
531
|
+
name: "Clear declined instant claim status after its shape changed",
|
|
532
|
+
sql: [
|
|
533
|
+
`UPDATE brz_unclaimed_deposits SET instant_claim_status = NULL
|
|
534
|
+
WHERE LOWER(instant_claim_status::text) LIKE '%declined%'`,
|
|
535
|
+
],
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "Add refund state to brz_unclaimed_deposits",
|
|
539
|
+
sql: [
|
|
540
|
+
`ALTER TABLE brz_unclaimed_deposits ADD COLUMN refund_state JSONB`,
|
|
541
|
+
],
|
|
542
|
+
},
|
|
527
543
|
];
|
|
528
544
|
}
|
|
529
545
|
}
|
|
@@ -1267,6 +1267,112 @@ class PostgresTokenStore {
|
|
|
1267
1267
|
}
|
|
1268
1268
|
}
|
|
1269
1269
|
|
|
1270
|
+
/**
|
|
1271
|
+
* Translates the `sslmode` URI parameter into pg's `ssl` option.
|
|
1272
|
+
* The pinned `pg` maps `require` to verified TLS but warns that pg v9 reverts
|
|
1273
|
+
* it to libpq semantics (no verification); handling `sslmode` here keeps the
|
|
1274
|
+
* documented guarantees stable across driver versions. The parameter (and the
|
|
1275
|
+
* `ssl`/`sslcert`/`sslkey`/`sslrootcert` parameters folded into the option)
|
|
1276
|
+
* are stripped from the URI because a connection string's ssl settings
|
|
1277
|
+
* override the explicit option.
|
|
1278
|
+
*
|
|
1279
|
+
* Spellings mirror the Rust SDK where pg has an equivalent:
|
|
1280
|
+
* - absent or `disable`: no TLS
|
|
1281
|
+
* - `prefer` / `require` / `verify-full`: TLS with certificate chain and
|
|
1282
|
+
* hostname verification (pg cannot fall back to plaintext, so `prefer`
|
|
1283
|
+
* behaves like `require`)
|
|
1284
|
+
* - `verify-ca`: chain verification only, hostname not checked
|
|
1285
|
+
* - `no-verify`: TLS without certificate verification (explicit opt-in)
|
|
1286
|
+
*
|
|
1287
|
+
* An unrecognized value throws rather than silently changing the TLS level.
|
|
1288
|
+
* Pin a private CA with `sslrootcert=<path>` (required for `verify-ca`); for
|
|
1289
|
+
* the hostname-verified modes, adding the CA to Node's trust store
|
|
1290
|
+
* (e.g. NODE_EXTRA_CA_CERTS) also works, though it extends the store rather
|
|
1291
|
+
* than pinning.
|
|
1292
|
+
*/
|
|
1293
|
+
function extractSslMode(connectionString) {
|
|
1294
|
+
if (
|
|
1295
|
+
!connectionString.startsWith("postgres://") &&
|
|
1296
|
+
!connectionString.startsWith("postgresql://")
|
|
1297
|
+
) {
|
|
1298
|
+
return { connectionString, ssl: undefined };
|
|
1299
|
+
}
|
|
1300
|
+
const queryIndex = connectionString.indexOf("?");
|
|
1301
|
+
if (queryIndex === -1) {
|
|
1302
|
+
return { connectionString, ssl: undefined };
|
|
1303
|
+
}
|
|
1304
|
+
const params = connectionString.slice(queryIndex + 1).split("&");
|
|
1305
|
+
if (!params.some((param) => param.startsWith("sslmode="))) {
|
|
1306
|
+
return { connectionString, ssl: undefined };
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const base = connectionString.slice(0, queryIndex);
|
|
1310
|
+
const retained = [];
|
|
1311
|
+
const files = {};
|
|
1312
|
+
let sslMode;
|
|
1313
|
+
for (const param of params) {
|
|
1314
|
+
const eq = param.indexOf("=");
|
|
1315
|
+
const key = eq === -1 ? param : param.slice(0, eq);
|
|
1316
|
+
const value = eq === -1 ? "" : decodeURIComponent(param.slice(eq + 1));
|
|
1317
|
+
if (key === "sslmode") {
|
|
1318
|
+
sslMode = value;
|
|
1319
|
+
} else if (key === "sslcert" || key === "sslkey" || key === "sslrootcert") {
|
|
1320
|
+
files[key] = value;
|
|
1321
|
+
} else if (key === "ssl") {
|
|
1322
|
+
// consumed: sslmode governs TLS when present
|
|
1323
|
+
} else {
|
|
1324
|
+
retained.push(param);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
const rebuilt = retained.length ? `${base}?${retained.join("&")}` : base;
|
|
1328
|
+
return { connectionString: rebuilt, ssl: sslOptionForMode(sslMode, files) };
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function sslOptionForMode(mode, files) {
|
|
1332
|
+
let ssl;
|
|
1333
|
+
switch (mode) {
|
|
1334
|
+
case "disable":
|
|
1335
|
+
return false;
|
|
1336
|
+
case "prefer":
|
|
1337
|
+
case "require":
|
|
1338
|
+
case "verify-full":
|
|
1339
|
+
ssl = { rejectUnauthorized: true };
|
|
1340
|
+
break;
|
|
1341
|
+
case "verify-ca":
|
|
1342
|
+
// Without a pinned CA, chain-only verification accepts a certificate
|
|
1343
|
+
// from any trusted CA for any host, which authenticates nothing.
|
|
1344
|
+
if (!files.sslrootcert) {
|
|
1345
|
+
throw new TokenStoreError(
|
|
1346
|
+
"sslmode=verify-ca requires sslrootcert=<path>; supply the CA to " +
|
|
1347
|
+
"pin, or use sslmode=require / verify-full for " +
|
|
1348
|
+
"hostname-verified TLS"
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
ssl = { rejectUnauthorized: true, checkServerIdentity: () => undefined };
|
|
1352
|
+
break;
|
|
1353
|
+
case "no-verify":
|
|
1354
|
+
ssl = { rejectUnauthorized: false };
|
|
1355
|
+
break;
|
|
1356
|
+
default:
|
|
1357
|
+
throw new TokenStoreError(
|
|
1358
|
+
`Unrecognized sslmode value \`${mode}\`; expected one of: ` +
|
|
1359
|
+
"disable, prefer, require, verify-ca, verify-full, no-verify"
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
const fs =
|
|
1363
|
+
files.sslcert || files.sslkey || files.sslrootcert ? require("fs") : null;
|
|
1364
|
+
if (files.sslcert) {
|
|
1365
|
+
ssl.cert = fs.readFileSync(files.sslcert).toString();
|
|
1366
|
+
}
|
|
1367
|
+
if (files.sslkey) {
|
|
1368
|
+
ssl.key = fs.readFileSync(files.sslkey).toString();
|
|
1369
|
+
}
|
|
1370
|
+
if (files.sslrootcert) {
|
|
1371
|
+
ssl.ca = fs.readFileSync(files.sslrootcert).toString();
|
|
1372
|
+
}
|
|
1373
|
+
return ssl;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1270
1376
|
/**
|
|
1271
1377
|
* Create a PostgresTokenStore instance from a config object.
|
|
1272
1378
|
*
|
|
@@ -1280,8 +1386,10 @@ class PostgresTokenStore {
|
|
|
1280
1386
|
* @returns {Promise<PostgresTokenStore>}
|
|
1281
1387
|
*/
|
|
1282
1388
|
async function createPostgresTokenStore(config, identity, logger = null) {
|
|
1389
|
+
const { connectionString, ssl } = extractSslMode(config.connectionString);
|
|
1283
1390
|
const pool = new pg.Pool({
|
|
1284
|
-
connectionString
|
|
1391
|
+
connectionString,
|
|
1392
|
+
...(ssl !== undefined && { ssl }),
|
|
1285
1393
|
max: config.maxPoolSize,
|
|
1286
1394
|
connectionTimeoutMillis: config.createTimeoutSecs * 1000,
|
|
1287
1395
|
idleTimeoutMillis: config.recycleTimeoutSecs * 1000,
|
|
@@ -1392,6 +1392,112 @@ class PostgresTreeStore {
|
|
|
1392
1392
|
}
|
|
1393
1393
|
}
|
|
1394
1394
|
|
|
1395
|
+
/**
|
|
1396
|
+
* Translates the `sslmode` URI parameter into pg's `ssl` option.
|
|
1397
|
+
* The pinned `pg` maps `require` to verified TLS but warns that pg v9 reverts
|
|
1398
|
+
* it to libpq semantics (no verification); handling `sslmode` here keeps the
|
|
1399
|
+
* documented guarantees stable across driver versions. The parameter (and the
|
|
1400
|
+
* `ssl`/`sslcert`/`sslkey`/`sslrootcert` parameters folded into the option)
|
|
1401
|
+
* are stripped from the URI because a connection string's ssl settings
|
|
1402
|
+
* override the explicit option.
|
|
1403
|
+
*
|
|
1404
|
+
* Spellings mirror the Rust SDK where pg has an equivalent:
|
|
1405
|
+
* - absent or `disable`: no TLS
|
|
1406
|
+
* - `prefer` / `require` / `verify-full`: TLS with certificate chain and
|
|
1407
|
+
* hostname verification (pg cannot fall back to plaintext, so `prefer`
|
|
1408
|
+
* behaves like `require`)
|
|
1409
|
+
* - `verify-ca`: chain verification only, hostname not checked
|
|
1410
|
+
* - `no-verify`: TLS without certificate verification (explicit opt-in)
|
|
1411
|
+
*
|
|
1412
|
+
* An unrecognized value throws rather than silently changing the TLS level.
|
|
1413
|
+
* Pin a private CA with `sslrootcert=<path>` (required for `verify-ca`); for
|
|
1414
|
+
* the hostname-verified modes, adding the CA to Node's trust store
|
|
1415
|
+
* (e.g. NODE_EXTRA_CA_CERTS) also works, though it extends the store rather
|
|
1416
|
+
* than pinning.
|
|
1417
|
+
*/
|
|
1418
|
+
function extractSslMode(connectionString) {
|
|
1419
|
+
if (
|
|
1420
|
+
!connectionString.startsWith("postgres://") &&
|
|
1421
|
+
!connectionString.startsWith("postgresql://")
|
|
1422
|
+
) {
|
|
1423
|
+
return { connectionString, ssl: undefined };
|
|
1424
|
+
}
|
|
1425
|
+
const queryIndex = connectionString.indexOf("?");
|
|
1426
|
+
if (queryIndex === -1) {
|
|
1427
|
+
return { connectionString, ssl: undefined };
|
|
1428
|
+
}
|
|
1429
|
+
const params = connectionString.slice(queryIndex + 1).split("&");
|
|
1430
|
+
if (!params.some((param) => param.startsWith("sslmode="))) {
|
|
1431
|
+
return { connectionString, ssl: undefined };
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const base = connectionString.slice(0, queryIndex);
|
|
1435
|
+
const retained = [];
|
|
1436
|
+
const files = {};
|
|
1437
|
+
let sslMode;
|
|
1438
|
+
for (const param of params) {
|
|
1439
|
+
const eq = param.indexOf("=");
|
|
1440
|
+
const key = eq === -1 ? param : param.slice(0, eq);
|
|
1441
|
+
const value = eq === -1 ? "" : decodeURIComponent(param.slice(eq + 1));
|
|
1442
|
+
if (key === "sslmode") {
|
|
1443
|
+
sslMode = value;
|
|
1444
|
+
} else if (key === "sslcert" || key === "sslkey" || key === "sslrootcert") {
|
|
1445
|
+
files[key] = value;
|
|
1446
|
+
} else if (key === "ssl") {
|
|
1447
|
+
// consumed: sslmode governs TLS when present
|
|
1448
|
+
} else {
|
|
1449
|
+
retained.push(param);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const rebuilt = retained.length ? `${base}?${retained.join("&")}` : base;
|
|
1453
|
+
return { connectionString: rebuilt, ssl: sslOptionForMode(sslMode, files) };
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function sslOptionForMode(mode, files) {
|
|
1457
|
+
let ssl;
|
|
1458
|
+
switch (mode) {
|
|
1459
|
+
case "disable":
|
|
1460
|
+
return false;
|
|
1461
|
+
case "prefer":
|
|
1462
|
+
case "require":
|
|
1463
|
+
case "verify-full":
|
|
1464
|
+
ssl = { rejectUnauthorized: true };
|
|
1465
|
+
break;
|
|
1466
|
+
case "verify-ca":
|
|
1467
|
+
// Without a pinned CA, chain-only verification accepts a certificate
|
|
1468
|
+
// from any trusted CA for any host, which authenticates nothing.
|
|
1469
|
+
if (!files.sslrootcert) {
|
|
1470
|
+
throw new TreeStoreError(
|
|
1471
|
+
"sslmode=verify-ca requires sslrootcert=<path>; supply the CA to " +
|
|
1472
|
+
"pin, or use sslmode=require / verify-full for " +
|
|
1473
|
+
"hostname-verified TLS"
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
ssl = { rejectUnauthorized: true, checkServerIdentity: () => undefined };
|
|
1477
|
+
break;
|
|
1478
|
+
case "no-verify":
|
|
1479
|
+
ssl = { rejectUnauthorized: false };
|
|
1480
|
+
break;
|
|
1481
|
+
default:
|
|
1482
|
+
throw new TreeStoreError(
|
|
1483
|
+
`Unrecognized sslmode value \`${mode}\`; expected one of: ` +
|
|
1484
|
+
"disable, prefer, require, verify-ca, verify-full, no-verify"
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
const fs =
|
|
1488
|
+
files.sslcert || files.sslkey || files.sslrootcert ? require("fs") : null;
|
|
1489
|
+
if (files.sslcert) {
|
|
1490
|
+
ssl.cert = fs.readFileSync(files.sslcert).toString();
|
|
1491
|
+
}
|
|
1492
|
+
if (files.sslkey) {
|
|
1493
|
+
ssl.key = fs.readFileSync(files.sslkey).toString();
|
|
1494
|
+
}
|
|
1495
|
+
if (files.sslrootcert) {
|
|
1496
|
+
ssl.ca = fs.readFileSync(files.sslrootcert).toString();
|
|
1497
|
+
}
|
|
1498
|
+
return ssl;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1395
1501
|
/**
|
|
1396
1502
|
* Create a PostgresTreeStore instance from a config object.
|
|
1397
1503
|
*
|
|
@@ -1405,8 +1511,10 @@ class PostgresTreeStore {
|
|
|
1405
1511
|
* @returns {Promise<PostgresTreeStore>}
|
|
1406
1512
|
*/
|
|
1407
1513
|
async function createPostgresTreeStore(config, identity, logger = null) {
|
|
1514
|
+
const { connectionString, ssl } = extractSslMode(config.connectionString);
|
|
1408
1515
|
const pool = new pg.Pool({
|
|
1409
|
-
connectionString
|
|
1516
|
+
connectionString,
|
|
1517
|
+
...(ssl !== undefined && { ssl }),
|
|
1410
1518
|
max: config.maxPoolSize,
|
|
1411
1519
|
connectionTimeoutMillis: config.createTimeoutSecs * 1000,
|
|
1412
1520
|
idleTimeoutMillis: config.recycleTimeoutSecs * 1000,
|
package/nodejs/storage/index.cjs
CHANGED
|
@@ -700,7 +700,7 @@ class SqliteStorage {
|
|
|
700
700
|
listDeposits() {
|
|
701
701
|
try {
|
|
702
702
|
const stmt = this.db.prepare(`
|
|
703
|
-
SELECT txid, vout, amount_sats, is_mature, claim_error, refund_tx, refund_tx_id, instant_claim_status
|
|
703
|
+
SELECT txid, vout, amount_sats, is_mature, claim_error, refund_tx, refund_tx_id, instant_claim_status, refund_state
|
|
704
704
|
FROM unclaimed_deposits
|
|
705
705
|
`);
|
|
706
706
|
|
|
@@ -717,6 +717,7 @@ class SqliteStorage {
|
|
|
717
717
|
instantClaimStatus: row.instant_claim_status
|
|
718
718
|
? JSON.parse(row.instant_claim_status)
|
|
719
719
|
: null,
|
|
720
|
+
refundState: row.refund_state ? JSON.parse(row.refund_state) : null,
|
|
720
721
|
}))
|
|
721
722
|
);
|
|
722
723
|
} catch (error) {
|
|
@@ -731,7 +732,7 @@ class SqliteStorage {
|
|
|
731
732
|
if (payload.type === "claimError") {
|
|
732
733
|
const stmt = this.db.prepare(`
|
|
733
734
|
UPDATE unclaimed_deposits
|
|
734
|
-
SET claim_error =
|
|
735
|
+
SET claim_error = ?
|
|
735
736
|
WHERE txid = ? AND vout = ?
|
|
736
737
|
`);
|
|
737
738
|
|
|
@@ -739,11 +740,17 @@ class SqliteStorage {
|
|
|
739
740
|
} else if (payload.type === "refund") {
|
|
740
741
|
const stmt = this.db.prepare(`
|
|
741
742
|
UPDATE unclaimed_deposits
|
|
742
|
-
SET refund_tx = ?, refund_tx_id = ?, claim_error = NULL
|
|
743
|
+
SET refund_tx = ?, refund_tx_id = ?, refund_state = ?, claim_error = NULL
|
|
743
744
|
WHERE txid = ? AND vout = ?
|
|
744
745
|
`);
|
|
745
746
|
|
|
746
|
-
stmt.run(
|
|
747
|
+
stmt.run(
|
|
748
|
+
payload.refundTx,
|
|
749
|
+
payload.refundTxid,
|
|
750
|
+
JSON.stringify(payload.state),
|
|
751
|
+
txid,
|
|
752
|
+
vout
|
|
753
|
+
);
|
|
747
754
|
} else if (payload.type === "instantClaim") {
|
|
748
755
|
const stmt = this.db.prepare(`
|
|
749
756
|
UPDATE unclaimed_deposits
|
|
@@ -752,6 +759,14 @@ class SqliteStorage {
|
|
|
752
759
|
`);
|
|
753
760
|
|
|
754
761
|
stmt.run(JSON.stringify(payload.status), txid, vout);
|
|
762
|
+
} else if (payload.type === "refundBroadcastState") {
|
|
763
|
+
const stmt = this.db.prepare(`
|
|
764
|
+
UPDATE unclaimed_deposits
|
|
765
|
+
SET refund_state = ?
|
|
766
|
+
WHERE txid = ? AND vout = ? AND refund_tx_id = ?
|
|
767
|
+
`);
|
|
768
|
+
|
|
769
|
+
stmt.run(JSON.stringify(payload.state), txid, vout, payload.refundTxid);
|
|
755
770
|
} else {
|
|
756
771
|
return Promise.reject(
|
|
757
772
|
new StorageError(`Unknown payload type: ${payload.type}`)
|
|
@@ -486,6 +486,20 @@ class MigrationManager {
|
|
|
486
486
|
`ALTER TABLE unclaimed_deposits ADD COLUMN instant_claim_status TEXT`,
|
|
487
487
|
],
|
|
488
488
|
},
|
|
489
|
+
{
|
|
490
|
+
// Only the Declined shape changed, and clearing it costs at most one
|
|
491
|
+
// extra quote. Submitted rows are left alone: their shape is unchanged
|
|
492
|
+
// and they are the only guard against re-claiming a settling deposit.
|
|
493
|
+
name: "Clear declined instant claim status after its shape changed",
|
|
494
|
+
sql: [
|
|
495
|
+
`UPDATE unclaimed_deposits SET instant_claim_status = NULL
|
|
496
|
+
WHERE LOWER(instant_claim_status) LIKE '%declined%'`,
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: "Add refund state to unclaimed_deposits",
|
|
501
|
+
sql: [`ALTER TABLE unclaimed_deposits ADD COLUMN refund_state TEXT`],
|
|
502
|
+
},
|
|
489
503
|
];
|
|
490
504
|
}
|
|
491
505
|
}
|
package/package.json
CHANGED