@breeztech/breez-sdk-spark 0.13.11-dev1 → 0.13.12-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.
Files changed (35) hide show
  1. package/breez-sdk-spark.tgz +0 -0
  2. package/bundler/breez_sdk_spark_wasm.d.ts +124 -0
  3. package/bundler/breez_sdk_spark_wasm.js +1 -1
  4. package/bundler/breez_sdk_spark_wasm_bg.js +369 -33
  5. package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
  6. package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -7
  7. package/deno/breez_sdk_spark_wasm.d.ts +124 -0
  8. package/deno/breez_sdk_spark_wasm.js +369 -33
  9. package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
  10. package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -7
  11. package/nodejs/breez_sdk_spark_wasm.d.ts +124 -0
  12. package/nodejs/breez_sdk_spark_wasm.js +377 -33
  13. package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
  14. package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -7
  15. package/nodejs/index.js +22 -0
  16. package/nodejs/index.mjs +8 -0
  17. package/nodejs/mysql-session-manager/errors.cjs +13 -0
  18. package/nodejs/mysql-session-manager/index.cjs +144 -0
  19. package/nodejs/mysql-session-manager/migrations.cjs +102 -0
  20. package/nodejs/mysql-session-manager/package.json +9 -0
  21. package/nodejs/mysql-storage/index.cjs +1 -0
  22. package/nodejs/mysql-token-store/index.cjs +4 -12
  23. package/nodejs/mysql-tree-store/index.cjs +2 -0
  24. package/nodejs/package.json +2 -0
  25. package/nodejs/postgres-session-manager/errors.cjs +13 -0
  26. package/nodejs/postgres-session-manager/index.cjs +165 -0
  27. package/nodejs/postgres-session-manager/migrations.cjs +126 -0
  28. package/nodejs/postgres-session-manager/package.json +9 -0
  29. package/nodejs/postgres-token-store/index.cjs +2 -13
  30. package/package.json +1 -1
  31. package/ssr/index.js +48 -0
  32. package/web/breez_sdk_spark_wasm.d.ts +149 -7
  33. package/web/breez_sdk_spark_wasm.js +369 -33
  34. package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
  35. package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -7
@@ -1,11 +1,19 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const __wbg_bitcoinchainservicehandle_free: (a: number, b: number) => void;
4
5
  export const __wbg_breezsdk_free: (a: number, b: number) => void;
5
6
  export const __wbg_defaultsigner_free: (a: number, b: number) => void;
7
+ export const __wbg_mysqlconnectionpool_free: (a: number, b: number) => void;
6
8
  export const __wbg_passkey_free: (a: number, b: number) => void;
7
9
  export const __wbg_sdkbuilder_free: (a: number, b: number) => void;
10
+ export const __wbg_sspconnectionmanager_free: (a: number, b: number) => void;
8
11
  export const __wbg_tokenissuer_free: (a: number, b: number) => void;
12
+ export const bitcoinchainservicehandle_broadcastTransaction: (a: number, b: number, c: number) => any;
13
+ export const bitcoinchainservicehandle_getAddressUtxos: (a: number, b: number, c: number) => any;
14
+ export const bitcoinchainservicehandle_getTransactionHex: (a: number, b: number, c: number) => any;
15
+ export const bitcoinchainservicehandle_getTransactionStatus: (a: number, b: number, c: number) => any;
16
+ export const bitcoinchainservicehandle_recommendedFees: (a: number) => any;
9
17
  export const breezsdk_addContact: (a: number, b: any) => any;
10
18
  export const breezsdk_addEventListener: (a: number, b: any) => any;
11
19
  export const breezsdk_buyBitcoin: (a: number, b: any) => any;
@@ -52,6 +60,8 @@ export const breezsdk_updateContact: (a: number, b: any) => any;
52
60
  export const breezsdk_updateUserSettings: (a: number, b: any) => any;
53
61
  export const connect: (a: any) => any;
54
62
  export const connectWithSigner: (a: any, b: any, c: number, d: number) => any;
