@breeztech/breez-sdk-spark 0.23.0 → 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.
@@ -93,6 +93,13 @@ class BitcoinChainServiceHandle {
93
93
  const ret = wasm.bitcoinchainservicehandle_recommendedFees(this.__wbg_ptr);
94
94
  return ret;
95
95
  }
96
+ /**
97
+ * @returns {Promise<any>}
98
+ */
99
+ tipHeight() {
100
+ const ret = wasm.bitcoinchainservicehandle_tipHeight(this.__wbg_ptr);
101
+ return ret;
102
+ }
96
103
  }
97
104
  if (Symbol.dispose) BitcoinChainServiceHandle.prototype[Symbol.dispose] = BitcoinChainServiceHandle.prototype.free;
98
105
  exports.BitcoinChainServiceHandle = BitcoinChainServiceHandle;
@@ -241,6 +248,14 @@ class BreezSdk {
241
248
  const ret = wasm.breezsdk_exportUnilateralExitState(this.__wbg_ptr);
242
249
  return ret;
243
250
  }
251
+ /**
252
+ * @param {FetchClaimDepositQuoteRequest} request
253
+ * @returns {Promise<FetchClaimDepositQuoteResponse>}
254
+ */
255
+ fetchClaimDepositQuote(request) {
256
+ const ret = wasm.breezsdk_fetchClaimDepositQuote(this.__wbg_ptr, request);
257
+ return ret;
258
+ }
244
259
  /**
245
260
  * @param {FetchConversionLimitsRequest} request
246
261
  * @returns {Promise<FetchConversionLimitsResponse>}
@@ -1231,7 +1246,10 @@ class PasskeyClient {
1231
1246
  var ptr0 = isLikeNone(breez_api_key) ? 0 : passStringToWasm0(breez_api_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1232
1247
  var len0 = WASM_VECTOR_LEN;
1233
1248
  const ret = wasm.passkeyclient_new(prf_provider, ptr0, len0, isLikeNone(config) ? 0 : addToExternrefTable0(config));
1234
- this.__wbg_ptr = ret;
1249
+ if (ret[2]) {
1250
+ throw takeFromExternrefTable0(ret[1]);
1251
+ }
1252
+ this.__wbg_ptr = ret[0];
1235
1253
  PasskeyClientFinalization.register(this, this.__wbg_ptr, this);
1236
1254
  return this;
1237
1255
  }
@@ -2141,10 +2159,6 @@ function __wbg_get_imports() {
2141
2159
  const ret = arg0.applyPaymentUpdate(arg1);
2142
2160
  return ret;
2143
2161
  }, arguments); },
2144
- __wbg_arrayBuffer_87e3ac06d961f7a0: function() { return handleError(function (arg0) {
2145
- const ret = arg0.arrayBuffer();
2146
- return ret;
2147
- }, arguments); },
2148
2162
  __wbg_beforeSend_e8a50acd6afd73ed: function() { return handleError(function (arg0, arg1, arg2) {
2149
2163
  var v0 = getArrayJsValueFromWasm0(arg1, arg2).slice();
2150
2164
  wasm.__wbindgen_free(arg1, arg2 * 4, 4);
@@ -3543,10 +3557,6 @@ function __wbg_get_imports() {
3543
3557
  const ret = arg0.syncUpdateRecordFromIncoming(arg1);
3544
3558
  return ret;
3545
3559
  }, arguments); },
3546
- __wbg_text_de416916b5c06490: function() { return handleError(function (arg0) {
3547
- const ret = arg0.text();
3548
- return ret;
3549
- }, arguments); },
3550
3560
  __wbg_then_20a157d939b514f5: function(arg0, arg1) {
3551
3561
  const ret = arg0.then(arg1);
3552
3562
  return ret;
@@ -3555,6 +3565,10 @@ function __wbg_get_imports() {
3555
3565
  const ret = arg0.then(arg1, arg2);
3556
3566
  return ret;
3557
3567
  },
3568
+ __wbg_tipHeight_e14ddf8ed9a68d5a: function() { return handleError(function (arg0) {
3569
+ const ret = arg0.tipHeight();
3570
+ return ret;
3571
+ }, arguments); },
3558
3572
  __wbg_toString_15656af8d8e71f16: function(arg0, arg1, arg2) {
3559
3573
  const ret = arg1.toString(arg2);
3560
3574
  const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -3662,27 +3676,27 @@ function __wbg_get_imports() {
3662
3676
  return ret;
3663
3677
  },
3664
3678
  __wbindgen_cast_0000000000000002: function(arg0, arg1) {
3665
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 395, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3679
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 400, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3666
3680
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2);
3667
3681
  return ret;
3668
3682
  },
3669
3683
  __wbindgen_cast_0000000000000003: function(arg0, arg1) {
3670
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("CloseEvent")], shim_idx: 395, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3684
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("CloseEvent")], shim_idx: 400, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3671
3685
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_2);
3672
3686
  return ret;
3673
3687
  },
3674
3688
  __wbindgen_cast_0000000000000004: function(arg0, arg1) {
3675
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("ErrorEvent")], shim_idx: 395, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3689
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("ErrorEvent")], shim_idx: 400, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3676
3690
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_3);
3677
3691
  return ret;
3678
3692
  },
3679
3693
  __wbindgen_cast_0000000000000005: function(arg0, arg1) {
3680
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 395, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3694
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 400, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3681
3695
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_4);
3682
3696
  return ret;
3683
3697
  },
3684
3698
  __wbindgen_cast_0000000000000006: function(arg0, arg1) {
3685
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 395, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3699
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 400, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3686
3700
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_5);
3687
3701
  return ret;
3688
3702
  },
@@ -3707,12 +3721,12 @@ function __wbg_get_imports() {
3707
3721
  return ret;
3708
3722
  },
3709
3723
  __wbindgen_cast_000000000000000b: function(arg0, arg1) {
3710
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 400, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3711
- const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h1d6669a7b693932a);
3724
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 405, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
3725
+ const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h484cd36e13f37bd7);
3712
3726
  return ret;
3713
3727
  },
3714
3728
  __wbindgen_cast_000000000000000c: function(arg0, arg1) {
3715
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 429, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
3729
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 434, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
3716
3730
  const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0fa3f876e31d2a3e);
3717
3731
  return ret;
3718
3732
  },
@@ -3804,8 +3818,8 @@ function __wbg_get_imports() {
3804
3818
  };
3805
3819
  }
3806
3820
 
3807
- function wasm_bindgen__convert__closures_____invoke__h1d6669a7b693932a(arg0, arg1) {
3808
- wasm.wasm_bindgen__convert__closures_____invoke__h1d6669a7b693932a(arg0, arg1);
3821
+ function wasm_bindgen__convert__closures_____invoke__h484cd36e13f37bd7(arg0, arg1) {
3822
+ wasm.wasm_bindgen__convert__closures_____invoke__h484cd36e13f37bd7(arg0, arg1);
3809
3823
  }
3810
3824
 
3811
3825
  function wasm_bindgen__convert__closures_____invoke__h0fa3f876e31d2a3e(arg0, arg1) {
Binary file
@@ -18,6 +18,7 @@ export const bitcoinchainservicehandle_getOutspend: (a: number, b: number, c: nu
18
18
  export const bitcoinchainservicehandle_getTransactionHex: (a: number, b: number, c: number) => any;
19
19
  export const bitcoinchainservicehandle_getTransactionStatus: (a: number, b: number, c: number) => any;
20
20
  export const bitcoinchainservicehandle_recommendedFees: (a: number) => any;
21
+ export const bitcoinchainservicehandle_tipHeight: (a: number) => any;
21
22
  export const breezsdk_addContact: (a: number, b: any) => any;
22
23
  export const breezsdk_addEventListener: (a: number, b: any) => any;
23
24
  export const breezsdk_authorizeLightningAddressTransfer: (a: number, b: any) => any;
@@ -34,6 +35,7 @@ export const breezsdk_deleteContact: (a: number, b: number, c: number) => any;
34
35
  export const breezsdk_deleteLightningAddress: (a: number) => any;
35
36
  export const breezsdk_disconnect: (a: number) => any;
36
37
  export const breezsdk_exportUnilateralExitState: (a: number) => any;
38
+ export const breezsdk_fetchClaimDepositQuote: (a: number, b: any) => any;
37
39
  export const breezsdk_fetchConversionLimits: (a: number, b: any) => any;
38
40
  export const breezsdk_getCrossChainRoutes: (a: number, b: any) => any;
39
41
  export const breezsdk_getInfo: (a: number, b: any) => any;
@@ -128,7 +130,7 @@ export const newSharedSdkContext: (a: any) => any;
128
130
  export const passkeyclient_checkAvailability: (a: number) => any;
129
131
  export const passkeyclient_connectWithPasskey: (a: number, b: any) => any;
130
132
  export const passkeyclient_labels: (a: number) => number;
131
- export const passkeyclient_new: (a: any, b: number, c: number, d: number) => number;
133
+ export const passkeyclient_new: (a: any, b: number, c: number, d: number) => [number, number, number];
132
134
  export const passkeyclient_register: (a: number, b: any) => any;
133
135
  export const passkeyclient_signIn: (a: number, b: any) => any;
134
136
  export const passkeyclient_supportsImmediateMediation: (a: number) => any;
@@ -181,8 +183,8 @@ export const intounderlyingsource_cancel: (a: number) => void;
181
183
  export const intounderlyingsource_pull: (a: number, b: any) => any;
182
184
  export const __wbg_externalbreezsignerhandle_free: (a: number, b: number) => void;
183
185
  export const __wbg_defaultsessionstore_free: (a: number, b: number) => void;
184
- export const __wbg_externalsparksignerhandle_free: (a: number, b: number) => void;
185
186
  export const __wbg_externalsigningsignerhandle_free: (a: number, b: number) => void;
187
+ export const __wbg_externalsparksignerhandle_free: (a: number, b: number) => void;
186
188
  export const __wbg_signingonlyexternalsigners_free: (a: number, b: number) => void;
187
189
  export const signingonlyexternalsigners_breezSigner: (a: number) => number;
188
190
  export const signingonlyexternalsigners_sparkSigner: (a: number) => number;
@@ -197,7 +199,7 @@ export const wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_2: (a
197
199
  export const wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_3: (a: number, b: number, c: any) => void;
198
200
  export const wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_4: (a: number, b: number, c: any) => void;
199
201
  export const wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_5: (a: number, b: number, c: any) => void;
200
- export const wasm_bindgen__convert__closures_____invoke__h1d6669a7b693932a: (a: number, b: number) => void;
202
+ export const wasm_bindgen__convert__closures_____invoke__h484cd36e13f37bd7: (a: number, b: number) => void;
201
203
  export const wasm_bindgen__convert__closures_____invoke__h0fa3f876e31d2a3e: (a: number, b: number) => void;
202
204
  export const __wbindgen_malloc: (a: number, b: number) => number;
203
205
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
@@ -801,7 +801,7 @@ class MysqlStorage {
801
801
  async listDeposits() {
802
802
  try {
803
803
  const [rows] = await this.pool.query(
804
- "SELECT txid, vout, amount_sats, is_mature, claim_error, refund_tx, refund_tx_id, instant_claim_status FROM brz_unclaimed_deposits WHERE user_id = ?",
804
+ "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 = ?",
805
805
  [this.identity]
806
806
  );
807
807
 
@@ -815,6 +815,7 @@ class MysqlStorage {
815
815
  refundTx: row.refund_tx,
816
816
  refundTxId: row.refund_tx_id,
817
817
  instantClaimStatus: parseJson(row.instant_claim_status),
818
+ refundState: parseJson(row.refund_state),
818
819
  }));
819
820
  } catch (error) {
820
821
  throw new StorageError(
@@ -829,16 +830,23 @@ class MysqlStorage {
829
830
  if (payload.type === "claimError") {
830
831
  await this.pool.query(
831
832
  `UPDATE brz_unclaimed_deposits
832
- SET claim_error = ?, refund_tx = NULL, refund_tx_id = NULL
833
+ SET claim_error = ?
833
834
  WHERE user_id = ? AND txid = ? AND vout = ?`,
834
835
  [JSON.stringify(payload.error), this.identity, txid, vout]
835
836
  );
836
837
  } else if (payload.type === "refund") {
837
838
  await this.pool.query(
838
839
  `UPDATE brz_unclaimed_deposits
839
- SET refund_tx = ?, refund_tx_id = ?, claim_error = NULL
840
+ SET refund_tx = ?, refund_tx_id = ?, refund_state = ?, claim_error = NULL
840
841
  WHERE user_id = ? AND txid = ? AND vout = ?`,
841
- [payload.refundTx, payload.refundTxid, this.identity, txid, vout]
842
+ [
843
+ payload.refundTx,
844
+ payload.refundTxid,
845
+ JSON.stringify(payload.state),
846
+ this.identity,
847
+ txid,
848
+ vout,
849
+ ]
842
850
  );
843
851
  } else if (payload.type === "instantClaim") {
844
852
  await this.pool.query(
@@ -847,6 +855,19 @@ class MysqlStorage {
847
855
  WHERE user_id = ? AND txid = ? AND vout = ?`,
848
856
  [JSON.stringify(payload.status), this.identity, txid, vout]
849
857
  );
858
+ } else if (payload.type === "refundBroadcastState") {
859
+ await this.pool.query(
860
+ `UPDATE brz_unclaimed_deposits
861
+ SET refund_state = ?
862
+ WHERE user_id = ? AND txid = ? AND vout = ? AND refund_tx_id = ?`,
863
+ [
864
+ JSON.stringify(payload.state),
865
+ this.identity,
866
+ txid,
867
+ vout,
868
+ payload.refundTxid,
869
+ ]
870
+ );
850
871
  } else {
851
872
  throw new StorageError(`Unknown payload type: ${payload.type}`);
852
873
  }
@@ -1558,13 +1579,120 @@ function defaultMysqlStorageConfig(connectionString) {
1558
1579
  };
1559
1580
  }
1560
1581
 
1582
+ /**
1583
+ * Translates the `ssl-mode` URL parameter into mysql2's `ssl` option.
1584
+ * mysql2 does not understand `ssl-mode`, so it is stripped from the URI here.
1585
+ *
1586
+ * Spellings mirror the Rust SDK:
1587
+ * - absent or `disabled`: no TLS
1588
+ * - `preferred` / `required` / `verify_identity`: TLS with certificate chain
1589
+ * and hostname verification
1590
+ * - `verify_ca`: chain verification only
1591
+ * - `no-verify`: TLS without certificate verification (explicit opt-in)
1592
+ *
1593
+ * An unrecognized value throws rather than silently downgrading to plaintext.
1594
+ * Pin a private CA with `ssl-ca=<path>` (required for `verify_ca`); for the
1595
+ * hostname-verified modes, adding the CA to Node's trust store
1596
+ * (e.g. NODE_EXTRA_CA_CERTS) also works, though it extends the store rather
1597
+ * than pinning.
1598
+ */
1599
+ function extractSslMode(connectionString) {
1600
+ const queryIndex = connectionString.indexOf("?");
1601
+ if (queryIndex === -1) {
1602
+ return { connectionString, ssl: undefined };
1603
+ }
1604
+ const base = connectionString.slice(0, queryIndex);
1605
+ const retained = [];
1606
+ let sslMode;
1607
+ let sslCaPath;
1608
+ for (const param of connectionString.slice(queryIndex + 1).split("&")) {
1609
+ const eq = param.indexOf("=");
1610
+ const key = (eq === -1 ? param : param.slice(0, eq)).toLowerCase();
1611
+ if (
1612
+ eq !== -1 &&
1613
+ (key === "ssl-mode" || key === "ssl_mode" || key === "sslmode")
1614
+ ) {
1615
+ sslMode = param.slice(eq + 1).toLowerCase();
1616
+ } else if (
1617
+ eq !== -1 &&
1618
+ (key === "ssl-ca" || key === "ssl_ca" || key === "sslca")
1619
+ ) {
1620
+ sslCaPath = decodeURIComponent(param.slice(eq + 1));
1621
+ } else {
1622
+ retained.push(param);
1623
+ }
1624
+ }
1625
+ if (sslMode === undefined && sslCaPath === undefined) {
1626
+ return { connectionString, ssl: undefined };
1627
+ }
1628
+ const rebuilt = retained.length ? `${base}?${retained.join("&")}` : base;
1629
+ if (sslMode === undefined) {
1630
+ // ssl-ca alone is stripped (mysql2 would reject it as an unknown option)
1631
+ // but has no effect without an ssl-mode.
1632
+ return { connectionString: rebuilt, ssl: undefined };
1633
+ }
1634
+ const ca =
1635
+ sslCaPath === undefined
1636
+ ? undefined
1637
+ : require("fs").readFileSync(sslCaPath).toString();
1638
+ return { connectionString: rebuilt, ssl: sslOptionForMode(sslMode, ca) };
1639
+ }
1640
+
1641
+ function sslOptionForMode(mode, ca) {
1642
+ switch (mode) {
1643
+ case "disabled":
1644
+ case "disable":
1645
+ return undefined;
1646
+ case "preferred":
1647
+ case "prefer":
1648
+ case "required":
1649
+ case "require":
1650
+ case "verify_identity":
1651
+ case "verify-identity":
1652
+ case "verifyidentity":
1653
+ case "verify-full":
1654
+ case "verify_full":
1655
+ // mysql2 checks the hostname only when verifyIdentity is set;
1656
+ // rejectUnauthorized alone verifies just the chain.
1657
+ return {
1658
+ rejectUnauthorized: true,
1659
+ verifyIdentity: true,
1660
+ ...(ca !== undefined && { ca }),
1661
+ };
1662
+ case "verify_ca":
1663
+ case "verify-ca":
1664
+ case "verifyca":
1665
+ // Without a pinned CA, chain-only verification accepts a certificate
1666
+ // from any trusted CA for any host, which authenticates nothing.
1667
+ if (ca === undefined) {
1668
+ throw new StorageError(
1669
+ "ssl-mode=verify_ca requires ssl-ca=<path>; supply the CA to pin, " +
1670
+ "or use ssl-mode=required / verify_identity for " +
1671
+ "hostname-verified TLS"
1672
+ );
1673
+ }
1674
+ return { rejectUnauthorized: true, ca };
1675
+ case "no-verify":
1676
+ case "no_verify":
1677
+ case "noverify":
1678
+ return { rejectUnauthorized: false };
1679
+ default:
1680
+ throw new StorageError(
1681
+ `Unrecognized ssl-mode value \`${mode}\`; expected one of: ` +
1682
+ "disabled, preferred, required, verify_ca, verify_identity, no-verify"
1683
+ );
1684
+ }
1685
+ }
1686
+
1561
1687
  /**
1562
1688
  * Create a mysql2 pool from a config object.
1563
1689
  * The returned pool can be shared across multiple store implementations.
1564
1690
  */
