@agentunion/fastaun 0.2.20 → 0.3.1
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/CHANGELOG.md +63 -23
- package/_packed_docs/CHANGELOG.md +63 -23
- package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -24
- package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +419 -0
- package/_packed_docs/protocol/index.md +13 -3
- package/_packed_docs/python-sdk-v2-only-changelog.md +189 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +39 -16
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +131 -39
- package/_packed_docs/sdk/09-message-rpc-manual.md +30 -67
- package/dist/auth.js +26 -7
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +117 -166
- package/dist/client.js +2130 -3419
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +0 -4
- package/dist/config.js +0 -4
- package/dist/config.js.map +1 -1
- package/dist/e2ee.d.ts +5 -139
- package/dist/e2ee.js +4 -1151
- package/dist/e2ee.js.map +1 -1
- package/dist/errors.d.ts +0 -8
- package/dist/errors.js +0 -14
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +12 -61
- package/dist/keystore/aid-db.js +41 -539
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/file.d.ts +5 -41
- package/dist/keystore/file.js +8 -64
- package/dist/keystore/file.js.map +1 -1
- package/dist/keystore/index.d.ts +1 -49
- package/dist/namespaces/auth.d.ts +8 -0
- package/dist/namespaces/auth.js +169 -2
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/protected-headers.d.ts +13 -0
- package/dist/protected-headers.js +47 -0
- package/dist/protected-headers.js.map +1 -0
- package/dist/seq-tracker.d.ts +7 -2
- package/dist/seq-tracker.js +33 -13
- package/dist/seq-tracker.js.map +1 -1
- package/dist/transport.d.ts +11 -1
- package/dist/transport.js +255 -6
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +0 -56
- package/dist/v2/crypto/aead.d.ts +20 -0
- package/dist/v2/crypto/aead.js +59 -0
- package/dist/v2/crypto/aead.js.map +1 -0
- package/dist/v2/crypto/canonical.d.ts +20 -0
- package/dist/v2/crypto/canonical.js +119 -0
- package/dist/v2/crypto/canonical.js.map +1 -0
- package/dist/v2/crypto/dh-path.d.ts +39 -0
- package/dist/v2/crypto/dh-path.js +55 -0
- package/dist/v2/crypto/dh-path.js.map +1 -0
- package/dist/v2/crypto/ecdh.d.ts +29 -0
- package/dist/v2/crypto/ecdh.js +122 -0
- package/dist/v2/crypto/ecdh.js.map +1 -0
- package/dist/v2/crypto/ecdsa.d.ts +29 -0
- package/dist/v2/crypto/ecdsa.js +120 -0
- package/dist/v2/crypto/ecdsa.js.map +1 -0
- package/dist/v2/crypto/hkdf.d.ts +19 -0
- package/dist/v2/crypto/hkdf.js +47 -0
- package/dist/v2/crypto/hkdf.js.map +1 -0
- package/dist/v2/crypto/index.d.ts +8 -0
- package/dist/v2/crypto/index.js +8 -0
- package/dist/v2/crypto/index.js.map +1 -0
- package/dist/v2/crypto/recipients.d.ts +32 -0
- package/dist/v2/crypto/recipients.js +183 -0
- package/dist/v2/crypto/recipients.js.map +1 -0
- package/dist/v2/e2ee/decrypt.d.ts +29 -0
- package/dist/v2/e2ee/decrypt.js +159 -0
- package/dist/v2/e2ee/decrypt.js.map +1 -0
- package/dist/v2/e2ee/encrypt-group.d.ts +17 -0
- package/dist/v2/e2ee/encrypt-group.js +143 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -0
- package/dist/v2/e2ee/encrypt-p2p.d.ts +31 -0
- package/dist/v2/e2ee/encrypt-p2p.js +190 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -0
- package/dist/v2/e2ee/index.d.ts +9 -0
- package/dist/v2/e2ee/index.js +9 -0
- package/dist/v2/e2ee/index.js.map +1 -0
- package/dist/v2/e2ee/metadata-auth.d.ts +15 -0
- package/dist/v2/e2ee/metadata-auth.js +50 -0
- package/dist/v2/e2ee/metadata-auth.js.map +1 -0
- package/dist/v2/e2ee/types.d.ts +57 -0
- package/dist/v2/e2ee/types.js +7 -0
- package/dist/v2/e2ee/types.js.map +1 -0
- package/dist/v2/session/index.d.ts +4 -0
- package/dist/v2/session/index.js +3 -0
- package/dist/v2/session/index.js.map +1 -0
- package/dist/v2/session/keystore.d.ts +50 -0
- package/dist/v2/session/keystore.js +138 -0
- package/dist/v2/session/keystore.js.map +1 -0
- package/dist/v2/session/session.d.ts +124 -0
- package/dist/v2/session/session.js +318 -0
- package/dist/v2/session/session.js.map +1 -0
- package/dist/v2/state/commitment.d.ts +58 -0
- package/dist/v2/state/commitment.js +85 -0
- package/dist/v2/state/commitment.js.map +1 -0
- package/dist/v2/state/index.d.ts +2 -0
- package/dist/v2/state/index.js +2 -0
- package/dist/v2/state/index.js.map +1 -0
- package/package.json +4 -3
package/dist/keystore/aid-db.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-AID SQLite 数据库。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - 敏感字段写入时一律字段级加密
|
|
7
|
-
* - 读取时兼容密文 JSON 与历史明文
|
|
4
|
+
* E2EE V2 ONLY:这里不再提供旧版 E2EE 密钥材料存储。
|
|
5
|
+
* V2 设备密钥由 `v2/session/keystore.ts` 通过同一个 SQLite 句柄维护。
|
|
8
6
|
*/
|
|
9
7
|
import { mkdirSync } from 'node:fs';
|
|
10
8
|
import { createRequire } from 'node:module';
|
|
@@ -20,95 +18,44 @@ function configureDatabase(db, busyTimeoutMs) {
|
|
|
20
18
|
}
|
|
21
19
|
db.exec('PRAGMA synchronous = NORMAL');
|
|
22
20
|
}
|
|
23
|
-
function runImmediateTransaction(db, fn) {
|
|
24
|
-
db.exec('BEGIN IMMEDIATE');
|
|
25
|
-
try {
|
|
26
|
-
const result = fn();
|
|
27
|
-
db.exec('COMMIT');
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
try {
|
|
32
|
-
if (db.isTransaction)
|
|
33
|
-
db.exec('ROLLBACK');
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
/* ignore rollback failure */
|
|
37
|
-
}
|
|
38
|
-
throw err;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
21
|
const DDL_STATEMENTS = [
|
|
42
|
-
`CREATE TABLE IF NOT EXISTS _schema_version (
|
|
43
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
44
|
-
version INTEGER NOT NULL
|
|
45
|
-
)`,
|
|
46
|
-
`CREATE TABLE IF NOT EXISTS tokens (
|
|
47
|
-
key TEXT PRIMARY KEY,
|
|
48
|
-
value TEXT NOT NULL,
|
|
49
|
-
updated_at INTEGER NOT NULL
|
|
50
|
-
)`,
|
|
51
|
-
`CREATE TABLE IF NOT EXISTS prekeys (
|
|
52
|
-
prekey_id TEXT NOT NULL,
|
|
53
|
-
device_id TEXT NOT NULL DEFAULT '',
|
|
54
|
-
private_key_enc TEXT NOT NULL DEFAULT '',
|
|
55
|
-
data TEXT NOT NULL DEFAULT '{}',
|
|
56
|
-
created_at INTEGER,
|
|
57
|
-
updated_at INTEGER NOT NULL,
|
|
58
|
-
expires_at INTEGER,
|
|
59
|
-
PRIMARY KEY (prekey_id, device_id)
|
|
22
|
+
`CREATE TABLE IF NOT EXISTS _schema_version (
|
|
23
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
24
|
+
version INTEGER NOT NULL
|
|
60
25
|
)`,
|
|
61
|
-
`CREATE
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
secret_enc TEXT NOT NULL DEFAULT '',
|
|
66
|
-
data TEXT NOT NULL DEFAULT '{}',
|
|
67
|
-
updated_at INTEGER NOT NULL
|
|
26
|
+
`CREATE TABLE IF NOT EXISTS tokens (
|
|
27
|
+
key TEXT PRIMARY KEY,
|
|
28
|
+
value TEXT NOT NULL,
|
|
29
|
+
updated_at INTEGER NOT NULL
|
|
68
30
|
)`,
|
|
69
|
-
`CREATE TABLE IF NOT EXISTS
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
expires_at INTEGER,
|
|
76
|
-
PRIMARY KEY (group_id, epoch)
|
|
31
|
+
`CREATE TABLE IF NOT EXISTS instance_state (
|
|
32
|
+
device_id TEXT NOT NULL,
|
|
33
|
+
slot_id TEXT NOT NULL DEFAULT '_singleton',
|
|
34
|
+
data TEXT NOT NULL DEFAULT '{}',
|
|
35
|
+
updated_at INTEGER NOT NULL,
|
|
36
|
+
PRIMARY KEY (device_id, slot_id)
|
|
77
37
|
)`,
|
|
78
|
-
`CREATE
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
38
|
+
`CREATE TABLE IF NOT EXISTS seq_tracker (
|
|
39
|
+
device_id TEXT NOT NULL,
|
|
40
|
+
slot_id TEXT NOT NULL DEFAULT '_singleton',
|
|
41
|
+
namespace TEXT NOT NULL,
|
|
42
|
+
contiguous_seq INTEGER NOT NULL DEFAULT 0,
|
|
43
|
+
updated_at INTEGER NOT NULL,
|
|
44
|
+
PRIMARY KEY (device_id, slot_id, namespace)
|
|
83
45
|
)`,
|
|
84
|
-
`CREATE TABLE IF NOT EXISTS
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
updated_at INTEGER NOT NULL,
|
|
89
|
-
PRIMARY KEY (device_id, slot_id)
|
|
46
|
+
`CREATE TABLE IF NOT EXISTS metadata_kv (
|
|
47
|
+
key TEXT PRIMARY KEY,
|
|
48
|
+
value TEXT NOT NULL,
|
|
49
|
+
updated_at INTEGER NOT NULL
|
|
90
50
|
)`,
|
|
91
|
-
`CREATE TABLE IF NOT EXISTS
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`CREATE TABLE IF NOT EXISTS metadata_kv (
|
|
100
|
-
key TEXT PRIMARY KEY,
|
|
101
|
-
value TEXT NOT NULL,
|
|
102
|
-
updated_at INTEGER NOT NULL
|
|
103
|
-
)`,
|
|
104
|
-
`CREATE TABLE IF NOT EXISTS group_state (
|
|
105
|
-
group_id TEXT PRIMARY KEY,
|
|
106
|
-
state_version INTEGER NOT NULL DEFAULT 0,
|
|
107
|
-
state_hash TEXT NOT NULL DEFAULT '',
|
|
108
|
-
key_epoch INTEGER NOT NULL DEFAULT 0,
|
|
109
|
-
membership_json TEXT NOT NULL DEFAULT '',
|
|
110
|
-
policy_json TEXT NOT NULL DEFAULT '',
|
|
111
|
-
updated_at INTEGER NOT NULL DEFAULT 0
|
|
51
|
+
`CREATE TABLE IF NOT EXISTS group_state (
|
|
52
|
+
group_id TEXT PRIMARY KEY,
|
|
53
|
+
state_version INTEGER NOT NULL DEFAULT 0,
|
|
54
|
+
state_hash TEXT NOT NULL DEFAULT '',
|
|
55
|
+
key_epoch INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
membership_json TEXT NOT NULL DEFAULT '',
|
|
57
|
+
policy_json TEXT NOT NULL DEFAULT '',
|
|
58
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
112
59
|
)`,
|
|
113
60
|
];
|
|
114
61
|
function jsonParseObject(value) {
|
|
@@ -123,14 +70,12 @@ function jsonParseObject(value) {
|
|
|
123
70
|
export class AIDDatabase {
|
|
124
71
|
_db;
|
|
125
72
|
_dbPath;
|
|
126
|
-
_secretStore;
|
|
127
73
|
_scope;
|
|
128
74
|
_log;
|
|
129
|
-
constructor(dbPath,
|
|
75
|
+
constructor(dbPath, _secretStore, scope, logger) {
|
|
130
76
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
131
77
|
const absPath = resolve(dbPath);
|
|
132
78
|
this._dbPath = absPath;
|
|
133
|
-
this._secretStore = secretStore ?? null;
|
|
134
79
|
this._scope = scope ?? dirname(absPath).split(/[\\/]/).pop() ?? '';
|
|
135
80
|
this._log = logger ?? { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
|
|
136
81
|
const cached = _dbPool.get(absPath);
|
|
@@ -146,6 +91,10 @@ export class AIDDatabase {
|
|
|
146
91
|
this._initSchema();
|
|
147
92
|
this._log.debug(`AIDDatabase ready: path=${this._dbPath} scope=${this._scope}`);
|
|
148
93
|
}
|
|
94
|
+
/** 暴露底层 SQLite 句柄,供 V2KeyStore 共享同一 DB 连接。 */
|
|
95
|
+
getSqliteHandle() {
|
|
96
|
+
return this._db;
|
|
97
|
+
}
|
|
149
98
|
close() {
|
|
150
99
|
const cached = _dbPool.get(this._dbPath);
|
|
151
100
|
if (cached) {
|
|
@@ -166,50 +115,10 @@ export class AIDDatabase {
|
|
|
166
115
|
_initSchema() {
|
|
167
116
|
for (const ddl of DDL_STATEMENTS)
|
|
168
117
|
this._db.exec(ddl);
|
|
169
|
-
this._migrateLegacyColumns();
|
|
170
118
|
const row = this._db.prepare('SELECT version FROM _schema_version WHERE id = 1').get();
|
|
171
119
|
if (!row)
|
|
172
120
|
this._db.prepare('INSERT INTO _schema_version (id, version) VALUES (1, ?)').run(SCHEMA_VERSION);
|
|
173
121
|
}
|
|
174
|
-
_migrateLegacyColumns() {
|
|
175
|
-
this._renameColumnIfExists('prekeys', 'private_key_pem', 'private_key_enc');
|
|
176
|
-
this._renameColumnIfExists('group_current', 'secret', 'secret_enc');
|
|
177
|
-
this._renameColumnIfExists('group_old_epochs', 'secret', 'secret_enc');
|
|
178
|
-
this._renameColumnIfExists('e2ee_sessions', 'data', 'data_enc');
|
|
179
|
-
}
|
|
180
|
-
_renameColumnIfExists(table, oldName, newName) {
|
|
181
|
-
const rows = this._db.prepare(`PRAGMA table_info(${table})`).all();
|
|
182
|
-
const names = new Set(rows.map((row) => row.name));
|
|
183
|
-
if (names.has(oldName) && !names.has(newName)) {
|
|
184
|
-
this._db.exec(`ALTER TABLE ${table} RENAME COLUMN ${oldName} TO ${newName}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
_protectText(name, plaintext) {
|
|
188
|
-
if (!this._secretStore || !plaintext)
|
|
189
|
-
return plaintext;
|
|
190
|
-
try {
|
|
191
|
-
return JSON.stringify(this._secretStore.protect(this._scope, name, Buffer.from(plaintext, 'utf-8')));
|
|
192
|
-
}
|
|
193
|
-
catch (exc) {
|
|
194
|
-
this._log.error(`field encryption failed (scope=${this._scope}, name=${name}), degrading to plaintext: ${exc instanceof Error ? exc.message : String(exc)}`, exc instanceof Error ? exc : undefined);
|
|
195
|
-
return plaintext;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
_revealText(name, stored) {
|
|
199
|
-
if (!this._secretStore || !stored)
|
|
200
|
-
return stored;
|
|
201
|
-
try {
|
|
202
|
-
const record = JSON.parse(stored);
|
|
203
|
-
if (record?.scheme !== 'file_aes')
|
|
204
|
-
return stored;
|
|
205
|
-
const plain = this._secretStore.reveal(this._scope, name, record);
|
|
206
|
-
return plain ? plain.toString('utf-8') : stored;
|
|
207
|
-
}
|
|
208
|
-
catch (exc) {
|
|
209
|
-
this._log.error(`field decryption failed (scope=${this._scope}, name=${name}): ${exc instanceof Error ? exc.message : String(exc)}`, exc instanceof Error ? exc : undefined);
|
|
210
|
-
return stored;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
122
|
getToken(key) {
|
|
214
123
|
const row = this._db.prepare('SELECT value FROM tokens WHERE key = ?').get(key);
|
|
215
124
|
return row?.value ?? null;
|
|
@@ -227,412 +136,6 @@ export class AIDDatabase {
|
|
|
227
136
|
result[row.key] = row.value;
|
|
228
137
|
return result;
|
|
229
138
|
}
|
|
230
|
-
savePrekey(prekeyId, privateKeyPem, deviceId = '', createdAt, expiresAt, extraData) {
|
|
231
|
-
const now = Date.now();
|
|
232
|
-
this._db.prepare(`INSERT INTO prekeys (prekey_id, device_id, private_key_enc, data, created_at, updated_at, expires_at)
|
|
233
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
234
|
-
ON CONFLICT(prekey_id, device_id) DO UPDATE SET
|
|
235
|
-
private_key_enc=excluded.private_key_enc, data=excluded.data,
|
|
236
|
-
updated_at=excluded.updated_at, expires_at=excluded.expires_at`).run(prekeyId, deviceId, this._protectText(`prekey/${prekeyId}`, privateKeyPem), JSON.stringify(extraData ?? {}), createdAt ?? now, now, expiresAt ?? null);
|
|
237
|
-
}
|
|
238
|
-
loadPrekeys(deviceId = '') {
|
|
239
|
-
const rows = this._db.prepare('SELECT prekey_id, private_key_enc, data, created_at, updated_at, expires_at FROM prekeys WHERE device_id = ?').all(deviceId);
|
|
240
|
-
const result = {};
|
|
241
|
-
for (const row of rows) {
|
|
242
|
-
const entry = {
|
|
243
|
-
private_key_pem: this._revealText(`prekey/${row.prekey_id}`, row.private_key_enc),
|
|
244
|
-
};
|
|
245
|
-
if (row.created_at != null)
|
|
246
|
-
entry.created_at = row.created_at;
|
|
247
|
-
if (row.updated_at != null)
|
|
248
|
-
entry.updated_at = row.updated_at;
|
|
249
|
-
if (row.expires_at != null)
|
|
250
|
-
entry.expires_at = row.expires_at;
|
|
251
|
-
Object.assign(entry, jsonParseObject(row.data));
|
|
252
|
-
result[row.prekey_id] = entry;
|
|
253
|
-
}
|
|
254
|
-
return result;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* 按 prekey_id 单点查询(WHERE prekey_id = ? LIMIT 1)。
|
|
258
|
-
* 相比 loadPrekeys 的全量加载(O(N)),单条查询是 O(1)。
|
|
259
|
-
* 解密入站消息时信封里有 prekey_id,应优先走这条路径。
|
|
260
|
-
*/
|
|
261
|
-
loadPrekeyById(prekeyId) {
|
|
262
|
-
const row = this._db.prepare('SELECT prekey_id, private_key_enc, data, created_at, updated_at, expires_at FROM prekeys WHERE prekey_id = ? LIMIT 1').get(prekeyId);
|
|
263
|
-
if (!row)
|
|
264
|
-
return null;
|
|
265
|
-
const entry = {
|
|
266
|
-
private_key_pem: this._revealText(`prekey/${row.prekey_id}`, row.private_key_enc),
|
|
267
|
-
};
|
|
268
|
-
if (row.created_at != null)
|
|
269
|
-
entry.created_at = row.created_at;
|
|
270
|
-
if (row.updated_at != null)
|
|
271
|
-
entry.updated_at = row.updated_at;
|
|
272
|
-
if (row.expires_at != null)
|
|
273
|
-
entry.expires_at = row.expires_at;
|
|
274
|
-
Object.assign(entry, jsonParseObject(row.data));
|
|
275
|
-
return entry;
|
|
276
|
-
}
|
|
277
|
-
deletePrekey(prekeyId, deviceId = '') {
|
|
278
|
-
this._db.prepare('DELETE FROM prekeys WHERE prekey_id = ? AND device_id = ?').run(prekeyId, deviceId);
|
|
279
|
-
}
|
|
280
|
-
cleanupPrekeys(deviceId, cutoffMs, keepLatest) {
|
|
281
|
-
const rows = this._db.prepare('SELECT prekey_id, created_at FROM prekeys WHERE device_id = ? ORDER BY created_at DESC').all(deviceId);
|
|
282
|
-
const latestIds = new Set(rows.slice(0, keepLatest).map((row) => row.prekey_id));
|
|
283
|
-
const toDelete = rows
|
|
284
|
-
.filter((row) => !latestIds.has(row.prekey_id) && row.created_at != null && row.created_at < cutoffMs)
|
|
285
|
-
.map((row) => row.prekey_id);
|
|
286
|
-
if (toDelete.length > 0) {
|
|
287
|
-
const del = this._db.prepare('DELETE FROM prekeys WHERE device_id = ? AND prekey_id = ?');
|
|
288
|
-
runImmediateTransaction(this._db, () => {
|
|
289
|
-
for (const id of toDelete)
|
|
290
|
-
del.run(deviceId, id);
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
return toDelete;
|
|
294
|
-
}
|
|
295
|
-
saveGroupCurrent(groupId, epoch, secret, data) {
|
|
296
|
-
this._db.prepare(`INSERT INTO group_current (group_id, epoch, secret_enc, data, updated_at)
|
|
297
|
-
VALUES (?, ?, ?, ?, ?)
|
|
298
|
-
ON CONFLICT(group_id) DO UPDATE SET epoch=excluded.epoch, secret_enc=excluded.secret_enc, data=excluded.data, updated_at=excluded.updated_at`).run(groupId, epoch, this._protectText(`group/${groupId}/current`, secret), JSON.stringify(data), Date.now());
|
|
299
|
-
}
|
|
300
|
-
loadGroupCurrent(groupId) {
|
|
301
|
-
const row = this._db.prepare('SELECT epoch, secret_enc, data, updated_at FROM group_current WHERE group_id = ?').get(groupId);
|
|
302
|
-
if (!row)
|
|
303
|
-
return null;
|
|
304
|
-
return {
|
|
305
|
-
group_id: groupId,
|
|
306
|
-
epoch: row.epoch,
|
|
307
|
-
secret: this._revealText(`group/${groupId}/current`, row.secret_enc),
|
|
308
|
-
...jsonParseObject(row.data),
|
|
309
|
-
updated_at: row.updated_at,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
loadGroupSecretEpoch(groupId, epoch) {
|
|
313
|
-
const current = this.loadGroupCurrent(groupId);
|
|
314
|
-
if (epoch === undefined || epoch === null)
|
|
315
|
-
return current;
|
|
316
|
-
if (current && Number(current.epoch ?? 0) === Number(epoch))
|
|
317
|
-
return current;
|
|
318
|
-
const row = this._db.prepare('SELECT epoch, secret_enc, data, updated_at, expires_at FROM group_old_epochs WHERE group_id = ? AND epoch = ?').get(groupId, epoch);
|
|
319
|
-
if (!row)
|
|
320
|
-
return null;
|
|
321
|
-
const entry = {
|
|
322
|
-
epoch: row.epoch,
|
|
323
|
-
secret: this._revealText(`group/${groupId}/epoch/${row.epoch}`, row.secret_enc),
|
|
324
|
-
...jsonParseObject(row.data),
|
|
325
|
-
updated_at: row.updated_at,
|
|
326
|
-
};
|
|
327
|
-
if (row.expires_at != null)
|
|
328
|
-
entry.expires_at = row.expires_at;
|
|
329
|
-
return entry;
|
|
330
|
-
}
|
|
331
|
-
loadGroupSecretEpochs(groupId) {
|
|
332
|
-
const result = [];
|
|
333
|
-
const current = this.loadGroupCurrent(groupId);
|
|
334
|
-
if (current)
|
|
335
|
-
result.push(current);
|
|
336
|
-
const rows = this._db.prepare('SELECT epoch, secret_enc, data, updated_at, expires_at FROM group_old_epochs WHERE group_id = ? ORDER BY epoch ASC').all(groupId);
|
|
337
|
-
for (const row of rows) {
|
|
338
|
-
const entry = {
|
|
339
|
-
epoch: row.epoch,
|
|
340
|
-
secret: this._revealText(`group/${groupId}/epoch/${row.epoch}`, row.secret_enc),
|
|
341
|
-
...jsonParseObject(row.data),
|
|
342
|
-
updated_at: row.updated_at,
|
|
343
|
-
};
|
|
344
|
-
if (row.expires_at != null)
|
|
345
|
-
entry.expires_at = row.expires_at;
|
|
346
|
-
result.push(entry);
|
|
347
|
-
}
|
|
348
|
-
return result;
|
|
349
|
-
}
|
|
350
|
-
loadAllGroupCurrent() {
|
|
351
|
-
const rows = this._db.prepare('SELECT group_id, epoch, secret_enc, data, updated_at FROM group_current').all();
|
|
352
|
-
const result = {};
|
|
353
|
-
for (const row of rows) {
|
|
354
|
-
result[row.group_id] = {
|
|
355
|
-
group_id: row.group_id,
|
|
356
|
-
epoch: row.epoch,
|
|
357
|
-
secret: this._revealText(`group/${row.group_id}/current`, row.secret_enc),
|
|
358
|
-
...jsonParseObject(row.data),
|
|
359
|
-
updated_at: row.updated_at,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
return result;
|
|
363
|
-
}
|
|
364
|
-
deleteGroupCurrent(groupId) {
|
|
365
|
-
this._db.prepare('DELETE FROM group_current WHERE group_id = ?').run(groupId);
|
|
366
|
-
}
|
|
367
|
-
saveGroupOldEpoch(groupId, epoch, secret, data, updatedAt, expiresAt) {
|
|
368
|
-
this._db.prepare(`INSERT INTO group_old_epochs (group_id, epoch, secret_enc, data, updated_at, expires_at)
|
|
369
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
370
|
-
ON CONFLICT(group_id, epoch) DO UPDATE SET secret_enc=excluded.secret_enc, data=excluded.data, updated_at=excluded.updated_at, expires_at=excluded.expires_at`).run(groupId, epoch, this._protectText(`group/${groupId}/epoch/${epoch}`, secret), JSON.stringify(data), updatedAt ?? Date.now(), expiresAt ?? null);
|
|
371
|
-
}
|
|
372
|
-
loadGroupOldEpochs(groupId) {
|
|
373
|
-
const rows = this._db.prepare('SELECT epoch, secret_enc, data, updated_at, expires_at FROM group_old_epochs WHERE group_id = ? ORDER BY epoch ASC').all(groupId);
|
|
374
|
-
return rows.map((row) => {
|
|
375
|
-
const entry = {
|
|
376
|
-
epoch: row.epoch,
|
|
377
|
-
secret: this._revealText(`group/${groupId}/epoch/${row.epoch}`, row.secret_enc),
|
|
378
|
-
...jsonParseObject(row.data),
|
|
379
|
-
updated_at: row.updated_at,
|
|
380
|
-
};
|
|
381
|
-
if (row.expires_at != null)
|
|
382
|
-
entry.expires_at = row.expires_at;
|
|
383
|
-
return entry;
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
deleteAllGroupOldEpochs(groupId) {
|
|
387
|
-
this._db.prepare('DELETE FROM group_old_epochs WHERE group_id = ?').run(groupId);
|
|
388
|
-
}
|
|
389
|
-
loadAllGroupIdsWithOldEpochs() {
|
|
390
|
-
const rows = this._db.prepare('SELECT DISTINCT group_id FROM group_old_epochs').all();
|
|
391
|
-
return rows.map((row) => row.group_id);
|
|
392
|
-
}
|
|
393
|
-
cleanupGroupOldEpochs(groupId, cutoffMs) {
|
|
394
|
-
const result = this._db.prepare('DELETE FROM group_old_epochs WHERE group_id = ? AND updated_at <= ?').run(groupId, cutoffMs);
|
|
395
|
-
return Number(result.changes);
|
|
396
|
-
}
|
|
397
|
-
storeGroupSecretTransition(groupId, opts) {
|
|
398
|
-
return Boolean(runImmediateTransaction(this._db, () => {
|
|
399
|
-
const now = Date.now();
|
|
400
|
-
const epoch = Number(opts.epoch);
|
|
401
|
-
const members = [...(opts.memberAids ?? [])].map((item) => String(item)).sort();
|
|
402
|
-
const pendingRotationId = String(opts.pendingRotationId ?? '').trim();
|
|
403
|
-
const current = this._db.prepare('SELECT epoch, secret_enc, data, updated_at FROM group_current WHERE group_id = ?').get(groupId);
|
|
404
|
-
if (current) {
|
|
405
|
-
const localEpoch = Number(current.epoch);
|
|
406
|
-
const currentSecret = this._revealText(`group/${groupId}/current`, current.secret_enc);
|
|
407
|
-
const currentData = jsonParseObject(current.data);
|
|
408
|
-
if (epoch < localEpoch)
|
|
409
|
-
return false;
|
|
410
|
-
if (epoch === localEpoch && currentSecret) {
|
|
411
|
-
if (currentSecret !== opts.secret) {
|
|
412
|
-
if (String(currentData.pending_rotation_id ?? '').trim()) {
|
|
413
|
-
this._upsertGroupCurrent(groupId, epoch, opts.secret, this._buildGroupCurrentData(opts, members, now), now);
|
|
414
|
-
return true;
|
|
415
|
-
}
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
const updated = { ...currentData };
|
|
419
|
-
let changed = false;
|
|
420
|
-
const oldMembers = Array.isArray(updated.member_aids) ? updated.member_aids.map(String).sort() : [];
|
|
421
|
-
if (members.length > 0 && JSON.stringify(oldMembers) !== JSON.stringify(members)) {
|
|
422
|
-
updated.member_aids = members;
|
|
423
|
-
updated.commitment = opts.commitment;
|
|
424
|
-
changed = true;
|
|
425
|
-
}
|
|
426
|
-
if (opts.epochChain !== undefined && updated.epoch_chain !== opts.epochChain) {
|
|
427
|
-
updated.epoch_chain = opts.epochChain;
|
|
428
|
-
changed = true;
|
|
429
|
-
}
|
|
430
|
-
if (opts.epochChainUnverified === true) {
|
|
431
|
-
if (updated.epoch_chain_unverified !== true) {
|
|
432
|
-
updated.epoch_chain_unverified = true;
|
|
433
|
-
changed = true;
|
|
434
|
-
}
|
|
435
|
-
if (opts.epochChainUnverifiedReason && updated.epoch_chain_unverified_reason !== opts.epochChainUnverifiedReason) {
|
|
436
|
-
updated.epoch_chain_unverified_reason = opts.epochChainUnverifiedReason;
|
|
437
|
-
changed = true;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
else if (opts.epochChainUnverified === false) {
|
|
441
|
-
if ('epoch_chain_unverified' in updated || 'epoch_chain_unverified_reason' in updated) {
|
|
442
|
-
delete updated.epoch_chain_unverified;
|
|
443
|
-
delete updated.epoch_chain_unverified_reason;
|
|
444
|
-
changed = true;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (pendingRotationId && updated.pending_rotation_id !== pendingRotationId) {
|
|
448
|
-
updated.pending_rotation_id = pendingRotationId;
|
|
449
|
-
updated.pending_created_at = now;
|
|
450
|
-
changed = true;
|
|
451
|
-
}
|
|
452
|
-
if (!pendingRotationId && updated.pending_rotation_id) {
|
|
453
|
-
delete updated.pending_rotation_id;
|
|
454
|
-
delete updated.pending_created_at;
|
|
455
|
-
changed = true;
|
|
456
|
-
}
|
|
457
|
-
if (changed)
|
|
458
|
-
this._upsertGroupCurrent(groupId, epoch, currentSecret, updated, now);
|
|
459
|
-
return true;
|
|
460
|
-
}
|
|
461
|
-
if (localEpoch !== epoch) {
|
|
462
|
-
const expiresAt = Number(current.updated_at || now) + opts.oldEpochRetentionMs;
|
|
463
|
-
this._upsertGroupOldEpoch(groupId, localEpoch, currentSecret, currentData, Number(current.updated_at || now), expiresAt);
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
// epoch === localEpoch 但 currentSecret 为空:合并 data,不覆盖已有字段
|
|
467
|
-
const merged = { ...currentData, ...this._buildGroupCurrentData(opts, members, now) };
|
|
468
|
-
// 保留已有的 epoch_chain(如果新值为空)
|
|
469
|
-
if (!opts.epochChain && currentData.epoch_chain) {
|
|
470
|
-
merged.epoch_chain = currentData.epoch_chain;
|
|
471
|
-
}
|
|
472
|
-
if (currentData.pending_rotation_id && !opts.pendingRotationId) {
|
|
473
|
-
merged.pending_rotation_id = currentData.pending_rotation_id;
|
|
474
|
-
if (currentData.pending_created_at)
|
|
475
|
-
merged.pending_created_at = currentData.pending_created_at;
|
|
476
|
-
}
|
|
477
|
-
this._upsertGroupCurrent(groupId, epoch, opts.secret, merged, now);
|
|
478
|
-
return true;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
this._upsertGroupCurrent(groupId, epoch, opts.secret, this._buildGroupCurrentData(opts, members, now), now);
|
|
482
|
-
return true;
|
|
483
|
-
}));
|
|
484
|
-
}
|
|
485
|
-
storeGroupSecretEpoch(groupId, opts) {
|
|
486
|
-
return Boolean(runImmediateTransaction(this._db, () => {
|
|
487
|
-
const now = Date.now();
|
|
488
|
-
const epoch = Number(opts.epoch);
|
|
489
|
-
const members = [...(opts.memberAids ?? [])].map((item) => String(item)).sort();
|
|
490
|
-
const pendingRotationId = String(opts.pendingRotationId ?? '').trim();
|
|
491
|
-
const data = this._buildGroupCurrentData(opts, members, now);
|
|
492
|
-
const current = this._db.prepare('SELECT epoch, secret_enc, data, updated_at FROM group_current WHERE group_id = ?').get(groupId);
|
|
493
|
-
if (!current) {
|
|
494
|
-
this._upsertGroupCurrent(groupId, epoch, opts.secret, data, now);
|
|
495
|
-
return true;
|
|
496
|
-
}
|
|
497
|
-
const localEpoch = Number(current.epoch);
|
|
498
|
-
if (epoch > localEpoch) {
|
|
499
|
-
// 归档旧 epoch 到 old_epochs,然后用新 epoch 更新 current
|
|
500
|
-
const oldSecret = this._revealText(`group/${groupId}/current`, current.secret_enc);
|
|
501
|
-
const oldData = jsonParseObject(current.data);
|
|
502
|
-
const expiresAt = now + (opts.oldEpochRetentionMs ?? 604800_000);
|
|
503
|
-
this._upsertGroupOldEpoch(groupId, localEpoch, oldSecret, oldData, current.updated_at, expiresAt);
|
|
504
|
-
this._upsertGroupCurrent(groupId, epoch, opts.secret, data, now);
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
507
|
-
if (epoch === localEpoch) {
|
|
508
|
-
const currentSecret = this._revealText(`group/${groupId}/current`, current.secret_enc);
|
|
509
|
-
const currentData = jsonParseObject(current.data);
|
|
510
|
-
if (currentSecret && currentSecret !== opts.secret) {
|
|
511
|
-
if (!String(currentData.pending_rotation_id ?? '').trim())
|
|
512
|
-
return false;
|
|
513
|
-
this._upsertGroupCurrent(groupId, epoch, opts.secret, data, now);
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
const updated = { ...currentData };
|
|
517
|
-
let changed = false;
|
|
518
|
-
const oldMembers = Array.isArray(updated.member_aids) ? updated.member_aids.map(String).sort() : [];
|
|
519
|
-
if (members.length > 0 && JSON.stringify(oldMembers) !== JSON.stringify(members)) {
|
|
520
|
-
updated.member_aids = members;
|
|
521
|
-
updated.commitment = opts.commitment;
|
|
522
|
-
changed = true;
|
|
523
|
-
}
|
|
524
|
-
if (opts.epochChain !== undefined && updated.epoch_chain !== opts.epochChain) {
|
|
525
|
-
updated.epoch_chain = opts.epochChain;
|
|
526
|
-
changed = true;
|
|
527
|
-
}
|
|
528
|
-
if (opts.epochChainUnverified === true) {
|
|
529
|
-
if (updated.epoch_chain_unverified !== true) {
|
|
530
|
-
updated.epoch_chain_unverified = true;
|
|
531
|
-
changed = true;
|
|
532
|
-
}
|
|
533
|
-
if (opts.epochChainUnverifiedReason && updated.epoch_chain_unverified_reason !== opts.epochChainUnverifiedReason) {
|
|
534
|
-
updated.epoch_chain_unverified_reason = opts.epochChainUnverifiedReason;
|
|
535
|
-
changed = true;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
else if (opts.epochChainUnverified === false) {
|
|
539
|
-
if ('epoch_chain_unverified' in updated || 'epoch_chain_unverified_reason' in updated) {
|
|
540
|
-
delete updated.epoch_chain_unverified;
|
|
541
|
-
delete updated.epoch_chain_unverified_reason;
|
|
542
|
-
changed = true;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (pendingRotationId && updated.pending_rotation_id !== pendingRotationId) {
|
|
546
|
-
updated.pending_rotation_id = pendingRotationId;
|
|
547
|
-
updated.pending_created_at = now;
|
|
548
|
-
changed = true;
|
|
549
|
-
}
|
|
550
|
-
if (!pendingRotationId && updated.pending_rotation_id) {
|
|
551
|
-
delete updated.pending_rotation_id;
|
|
552
|
-
delete updated.pending_created_at;
|
|
553
|
-
changed = true;
|
|
554
|
-
}
|
|
555
|
-
if (changed)
|
|
556
|
-
this._upsertGroupCurrent(groupId, epoch, currentSecret, updated, now);
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
const old = this._db.prepare('SELECT secret_enc FROM group_old_epochs WHERE group_id = ? AND epoch = ?').get(groupId, epoch);
|
|
560
|
-
if (old) {
|
|
561
|
-
const oldSecret = this._revealText(`group/${groupId}/epoch/${epoch}`, old.secret_enc);
|
|
562
|
-
if (oldSecret && oldSecret !== opts.secret)
|
|
563
|
-
return false;
|
|
564
|
-
}
|
|
565
|
-
this._upsertGroupOldEpoch(groupId, epoch, opts.secret, data, now, now + opts.oldEpochRetentionMs);
|
|
566
|
-
return true;
|
|
567
|
-
}));
|
|
568
|
-
}
|
|
569
|
-
discardPendingGroupSecretState(groupId, epoch, rotationId) {
|
|
570
|
-
return Boolean(runImmediateTransaction(this._db, () => {
|
|
571
|
-
const rid = String(rotationId ?? '').trim();
|
|
572
|
-
if (!rid)
|
|
573
|
-
return false;
|
|
574
|
-
const current = this._db.prepare('SELECT epoch, data FROM group_current WHERE group_id = ?').get(groupId);
|
|
575
|
-
if (!current || Number(current.epoch) !== Number(epoch))
|
|
576
|
-
return false;
|
|
577
|
-
const data = jsonParseObject(current.data);
|
|
578
|
-
if (String(data.pending_rotation_id ?? '').trim() !== rid)
|
|
579
|
-
return false;
|
|
580
|
-
const old = this._db.prepare('SELECT epoch, secret_enc, data, updated_at, expires_at FROM group_old_epochs WHERE group_id = ? AND epoch < ? ORDER BY epoch DESC LIMIT 1').get(groupId, epoch);
|
|
581
|
-
if (!old) {
|
|
582
|
-
this._db.prepare('DELETE FROM group_current WHERE group_id = ?').run(groupId);
|
|
583
|
-
this._db.prepare('DELETE FROM group_old_epochs WHERE group_id = ?').run(groupId);
|
|
584
|
-
return true;
|
|
585
|
-
}
|
|
586
|
-
const secret = this._revealText(`group/${groupId}/epoch/${old.epoch}`, old.secret_enc);
|
|
587
|
-
this._upsertGroupCurrent(groupId, Number(old.epoch), secret, jsonParseObject(old.data), Date.now());
|
|
588
|
-
this._db.prepare('DELETE FROM group_old_epochs WHERE group_id = ? AND epoch = ?').run(groupId, old.epoch);
|
|
589
|
-
return true;
|
|
590
|
-
}));
|
|
591
|
-
}
|
|
592
|
-
_buildGroupCurrentData(opts, memberAids, now) {
|
|
593
|
-
const data = {
|
|
594
|
-
commitment: opts.commitment,
|
|
595
|
-
member_aids: memberAids,
|
|
596
|
-
};
|
|
597
|
-
if (opts.epochChain !== undefined)
|
|
598
|
-
data.epoch_chain = opts.epochChain;
|
|
599
|
-
const pendingRotationId = String(opts.pendingRotationId ?? '').trim();
|
|
600
|
-
if (pendingRotationId) {
|
|
601
|
-
data.pending_rotation_id = pendingRotationId;
|
|
602
|
-
data.pending_created_at = now;
|
|
603
|
-
}
|
|
604
|
-
if (opts.epochChainUnverified === true) {
|
|
605
|
-
data.epoch_chain_unverified = true;
|
|
606
|
-
if (opts.epochChainUnverifiedReason)
|
|
607
|
-
data.epoch_chain_unverified_reason = opts.epochChainUnverifiedReason;
|
|
608
|
-
}
|
|
609
|
-
return data;
|
|
610
|
-
}
|
|
611
|
-
_upsertGroupCurrent(groupId, epoch, secret, data, updatedAt) {
|
|
612
|
-
this._db.prepare(`INSERT INTO group_current (group_id, epoch, secret_enc, data, updated_at)
|
|
613
|
-
VALUES (?, ?, ?, ?, ?)
|
|
614
|
-
ON CONFLICT(group_id) DO UPDATE SET epoch=excluded.epoch, secret_enc=excluded.secret_enc, data=excluded.data, updated_at=excluded.updated_at`).run(groupId, epoch, this._protectText(`group/${groupId}/current`, secret), JSON.stringify(data), updatedAt);
|
|
615
|
-
}
|
|
616
|
-
_upsertGroupOldEpoch(groupId, epoch, secret, data, updatedAt, expiresAt) {
|
|
617
|
-
this._db.prepare(`INSERT INTO group_old_epochs (group_id, epoch, secret_enc, data, updated_at, expires_at)
|
|
618
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
619
|
-
ON CONFLICT(group_id, epoch) DO UPDATE SET secret_enc=excluded.secret_enc, data=excluded.data, updated_at=excluded.updated_at, expires_at=excluded.expires_at`).run(groupId, epoch, this._protectText(`group/${groupId}/epoch/${epoch}`, secret), JSON.stringify(data), updatedAt, expiresAt ?? null);
|
|
620
|
-
}
|
|
621
|
-
saveSession(sessionId, data) {
|
|
622
|
-
const dataJson = JSON.stringify(data);
|
|
623
|
-
this._db.prepare('INSERT INTO e2ee_sessions (session_id, data_enc, updated_at) VALUES (?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET data_enc=excluded.data_enc, updated_at=excluded.updated_at').run(sessionId, this._protectText(`session/${sessionId}`, dataJson), Date.now());
|
|
624
|
-
}
|
|
625
|
-
loadAllSessions() {
|
|
626
|
-
const rows = this._db.prepare('SELECT session_id, data_enc, updated_at FROM e2ee_sessions').all();
|
|
627
|
-
return rows.map((row) => ({
|
|
628
|
-
...jsonParseObject(this._revealText(`session/${row.session_id}`, row.data_enc)),
|
|
629
|
-
session_id: row.session_id,
|
|
630
|
-
updated_at: row.updated_at,
|
|
631
|
-
}));
|
|
632
|
-
}
|
|
633
|
-
deleteSession(sessionId) {
|
|
634
|
-
this._db.prepare('DELETE FROM e2ee_sessions WHERE session_id = ?').run(sessionId);
|
|
635
|
-
}
|
|
636
139
|
saveInstanceState(deviceId, slotId, state) {
|
|
637
140
|
const slot = slotId || '_singleton';
|
|
638
141
|
this._db.prepare('INSERT INTO instance_state (device_id, slot_id, data, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(device_id, slot_id) DO UPDATE SET data=excluded.data, updated_at=excluded.updated_at').run(deviceId, slot, JSON.stringify(state), Date.now());
|
|
@@ -680,10 +183,9 @@ export class AIDDatabase {
|
|
|
680
183
|
result[row.key] = row.value;
|
|
681
184
|
return result;
|
|
682
185
|
}
|
|
683
|
-
// ── Group State ─────────────────────────────────────────────
|
|
684
186
|
saveGroupState(groupId, stateVersion, stateHash, keyEpoch, membershipJson, policyJson) {
|
|
685
|
-
this._db.prepare(`INSERT INTO group_state (group_id, state_version, state_hash, key_epoch, membership_json, policy_json, updated_at)
|
|
686
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
187
|
+
this._db.prepare(`INSERT INTO group_state (group_id, state_version, state_hash, key_epoch, membership_json, policy_json, updated_at)
|
|
188
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
687
189
|
ON CONFLICT(group_id) DO UPDATE SET state_version=excluded.state_version, state_hash=excluded.state_hash, key_epoch=excluded.key_epoch, membership_json=excluded.membership_json, policy_json=excluded.policy_json, updated_at=excluded.updated_at`).run(groupId, stateVersion, stateHash, keyEpoch, membershipJson, policyJson, Date.now());
|
|
688
190
|
}
|
|
689
191
|
loadGroupState(groupId) {
|