63
+ export const createMysqlConnectionPool: (a: any) => [number, number, number];
64
+ export const createPostgresConnectionPool: (a: any) => [number, number, number];
55
65
  export const defaultConfig: (a: any) => any;
56
66
  export const defaultExternalSigner: (a: number, b: number, c: number, d: number, e: any, f: number) => [number, number, number];
57
67
  export const defaultMysqlStorageConfig: (a: number, b: number) => any;
@@ -77,6 +87,8 @@ export const defaultsigner_staticDepositSigningKey: (a: number, b: number) => an
77
87
  export const defaultsigner_subtractSecrets: (a: number, b: any, c: any) => any;
78
88
  export const getSparkStatus: () => any;
79
89
  export const initLogging: (a: any, b: number, c: number) => any;
90
+ export const newRestChainService: (a: number, b: number, c: any, d: any, e: number) => number;
91
+ export const newSspConnectionManager: (a: number, b: number) => number;
80
92
  export const passkey_getWallet: (a: number, b: number, c: number) => any;
81
93
  export const passkey_isAvailable: (a: number) => any;
82
94
  export const passkey_listLabels: (a: number) => any;
@@ -90,10 +102,14 @@ export const sdkbuilder_withDefaultStorage: (a: number, b: number, c: number) =>
90
102
  export const sdkbuilder_withFiatService: (a: number, b: any) => number;
91
103
  export const sdkbuilder_withKeySet: (a: number, b: any) => number;
92
104
  export const sdkbuilder_withLnurlClient: (a: number, b: any) => number;
93
- export const sdkbuilder_withMysqlBackend: (a: number, b: any) => number;
105
+ export const sdkbuilder_withMysqlBackend: (a: number, b: any) => [number, number, number];
106
+ export const sdkbuilder_withMysqlConnectionPool: (a: number, b: number) => number;
94
107
  export const sdkbuilder_withPaymentObserver: (a: number, b: any) => number;
95
- export const sdkbuilder_withPostgresBackend: (a: number, b: any) => number;
108
+ export const sdkbuilder_withPostgresBackend: (a: number, b: any) => [number, number, number];
109
+ export const sdkbuilder_withPostgresConnectionPool: (a: number, b: number) => number;
96
110
  export const sdkbuilder_withRestChainService: (a: number, b: number, c: number, d: any, e: number) => number;
111
+ export const sdkbuilder_withSessionManager: (a: number, b: any) => number;
112
+ export const sdkbuilder_withSspConnectionManager: (a: number, b: number) => number;
97
113
  export const sdkbuilder_withStorage: (a: number, b: any) => number;
98
114
  export const tokenissuer_burnIssuerToken: (a: number, b: any) => any;
99
115
  export const tokenissuer_createIssuerToken: (a: number, b: any) => any;
@@ -120,16 +136,18 @@ export const intounderlyingsink_close: (a: number) => any;
120
136
  export const intounderlyingsink_write: (a: number, b: any) => any;
121
137
  export const intounderlyingsource_cancel: (a: number) => void;
122
138
  export const intounderlyingsource_pull: (a: number, b: any) => any;
139
+ export const __wbg_postgresconnectionpool_free: (a: number, b: number) => void;
123
140
  export const defaultPostgresStorageConfig: (a: number, b: number) => any;