1565
1691
  function createMysqlPool(config) {
1692
+ const { connectionString, ssl } = extractSslMode(config.connectionString);
1566
1693
  return mysql.createPool({
1567
- uri: config.connectionString,
1694
+ uri: connectionString,
1695
+ ...(ssl !== undefined && { ssl }),
1568
1696
  connectionLimit: config.maxPoolSize,
1569
1697
  connectTimeout: (config.createTimeoutSecs || 0) * 1000 || 10000,
1570
1698
  idleTimeout: (config.recycleTimeoutSecs || 0) * 1000 || 10000,
@@ -568,6 +568,22 @@ class MysqlMigrationManager {
568
568
  `ALTER TABLE brz_unclaimed_deposits ADD COLUMN instant_claim_status JSON NULL`,
569
569
  ],
570
570
  },
571
+ {
572
+ // Only the Declined shape changed, and clearing it costs at most one
573
+ // extra quote. Submitted rows are left alone: their shape is unchanged
574
+ // and they are the only guard against re-claiming a settling deposit.
575
+ name: "Clear declined instant claim status after its shape changed",
576
+ sql: [
577
+ `UPDATE brz_unclaimed_deposits SET instant_claim_status = NULL
578
+ WHERE LOWER(CAST(instant_claim_status AS CHAR)) LIKE '%declined%'`,
579
+ ],
580
+ },
581
+ {
582
+ name: "Add refund state to brz_unclaimed_deposits",
583
+ sql: [
584
+ `ALTER TABLE brz_unclaimed_deposits ADD COLUMN refund_state JSON NULL`,
585
+ ],
586
+ },
571
587
  ];
572
588
  }
