@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.
- package/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +124 -0
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +369 -33
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -7
- package/deno/breez_sdk_spark_wasm.d.ts +124 -0
- package/deno/breez_sdk_spark_wasm.js +369 -33
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -7
- package/nodejs/breez_sdk_spark_wasm.d.ts +124 -0
- package/nodejs/breez_sdk_spark_wasm.js +377 -33
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -7
- package/nodejs/index.js +22 -0
- package/nodejs/index.mjs +8 -0
- package/nodejs/mysql-session-manager/errors.cjs +13 -0
- package/nodejs/mysql-session-manager/index.cjs +144 -0
- package/nodejs/mysql-session-manager/migrations.cjs +102 -0
- package/nodejs/mysql-session-manager/package.json +9 -0
- package/nodejs/mysql-storage/index.cjs +1 -0
- package/nodejs/mysql-token-store/index.cjs +4 -12
- package/nodejs/mysql-tree-store/index.cjs +2 -0
- package/nodejs/package.json +2 -0
- package/nodejs/postgres-session-manager/errors.cjs +13 -0
- package/nodejs/postgres-session-manager/index.cjs +165 -0
- package/nodejs/postgres-session-manager/migrations.cjs +126 -0
- package/nodejs/postgres-session-manager/package.json +9 -0
- package/nodejs/postgres-token-store/index.cjs +2 -13
- package/package.json +1 -1
- package/ssr/index.js +48 -0
- package/web/breez_sdk_spark_wasm.d.ts +149 -7
- package/web/breez_sdk_spark_wasm.js +369 -33
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- 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
|
|
125
|
-
export const
|
|
126
|
-
export const
|
|
127
|
-
export const
|
|
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
|
|
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 };
|
|
@@ -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
|
-
|
|
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();
|
package/nodejs/package.json
CHANGED
|
@@ -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
|
+
};
|