124
- export const wasm_bindgen__convert__closures_____invoke__h27d08ee20b1285c7: (a: number, b: number, c: any) => [number, number];
125
- export const wasm_bindgen__convert__closures_____invoke__h27d08ee20b1285c7_4: (a: number, b: number, c: any) => [number, number];
126
- export const wasm_bindgen__convert__closures_____invoke__h27d08ee20b1285c7_5: (a: number, b: number, c: any) => [number, number];
127
- export const wasm_bindgen__convert__closures_____invoke__h27d08ee20b1285c7_6: (a: number, b: number, c: any) => [number, number];
141
+ export const wasm_bindgen__convert__closures_____invoke__h459c42c6ffe5f52c: (a: number, b: number, c: any) => [number, number];
142
+ export const wasm_bindgen__convert__closures_____invoke__h459c42c6ffe5f52c_4: (a: number, b: number, c: any) => [number, number];
143
+ export const wasm_bindgen__convert__closures_____invoke__h459c42c6ffe5f52c_5: (a: number, b: number, c: any) => [number, number];
144
+ export const wasm_bindgen__convert__closures_____invoke__h459c42c6ffe5f52c_6: (a: number, b: number, c: any) => [number, number];
145
+ export const wasm_bindgen__convert__closures_____invoke__h459c42c6ffe5f52c_7: (a: number, b: number, c: any) => [number, number];
128
146
  export const wasm_bindgen__convert__closures_____invoke__h41057d61edf43a32: (a: number, b: number, c: any, d: any) => void;
129
147
  export const wasm_bindgen__convert__closures_____invoke__h4819aba3eed2db57: (a: number, b: number, c: any) => void;
130
148
  export const wasm_bindgen__convert__closures_____invoke__h4819aba3eed2db57_2: (a: number, b: number, c: any) => void;
131
149
  export const wasm_bindgen__convert__closures_____invoke__h4819aba3eed2db57_3: (a: number, b: number, c: any) => void;
132
- export const wasm_bindgen__convert__closures_____invoke__h484cd36e13f37bd7: (a: number, b: number) => void;
150
+ export const wasm_bindgen__convert__closures_____invoke__h124479769cd429fd: (a: number, b: number) => void;
133
151
  export const __wbindgen_malloc: (a: number, b: number) => number;
134
152
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
135
153
  export const __wbindgen_free: (a: number, b: number, c: number) => void;
package/nodejs/index.js CHANGED
@@ -48,6 +48,17 @@ try {
48
48
  }
49
49
  }
50
50
 
51
+ // Automatically import and set up the PostgreSQL session manager for Node.js
52
+ try {
53
+ const { createPostgresSessionManager, createPostgresSessionManagerWithPool } = require('./postgres-session-manager/index.cjs');
54
+ global.createPostgresSessionManager = createPostgresSessionManager;
55
+ global.createPostgresSessionManagerWithPool = createPostgresSessionManagerWithPool;
56
+ } catch (error) {
57
+ if (error.code !== 'MODULE_NOT_FOUND') {
58
+ console.warn('Breez SDK: Failed to load PostgreSQL session manager:', error.message);
59
+ }
60
+ }
61
+
51
62
  // Automatically import and set up the MySQL storage for Node.js
52
63
  try {
53
64
  const { createMysqlStorage, createMysqlPool, createMysqlStorageWithPool } = require('./mysql-storage/index.cjs');
@@ -82,5 +93,16 @@ try {
82
93
  }
83
94
  }
84
95
 
96
+ // Automatically import and set up the MySQL session manager for Node.js
97
+ try {
98
+ const { createMysqlSessionManager, createMysqlSessionManagerWithPool } = require('./mysql-session-manager/index.cjs');
99
+ global.createMysqlSessionManager = createMysqlSessionManager;
100
+ global.createMysqlSessionManagerWithPool = createMysqlSessionManagerWithPool;
101
+ } catch (error) {
102
+ if (error.code !== 'MODULE_NOT_FOUND') {
103
+ console.warn('Breez SDK: Failed to load MySQL session manager:', error.message);
104
+ }
105
+ }
106
+
85
107
  // Export all WASM functions
86
108
  module.exports = wasmModule;