573
589
  }
@@ -1247,9 +1247,116 @@ class MysqlTokenStore {
1247
1247
  }
1248
1248
  }
1249
1249
 
1250
+ /**
1251
+ * Translates the `ssl-mode` URL parameter into mysql2's `ssl` option.
1252
+ * mysql2 does not understand `ssl-mode`, so it is stripped from the URI here.
1253
+ *
1254
+ * Spellings mirror the Rust SDK:
1255
+ * - absent or `disabled`: no TLS
1256
+ * - `preferred` / `required` / `verify_identity`: TLS with certificate chain
1257
+ * and hostname verification
1258
+ * - `verify_ca`: chain verification only
1259
+ * - `no-verify`: TLS without certificate verification (explicit opt-in)
1260
+ *
1261
+ * An unrecognized value throws rather than silently downgrading to plaintext.
1262
+ * Pin a private CA with `ssl-ca=<path>` (required for `verify_ca`); for the
1263
+ * hostname-verified modes, adding the CA to Node's trust store
1264
+ * (e.g. NODE_EXTRA_CA_CERTS) also works, though it extends the store rather
1265
+ * than pinning.
1266
+ */
1267
+ function extractSslMode(connectionString) {
1268
+ const queryIndex = connectionString.indexOf("?");
1269
+ if (queryIndex === -1) {
1270
+ return { connectionString, ssl: undefined };
1271
+ }
1272
+ const base = connectionString.slice(0, queryIndex);
1273
+ const retained = [];
1274
+ let sslMode;
1275
+ let sslCaPath;
1276
+ for (const param of connectionString.slice(queryIndex + 1).split("&")) {
1277
+ const eq = param.indexOf("=");
1278
+ const key = (eq === -1 ? param : param.slice(0, eq)).toLowerCase();
1279
+ if (
1280
+ eq !== -1 &&
1281
+ (key === "ssl-mode" || key === "ssl_mode" || key === "sslmode")
1282
+ ) {
1283
+ sslMode = param.slice(eq + 1).toLowerCase();
1284
+ } else if (
1285
+ eq !== -1 &&
1286
+ (key === "ssl-ca" || key === "ssl_ca" || key === "sslca")
1287
+ ) {
1288
+ sslCaPath = decodeURIComponent(param.slice(eq + 1));
1289
+ } else {
1290
+ retained.push(param);
1291
+ }
1292
+ }
1293
+ if (sslMode === undefined && sslCaPath === undefined) {
1294
+ return { connectionString, ssl: undefined };
1295
+ }
1296
+ const rebuilt = retained.length ? `${base}?${retained.join("&")}` : base;
1297
+ if (sslMode === undefined) {
1298
+ // ssl-ca alone is stripped (mysql2 would reject it as an unknown option)
1299
+ // but has no effect without an ssl-mode.
1300
+ return { connectionString: rebuilt, ssl: undefined };
1301
+ }
1302
+ const ca =
1303
+ sslCaPath === undefined
1304
+ ? undefined
1305
+ : require("fs").readFileSync(sslCaPath).toString();
1306
+ return { connectionString: rebuilt, ssl: sslOptionForMode(sslMode, ca) };
1307
+ }
1308
+
1309
+ function sslOptionForMode(mode, ca) {
1310
+ switch (mode) {
1311
+ case "disabled":
1312
+ case "disable":
1313
+ return undefined;
1314
+ case "preferred":
1315
+ case "prefer":
1316
+ case "required":
1317
+ case "require":
1318
+ case "verify_identity":
1319
+ case "verify-identity":
1320
+ case "verifyidentity":
1321
+ case "verify-full":
1322
+ case "verify_full":
1323
+ // mysql2 checks the hostname only when verifyIdentity is set;
1324
+ // rejectUnauthorized alone verifies just the chain.
1325
+ return {
1326
+ rejectUnauthorized: true,
1327
+ verifyIdentity: true,
1328
+ ...(ca !== undefined && { ca }),
1329
+ };
1330
+ case "verify_ca":
1331
+ case "verify-ca":
1332
+ case "verifyca":
1333
+ // Without a pinned CA, chain-only verification accepts a certificate
1334
+ // from any trusted CA for any host, which authenticates nothing.
1335
+ if (ca === undefined) {
1336
+ throw new TokenStoreError(
1337
+ "ssl-mode=verify_ca requires ssl-ca=<path>; supply the CA to pin, " +
1338
+ "or use ssl-mode=required / verify_identity for " +
1339
+ "hostname-verified TLS"
1340
+ );
1341
+ }
1342
+ return { rejectUnauthorized: true, ca };
1343
+ case "no-verify":
1344
+ case "no_verify":
1345
+ case "noverify":
1346
+ return { rejectUnauthorized: false };
1347
+ default:
1348
+ throw new TokenStoreError(
1349
+ `Unrecognized ssl-mode value \`${mode}\`; expected one of: ` +
1350
+ "disabled, preferred, required, verify_ca, verify_identity, no-verify"
1351
+ );
1352
+ }
1353
+ }
1354
+
1250
1355
  function createMysqlPool(config) {
1356
+ const { connectionString, ssl } = extractSslMode(config.connectionString);
1251
1357
  return mysql.createPool({
1252
- uri: config.connectionString,
1358
+ uri: connectionString,
1359
+ ...(ssl !== undefined && { ssl }),
1253
1360
  connectionLimit: config.maxPoolSize,
1254
1361
  connectTimeout: (config.createTimeoutSecs || 0) * 1000 || 10000,
1255
1362
  idleTimeout: (config.recycleTimeoutSecs || 0) * 1000 || 10000,
@@ -1348,10 +1348,117 @@ class MysqlTreeStore {
1348
1348
  }
1349
1349
  }
1350
1350
 
1351
+ /**
1352
+ * Translates the `ssl-mode` URL parameter into mysql2's `ssl` option.
1353
+ * mysql2 does not understand `ssl-mode`, so it is stripped from the URI here.
1354
+ *
1355
+ * Spellings mirror the Rust SDK:
1356
+ * - absent or `disabled`: no TLS
1357
+ * - `preferred` / `required` / `verify_identity`: TLS with certificate chain
1358
+ * and hostname verification
1359
+ * - `verify_ca`: chain verification only
1360
+ * - `no-verify`: TLS without certificate verification (explicit opt-in)
1361
+ *
1362
+ * An unrecognized value throws rather than silently downgrading to plaintext.
1363
+ * Pin a private CA with `ssl-ca=<path>` (required for `verify_ca`); for the
1364
+ * hostname-verified modes, adding the CA to Node's trust store
1365
+ * (e.g. NODE_EXTRA_CA_CERTS) also works, though it extends the store rather
1366
+ * than pinning.
1367
+ */
1368
+ function extractSslMode(connectionString) {
1369
+ const queryIndex = connectionString.indexOf("?");
1370
+ if (queryIndex === -1) {
1371
+ return { connectionString, ssl: undefined };
1372
+ }
1373
+ const base = connectionString.slice(0, queryIndex);
1374
+ const retained = [];
1375
+ let sslMode;
1376
+ let sslCaPath;
1377
+ for (const param of connectionString.slice(queryIndex + 1).split("&")) {
1378
+ const eq = param.indexOf("=");
1379
+ const key = (eq === -1 ? param : param.slice(0, eq)).toLowerCase();
1380
+ if (
1381
+ eq !== -1 &&
1382
+ (key === "ssl-mode" || key === "ssl_mode" || key === "sslmode")
1383
+ ) {
1384
+ sslMode = param.slice(eq + 1).toLowerCase();
1385
+ } else if (
1386
+ eq !== -1 &&
1387
+ (key === "ssl-ca" || key === "ssl_ca" || key === "sslca")
1388
+ ) {
1389
+ sslCaPath = decodeURIComponent(param.slice(eq + 1));
1390
+ } else {
1391
+ retained.push(param);
1392
+ }
1393
+ }
1394
+ if (sslMode === undefined && sslCaPath === undefined) {
1395
+ return { connectionString, ssl: undefined };
1396
+ }
1397
+ const rebuilt = retained.length ? `${base}?${retained.join("&")}` : base;
1398
+ if (sslMode === undefined) {
1399
+ // ssl-ca alone is stripped (mysql2 would reject it as an unknown option)
1400
+ // but has no effect without an ssl-mode.
1401
+ return { connectionString: rebuilt, ssl: undefined };
1402
+ }
1403
+ const ca =
1404
+ sslCaPath === undefined
1405
+ ? undefined
1406
+ : require("fs").readFileSync(sslCaPath).toString();
1407
+ return { connectionString: rebuilt, ssl: sslOptionForMode(sslMode, ca) };
1408
+ }
1409
+
1410
+ function sslOptionForMode(mode, ca) {
1411
+ switch (mode) {
1412
+ case "disabled":
1413
+ case "disable":
1414
+ return undefined;
1415
+ case "preferred":
1416
+ case "prefer":
1417
+ case "required":
1418
+ case "require":
1419
+ case "verify_identity":
1420
+ case "verify-identity":
1421
+ case "verifyidentity":
1422
+ case "verify-full":
1423
+ case "verify_full":
1424
+ // mysql2 checks the hostname only when verifyIdentity is set;
1425
+ // rejectUnauthorized alone verifies just the chain.
1426
+ return {
1427
+ rejectUnauthorized: true,
1428
+ verifyIdentity: true,
1429
+ ...(ca !== undefined && { ca }),
1430
+ };
1431
+ case "verify_ca":
1432
+ case "verify-ca":
1433
+ case "verifyca":
1434
+ // Without a pinned CA, chain-only verification accepts a certificate
1435
+ // from any trusted CA for any host, which authenticates nothing.
1436
+ if (ca === undefined) {
1437
+ throw new TreeStoreError(
1438
+ "ssl-mode=verify_ca requires ssl-ca=<path>; supply the CA to pin, " +
1439
+ "or use ssl-mode=required / verify_identity for " +
1440
+ "hostname-verified TLS"
1441
+ );
1442
+ }
1443
+ return { rejectUnauthorized: true, ca };
1444
+ case "no-verify":
1445
+ case "no_verify":
1446
+ case "noverify":
1447
+ return { rejectUnauthorized: false };
1448
+ default:
1449
+ throw new TreeStoreError(
1450
+ `Unrecognized ssl-mode value \`${mode}\`; expected one of: ` +
1451
+ "disabled, preferred, required, verify_ca, verify_identity, no-verify"
1452
+ );
1453
+ }
1454
+ }
1455
+
1351
1456
  /** Create a mysql2 pool from a config object. */
1352
1457
  function createMysqlPool(config) {
1458
+ const { connectionString, ssl } = extractSslMode(config.connectionString);
1353
1459
  return mysql.createPool({
1354
- uri: config.connectionString,
1460
+ uri: connectionString,
1461
+ ...(ssl !== undefined && { ssl }),
1355
1462
  connectionLimit: config.maxPoolSize,
1356
1463
  connectTimeout: (config.createTimeoutSecs || 0) * 1000 || 10000,
1357
1464
  idleTimeout: (config.recycleTimeoutSecs || 0) * 1000 || 10000,