package/nodejs/index.mjs CHANGED
@@ -6,20 +6,28 @@ import pkg from './index.js';
6
6
  export const {
7
7
  connect,
8
8
  connectWithSigner,
9
+ createMysqlConnectionPool,
10
+ createPostgresConnectionPool,
9
11
  defaultConfig,
10
12
  defaultExternalSigner,
11
13
  defaultMysqlStorageConfig,
12
14
  defaultPostgresStorageConfig,
13
15
  getSparkStatus,
14
16
  initLogging,
17
+ newRestChainService,
18
+ newSspConnectionManager,
15
19
  task_worker_entry_point,
20
+ BitcoinChainServiceHandle,
16
21
  BreezSdk,
17
22
  DefaultSigner,
18
23
  IntoUnderlyingByteSource,
19
24
  IntoUnderlyingSink,
20
25
  IntoUnderlyingSource,
26
+ MysqlConnectionPool,
21
27
  Passkey,
28
+ PostgresConnectionPool,
22
29
  SdkBuilder,
30
+ SspConnectionManager,
23
31
  TokenIssuer,
24
32
  } = pkg;
25
33
 
@@ -0,0 +1,13 @@
1
+ // errors.cjs - Session manager error wrapper with cause chain support
2
+ class SessionManagerError extends Error {
3
+ constructor(message, cause = null) {
4
+ super(message);
5
+ this.name = 'SessionManagerError';
6
+ this.cause = cause;
7
+ if (Error.captureStackTrace) {
8
+ Error.captureStackTrace(this, SessionManagerError);
9
+ }
10
+ }
11
+ }
12
+
13
+ module.exports = { SessionManagerError };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * CommonJS implementation for Node.js MySQL Session Manager.
3
+ *
4
+ * Mirrors `postgres-session-manager/index.cjs` for MySQL 8.0+.
5
+ */
6
+
7
+ let mysql;
8
+ try {
9
+ const mainModule = require.main;
10
+ if (mainModule) {
11
+ mysql = mainModule.require("mysql2/promise");
12
+ } else {
13
+ mysql = require("mysql2/promise");
14
+ }
15
+ } catch (error) {
16
+ try {
17
+ mysql = require("mysql2/promise");
18
+ } catch (fallbackError) {
19
+ throw new Error(
20
+ `mysql2 not found. Please install it in your project: npm install mysql2@^3.11.0\n` +
21
+ `Original error: ${error.message}\nFallback error: ${fallbackError.message}`
22
+ );
23
+ }
24
+ }
25
+
26
+ const { SessionManagerError } = require("./errors.cjs");
27
+ const { MysqlSessionManagerMigrationManager } = require("./migrations.cjs");
28
+
29
+ class MysqlSessionManager {
30
+ /**
31
+ * @param {import('mysql2/promise').Pool} pool
32
+ * @param {Buffer|Uint8Array} identity - 33-byte secp256k1 compressed pubkey
33
+ * identifying the tenant. All reads and writes are scoped by this.
34
+ * @param {object} [logger]
35
+ */
36
+ constructor(pool, identity, logger = null) {
37
+ if (!identity || identity.length !== 33) {
38
+ throw new SessionManagerError(
39
+ "tenant identity (33-byte secp256k1 pubkey) is required"
40
+ );
41
+ }
42
+ this.pool = pool;
43
+ this.identity = Buffer.from(identity);
44
+ this.logger = logger;
45
+ }
46
+
47
+ async initialize() {
48
+ try {
49
+ const migrationManager = new MysqlSessionManagerMigrationManager(this.logger);
50
+ await migrationManager.migrate(this.pool);
51
+ return this;
52
+ } catch (error) {
53
+ throw new SessionManagerError(
54
+ `Failed to initialize MySQL session manager: ${error.message}`,
55
+ error
56
+ );
57
+ }
58
+ }
59
+
60
+ async close() {
61
+ if (this.pool) {
62
+ await this.pool.end();
63
+ this.pool = null;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
69
+ * @returns {Promise<{token: string, expiration: number} | null>}
70
+ */
71
+ async getSession(serviceIdentityKey) {
72
+ const serviceKey = _decodePubkey(serviceIdentityKey);
73
+ try {
74
+ const [rows] = await this.pool.execute(
75
+ `SELECT token, expiration FROM sessions
76
+ WHERE user_id = ? AND service_identity_key = ?`,
77
+ [this.identity, serviceKey]
78
+ );
79
+ if (!rows || rows.length === 0) {
80
+ return null;
81
+ }
82
+ const row = rows[0];
83
+ return {
84
+ token: row.token,
85
+ expiration: Number(row.expiration),
86
+ };
87
+ } catch (error) {
88
+ throw new SessionManagerError(
89
+ `Failed to read session: ${error.message}`,
90
+ error
91
+ );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
97
+ * @param {{token: string, expiration: number}} session
98
+ */
99
+ async setSession(serviceIdentityKey, session) {
100
+ const serviceKey = _decodePubkey(serviceIdentityKey);
101
+ try {
102
+ await this.pool.execute(
103
+ `INSERT INTO sessions (user_id, service_identity_key, token, expiration)
104
+ VALUES (?, ?, ?, ?)
105
+ ON DUPLICATE KEY UPDATE token = VALUES(token), expiration = VALUES(expiration)`,
106
+ [this.identity, serviceKey, session.token, session.expiration]
107
+ );
108
+ } catch (error) {
109
+ throw new SessionManagerError(
110
+ `Failed to write session: ${error.message}`,
111
+ error
112
+ );
113
+ }
114
+ }
115
+ }
116
+
117
+ function _decodePubkey(hex) {
118
+ if (typeof hex !== "string" || hex.length !== 66) {
119
+ throw new SessionManagerError(
120
+ "service_identity_key must be a 66-character hex-encoded 33-byte pubkey"
121
+ );
122
+ }
123
+ return Buffer.from(hex, "hex");
124
+ }
125
+
126
+ async function createMysqlSessionManager(poolConfig, identity, logger = null) {
127
+ const pool = mysql.createPool(poolConfig);
128
+ const manager = new MysqlSessionManager(pool, identity, logger);
129
+ await manager.initialize();
130
+ return manager;
131
+ }
132
+
133
+ async function createMysqlSessionManagerWithPool(pool, identity, logger = null) {
134
+ const manager = new MysqlSessionManager(pool, identity, logger);
135
+ await manager.initialize();
136
+ return manager;
137
+ }
138
+
139
+ module.exports = {
140
+ MysqlSessionManager,
141
+ createMysqlSessionManager,
142
+ createMysqlSessionManagerWithPool,
143
+ SessionManagerError,
144
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Session manager migrations for MySQL 8.0+. Mirrors the Rust
3
+ * `MysqlSessionManager` schema exactly.
4
+ */
5
+
6
+ const { SessionManagerError } = require("./errors.cjs");
7
+
8
+ const SESSION_MIGRATIONS_TABLE = "session_schema_migrations";
9
+ const MIGRATION_LOCK_NAME = "breez_mysql_session_manager_migration_lock";
10
+ const MIGRATION_LOCK_TIMEOUT = 60;
11
+
12
+ class MysqlSessionManagerMigrationManager {
13
+ constructor(logger = null) {
14
+ this.logger = logger;
15
+ }
16
+
17
+ /**
18
+ * @param {import('mysql2/promise').Pool} pool
19
+ */
20
+ async migrate(pool) {
21
+ const conn = await pool.getConnection();
22
+ try {
23
+ const [lockRows] = await conn.query(
24
+ "SELECT GET_LOCK(?, ?) AS acquired",
25
+ [MIGRATION_LOCK_NAME, MIGRATION_LOCK_TIMEOUT]
26
+ );
27
+ if (!lockRows || lockRows[0].acquired !== 1) {
28
+ throw new SessionManagerError(
29
+ `Failed to acquire session manager migration lock within ${MIGRATION_LOCK_TIMEOUT}s`
30
+ );
31
+ }
32
+
33
+ try {
34
+ await conn.query("START TRANSACTION");
35
+
36
+ await conn.query(`
37
+ CREATE TABLE IF NOT EXISTS \`${SESSION_MIGRATIONS_TABLE}\` (
38
+ version INT PRIMARY KEY,
39
+ applied_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
40
+ )
41
+ `);
42
+
43
+ const [versionRows] = await conn.query(
44
+ `SELECT COALESCE(MAX(version), 0) AS version FROM \`${SESSION_MIGRATIONS_TABLE}\``
45
+ );
46
+ const currentVersion = versionRows[0].version;
47
+
48
+ const migrations = this._getMigrations();
49
+
50
+ if (currentVersion >= migrations.length) {
51
+ await conn.query("COMMIT");
52
+ return;
53
+ }
54
+
55
+ for (let i = currentVersion; i < migrations.length; i++) {
56
+ const migration = migrations[i];
57
+ const version = i + 1;
58
+ for (const sql of migration.sql) {
59
+ await conn.query(sql);
60
+ }
61
+ await conn.query(
62
+ `INSERT INTO \`${SESSION_MIGRATIONS_TABLE}\` (version) VALUES (?)`,
63
+ [version]
64
+ );
65
+ }
66
+
67
+ await conn.query("COMMIT");
68
+ } catch (error) {
69
+ await conn.query("ROLLBACK").catch(() => {});
70
+ throw new SessionManagerError(
71
+ `Session manager migration failed: ${error.message}`,
72
+ error
73
+ );
74
+ } finally {
75
+ await conn
76
+ .query("SELECT RELEASE_LOCK(?)", [MIGRATION_LOCK_NAME])
77
+ .catch(() => {});
78
+ }
79
+ } finally {
80
+ conn.release();
81
+ }
82
+ }
83
+
84
+ _getMigrations() {
85
+ return [
86
+ {
87
+ name: "Create sessions table",
88
+ sql: [
89
+ `CREATE TABLE IF NOT EXISTS sessions (
90
+ user_id VARBINARY(33) NOT NULL,
91
+ service_identity_key VARBINARY(33) NOT NULL,
92
+ token TEXT NOT NULL,
93
+ expiration BIGINT NOT NULL,
94
+ PRIMARY KEY (user_id, service_identity_key)
95
+ )`,
96
+ ],
97
+ },
98
+ ];
99
+ }
100
+ }
101
+
102
+ module.exports = { MysqlSessionManagerMigrationManager };
@@ -0,0 +1,9 @@
1
+ {
2
+ "dependencies": {
3
+ "mysql2": "^3.11.0"
4
+ },
5
+ "description": "Node.js MySQL session manager implementation for Breez SDK WASM (CommonJS)",
6
+ "main": "index.cjs",
7
+ "name": "@breez-sdk/mysql-session-manager",
8
+ "version": "1.0.0"
9
+ }
@@ -147,6 +147,7 @@ class MysqlStorage {
147
147
  async _withTransaction(fn) {
148
148
  const conn = await this.pool.getConnection();
149
149
  try {
150
+ await conn.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
150
151
  await conn.beginTransaction();
151
152
  const result = await fn(conn);
152
153
  await conn.commit();
@@ -128,6 +128,7 @@ class MysqlTokenStore {
128
128
  }
129
129
  lockAcquired = true;
130
130
 
131
+ await conn.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
131
132
  await conn.beginTransaction();
132
133
  const result = await fn(conn);
133
134
  await conn.commit();
@@ -157,6 +158,7 @@ class MysqlTokenStore {
157
158
  async _withTransaction(fn) {
158
159
  const conn = await this.pool.getConnection();
159
160
  try {
161
+ await conn.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
160
162
  await conn.beginTransaction();
161
163
  const result = await fn(conn);
162
164
  await conn.commit();
@@ -518,10 +520,7 @@ class MysqlTokenStore {
518
520
 
519
521
  async insertTokenOutputs(tokenOutputs) {
520
522
  try {
521
- const conn = await this.pool.getConnection();
522
- try {
523
- await conn.beginTransaction();
524
-
523
+ await this._withTransaction(async (conn) => {
525
524
  await this._upsertMetadata(conn, tokenOutputs.metadata);
526
525
 
527
526
  const outputIds = tokenOutputs.outputs.map((o) => o.output.id);
@@ -540,14 +539,7 @@ class MysqlTokenStore {
540
539
  output
541
540
  );
542
541
  }
543
-
544
- await conn.commit();
545
- } catch (error) {
546
- await conn.rollback().catch(() => {});
547
- throw error;
548
- } finally {
549
- conn.release();
550
- }
542
+ });
551
543
  } catch (error) {
552
544
  if (error instanceof TokenStoreError) throw error;
553
545
  throw new TokenStoreError(
@@ -135,6 +135,7 @@ class MysqlTreeStore {
135
135
  }
136
136
  lockAcquired = true;
137
137
 
138
+ await conn.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
138
139
  await conn.beginTransaction();
139
140
  const result = await fn(conn);
140
141
  await conn.commit();
@@ -164,6 +165,7 @@ class MysqlTreeStore {
164
165
  async _withTransaction(fn) {
165
166
  const conn = await this.pool.getConnection();
166
167
  try {
168
+ await conn.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
167
169
  await conn.beginTransaction();
168
170
  const result = await fn(conn);
169
171
  await conn.commit();
@@ -7,9 +7,11 @@
7
7
  "postgres-storage/",
8
8
  "postgres-tree-store/",
9
9
  "postgres-token-store/",
10
+ "postgres-session-manager/",
10
11
  "mysql-storage/",
11
12
  "mysql-tree-store/",
12
13
  "mysql-token-store/",
14
+ "mysql-session-manager/",
13
15
  "index.js",
14
16
  "index.mjs"
15
17
  ],
@@ -0,0 +1,13 @@
1
+ // errors.cjs - Session manager error wrapper with cause chain support
2
+ class SessionManagerError extends Error {
3
+ constructor(message, cause = null) {
4
+ super(message);
5
+ this.name = 'SessionManagerError';
6
+ this.cause = cause;
7
+ if (Error.captureStackTrace) {
8
+ Error.captureStackTrace(this, SessionManagerError);
9
+ }
10
+ }
11
+ }
12
+
13
+ module.exports = { SessionManagerError };
@@ -0,0 +1,165 @@
1
+ /**
2
+ * CommonJS implementation for Node.js PostgreSQL Session Manager.
3
+ *
4
+ * Implements the JS-side `SessionManager` interface consumed by the Breez
5
+ * SDK WASM bindings: `getSession(serviceIdentityKey)` returns the cached
6
+ * session for the (tenant, service) pair or `null` when not found, and
7
+ * `setSession(serviceIdentityKey, session)` upserts a session.
8
+ *
9
+ * Tenant identity is bound at construction so multiple tenants can share
10
+ * a single Postgres database without leaking sessions across tenants.
11
+ */
12
+
13
+ let pg;
14
+ try {
15
+ const mainModule = require.main;
16
+ if (mainModule) {
17
+ pg = mainModule.require("pg");
18
+ } else {
19
+ pg = require("pg");
20
+ }
21
+ } catch (error) {
22
+ try {
23
+ pg = require("pg");
24
+ } catch (fallbackError) {
25
+ throw new Error(
26
+ `pg not found. Please install it in your project: npm install pg@^8.18.0\n` +
27
+ `Original error: ${error.message}\nFallback error: ${fallbackError.message}`
28
+ );
29
+ }
30
+ }
31
+
32
+ const { SessionManagerError } = require("./errors.cjs");
33
+ const { SessionManagerMigrationManager } = require("./migrations.cjs");
34
+
35
+ class PostgresSessionManager {
36
+ /**
37
+ * @param {import('pg').Pool} pool
38
+ * @param {Buffer|Uint8Array} identity - 33-byte secp256k1 compressed pubkey
39
+ * identifying the tenant. All reads and writes are scoped by this.
40
+ * @param {object} [logger]
41
+ */
42
+ constructor(pool, identity, logger = null) {
43
+ if (!identity || identity.length !== 33) {
44
+ throw new SessionManagerError(
45
+ "tenant identity (33-byte secp256k1 pubkey) is required"
46
+ );
47
+ }
48
+ this.pool = pool;
49
+ this.identity = Buffer.from(identity);
50
+ this.logger = logger;
51
+ }
52
+
53
+ async initialize() {
54
+ try {
55
+ const migrationManager = new SessionManagerMigrationManager(this.logger);
56
+ await migrationManager.migrate(this.pool);
57
+ return this;
58
+ } catch (error) {
59
+ throw new SessionManagerError(
60
+ `Failed to initialize PostgreSQL session manager: ${error.message}`,
61
+ error
62
+ );
63
+ }
64
+ }
65
+
66
+ async close() {
67
+ if (this.pool) {
68
+ await this.pool.end();
69
+ this.pool = null;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Returns the cached session for the given service identity key, or `null`
75
+ * if no session is cached. The Rust adapter maps `null` to
76
+ * `SessionManagerError::NotFound`.
77
+ * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
78
+ * @returns {Promise<{token: string, expiration: number} | null>}
79
+ */
80
+ async getSession(serviceIdentityKey) {
81
+ const serviceKey = _decodePubkey(serviceIdentityKey);
82
+ try {
83
+ const { rows } = await this.pool.query(
84
+ `SELECT token, expiration FROM sessions
85
+ WHERE user_id = $1 AND service_identity_key = $2`,
86
+ [this.identity, serviceKey]
87
+ );
88
+ if (rows.length === 0) {
89
+ return null;
90
+ }
91
+ const row = rows[0];
92
+ return {
93
+ token: row.token,
94
+ expiration: Number(row.expiration),
95
+ };
96
+ } catch (error) {
97
+ throw new SessionManagerError(
98
+ `Failed to read session: ${error.message}`,
99
+ error
100
+ );
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Upserts a session for the given service identity key.
106
+ * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
107
+ * @param {{token: string, expiration: number}} session
108
+ */
109
+ async setSession(serviceIdentityKey, session) {
110
+ const serviceKey = _decodePubkey(serviceIdentityKey);
111
+ try {
112
+ await this.pool.query(
113
+ `INSERT INTO sessions (user_id, service_identity_key, token, expiration)
114
+ VALUES ($1, $2, $3, $4)
115
+ ON CONFLICT (user_id, service_identity_key)
116
+ DO UPDATE SET token = EXCLUDED.token, expiration = EXCLUDED.expiration`,
117
+ [this.identity, serviceKey, session.token, session.expiration]
118
+ );
119
+ } catch (error) {
120
+ throw new SessionManagerError(
121
+ `Failed to write session: ${error.message}`,
122
+ error
123
+ );
124
+ }
125
+ }
126
+ }
127
+
128
+ function _decodePubkey(hex) {
129
+ if (typeof hex !== "string" || hex.length !== 66) {
130
+ throw new SessionManagerError(
131
+ "service_identity_key must be a 66-character hex-encoded 33-byte pubkey"
132
+ );
133
+ }
134
+ return Buffer.from(hex, "hex");
135
+ }
136
+
137
+ /**
138
+ * Convenience factory: creates a pool from a Pool config and returns an
139
+ * initialized `PostgresSessionManager`. Most callers should use
140
+ * `createPostgresSessionManagerWithPool` instead so the pool can be shared
141
+ * across stores.
142
+ */
143
+ async function createPostgresSessionManager(poolConfig, identity, logger = null) {
144
+ const pool = new pg.Pool(poolConfig);
145
+ const manager = new PostgresSessionManager(pool, identity, logger);
146
+ await manager.initialize();
147
+ return manager;
148
+ }
149
+
150
+ /**
151
+ * Wraps an existing pool — useful when sharing the pool with the storage,
152
+ * tree store, and token store implementations.
153
+ */
154
+ async function createPostgresSessionManagerWithPool(pool, identity, logger = null) {
155
+ const manager = new PostgresSessionManager(pool, identity, logger);
156
+ await manager.initialize();
157
+ return manager;
158
+ }
159
+
160
+ module.exports = {
161
+ PostgresSessionManager,
162
+ createPostgresSessionManager,
163
+ createPostgresSessionManagerWithPool,
164
+ SessionManagerError,
165
+ };