@onyx-p/imlib-web 3.0.1 → 3.0.2
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/README.md +108 -5
- package/database/main.cjs +341 -0
- package/database/preload.cjs +45 -0
- package/database/worker.cjs +592 -0
- package/index.esm.js +4 -4
- package/index.umd.js +4 -4
- package/package.json +28 -2
- package/types/database/contracts.d.ts +44 -0
- package/types/database/main/index.d.ts +2 -0
- package/types/database/main/ipc.d.ts +18 -0
- package/types/database/main/keyStore.d.ts +14 -0
- package/types/database/main/messageStore.d.ts +58 -0
- package/types/database/main/migrations.d.ts +9 -0
- package/types/database/main/service.d.ts +36 -0
- package/types/database/main/sqlTypes.d.ts +2 -0
- package/types/database/main/worker.d.ts +1 -0
- package/types/database/main/workerProtocol.d.ts +34 -0
- package/types/database/main-entry.d.ts +12 -0
- package/types/database/preload-entry.d.ts +1 -0
- package/types/database/preload.d.ts +8 -0
- package/types/database/renderer/bridge.d.ts +3 -0
- package/types/database/renderer/legacyIndexedDbMigration.d.ts +28 -0
- package/types/database/renderer/messageRepository.d.ts +43 -0
- package/types/index.d.ts +13 -2
package/README.md
CHANGED
|
@@ -1,14 +1,117 @@
|
|
|
1
|
-
|
|
1
|
+
# ac-imlib-web
|
|
2
2
|
|
|
3
|
+
Electron-only IM SDK with a Signal-style encrypted local message database.
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
6
8
|
npm install @onyx-p/imlib-web --save
|
|
7
9
|
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
The database runtime requires Electron 30 or newer. The local database uses
|
|
12
|
+
`@signalapp/sqlcipher`; review its AGPL-3.0-only license before distribution.
|
|
13
|
+
|
|
14
|
+
## Electron 主进程
|
|
15
|
+
|
|
16
|
+
Initialize the database service after `app.whenReady()`. This creates one
|
|
17
|
+
primary writer worker and three reader workers. The primary connection owns all
|
|
18
|
+
writes and schema migrations.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
const { app, BrowserWindow } = require('electron')
|
|
22
|
+
const {
|
|
23
|
+
initializeDatabaseMain
|
|
24
|
+
} = require('@onyx-p/imlib-web/main')
|
|
25
|
+
|
|
26
|
+
let database
|
|
27
|
+
|
|
28
|
+
app.whenReady().then(() => {
|
|
29
|
+
database = initializeDatabaseMain({
|
|
30
|
+
// Recommended: restrict IPC to your own renderer origin.
|
|
31
|
+
isTrustedEvent(event) {
|
|
32
|
+
return event.senderFrame?.url.startsWith('file://') === true
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const window = new BrowserWindow({
|
|
37
|
+
webPreferences: {
|
|
38
|
+
contextIsolation: true,
|
|
39
|
+
nodeIntegration: false,
|
|
40
|
+
preload: require.resolve('@onyx-p/imlib-web/preload')
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If the application already has a preload script, load the database preload
|
|
47
|
+
entry from that script instead:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
require('@onyx-p/imlib-web/preload')
|
|
10
51
|
```
|
|
11
|
-
|
|
52
|
+
|
|
53
|
+
The preload exposes only `window.acimDatabase.request()`. Renderer code cannot
|
|
54
|
+
execute arbitrary SQL or invoke arbitrary Electron channels.
|
|
55
|
+
|
|
56
|
+
## Renderer
|
|
57
|
+
|
|
58
|
+
The public IM API remains in the default package entry. Await user setup so the
|
|
59
|
+
SQLCipher database, schema migrations, and legacy import finish before message
|
|
60
|
+
synchronization starts.
|
|
61
|
+
|
|
62
|
+
```js
|
|
12
63
|
import * as ACIMLib from '@onyx-p/imlib-web'
|
|
64
|
+
|
|
65
|
+
ACIMLib.init({ appkey: 'your-app-key' })
|
|
66
|
+
await ACIMLib.setUserLogged(profile)
|
|
67
|
+
await ACIMLib.connect()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 数据库与迁移
|
|
71
|
+
|
|
72
|
+
- Database files are stored below Electron's `userData/sql` directory.
|
|
73
|
+
- Account identifiers are hashed before being used as filenames.
|
|
74
|
+
- Each account receives an independent random 256-bit SQLCipher key.
|
|
75
|
+
- Keys are encrypted with Electron `safeStorage` before they are written to
|
|
76
|
+
`database-keys.json`.
|
|
77
|
+
- Schema changes run automatically through ordered `PRAGMA user_version`
|
|
78
|
+
migrations in the primary worker.
|
|
79
|
+
- SQLCipher runs in WAL mode with foreign keys enabled and full synchronous
|
|
80
|
+
durability.
|
|
81
|
+
|
|
82
|
+
On first open, the renderer looks for the previous IndexedDB database named
|
|
83
|
+
`im_message_cache_1_<appKey>_<userId>`. Existing `messages_new` and
|
|
84
|
+
`dialogStates_new` records are read in bounded batches, legacy content fields
|
|
85
|
+
are decrypted, and records are idempotently inserted into SQLCipher. Completion
|
|
86
|
+
is recorded only after every batch commits. The old IndexedDB database is kept
|
|
87
|
+
as a recovery copy.
|
|
88
|
+
|
|
89
|
+
Migration runs automatically. It can also be retried explicitly with progress:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
const result = await ACIMLib.migrateLegacyDatabase(progress => {
|
|
93
|
+
console.log(
|
|
94
|
+
`Migrated ${progress.processedMessages} messages and ` +
|
|
95
|
+
`${progress.processedDialogStates} dialog states`
|
|
96
|
+
)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
console.log(result)
|
|
100
|
+
// { migrated: true|false, messages: number, dialogStates: number }
|
|
13
101
|
```
|
|
14
102
|
|
|
103
|
+
If migration is interrupted, call the same API again. Imports use upserts, so
|
|
104
|
+
already committed batches are safe to repeat.
|
|
105
|
+
|
|
106
|
+
## 消息搜索
|
|
107
|
+
|
|
108
|
+
单会话搜索接口保持不变。全局搜索使用 Signal 风格的 FTS5 前缀匹配,
|
|
109
|
+
按消息时间倒序返回,默认最多 500 条:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
const result = await ACIMLib.searchMessages('项目进度')
|
|
113
|
+
console.log(result.data)
|
|
114
|
+
|
|
115
|
+
// 可选:限制返回数量
|
|
116
|
+
const recent = await ACIMLib.searchMessages('signal', 50)
|
|
117
|
+
```
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_path = require('node:path');
|
|
4
|
+
var electron = require('electron');
|
|
5
|
+
var node_crypto = require('node:crypto');
|
|
6
|
+
var node_fs = require('node:fs');
|
|
7
|
+
var node_worker_threads = require('node:worker_threads');
|
|
8
|
+
|
|
9
|
+
class DatabaseKeyStore {
|
|
10
|
+
directory;
|
|
11
|
+
safeStorage;
|
|
12
|
+
filePath;
|
|
13
|
+
constructor(directory, safeStorage) {
|
|
14
|
+
this.directory = directory;
|
|
15
|
+
this.safeStorage = safeStorage;
|
|
16
|
+
this.filePath = node_path.join(directory, 'database-keys.json');
|
|
17
|
+
}
|
|
18
|
+
getOrCreate(accountId) {
|
|
19
|
+
const keys = this.readKeys();
|
|
20
|
+
const encrypted = keys[accountId];
|
|
21
|
+
if (encrypted) {
|
|
22
|
+
const key = this.safeStorage.decryptString(Buffer.from(encrypted, 'base64'));
|
|
23
|
+
this.validateKey(key);
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
const key = node_crypto.randomBytes(32).toString('hex');
|
|
27
|
+
keys[accountId] = Buffer.from(this.safeStorage.encryptString(key)).toString('base64');
|
|
28
|
+
this.writeKeys(keys);
|
|
29
|
+
return key;
|
|
30
|
+
}
|
|
31
|
+
readKeys() {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(node_fs.readFileSync(this.filePath, 'utf8'));
|
|
34
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
35
|
+
throw new Error('Database key file must contain an object');
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code === 'ENOENT') {
|
|
41
|
+
return Object.create(null);
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
writeKeys(keys) {
|
|
47
|
+
node_fs.mkdirSync(this.directory, { recursive: true });
|
|
48
|
+
const temporaryPath = `${this.filePath}.tmp`;
|
|
49
|
+
node_fs.writeFileSync(temporaryPath, JSON.stringify(keys), { mode: 0o600 });
|
|
50
|
+
node_fs.renameSync(temporaryPath, this.filePath);
|
|
51
|
+
}
|
|
52
|
+
validateKey(key) {
|
|
53
|
+
if (!/^[0-9a-f]{64}$/i.test(key)) {
|
|
54
|
+
throw new Error('Stored database key is invalid');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DATABASE_CHANNEL = 'acim-database:request';
|
|
60
|
+
const READ_METHOD_NAMES = [
|
|
61
|
+
'getMessages',
|
|
62
|
+
'getLatestMessage',
|
|
63
|
+
'getLatestMessages',
|
|
64
|
+
'getDialogLoadedState',
|
|
65
|
+
'getMessageById',
|
|
66
|
+
'getMessageByUId',
|
|
67
|
+
'getMessageIdRange',
|
|
68
|
+
'searchMessages',
|
|
69
|
+
'searchTextMessages',
|
|
70
|
+
'getLegacyMigrationState'
|
|
71
|
+
];
|
|
72
|
+
const WRITE_METHOD_NAMES = [
|
|
73
|
+
'addMessages',
|
|
74
|
+
'clearConversationCache',
|
|
75
|
+
'clearAllCache',
|
|
76
|
+
'removeMessagesByUId',
|
|
77
|
+
'updateMessageReceiptStatus',
|
|
78
|
+
'clearBurnAfterReadingExpiredMessages',
|
|
79
|
+
'upsertMessage',
|
|
80
|
+
'convertToRecallMessages',
|
|
81
|
+
'importLegacyBatch',
|
|
82
|
+
'completeLegacyMigration'
|
|
83
|
+
];
|
|
84
|
+
const READ_METHODS = new Set(READ_METHOD_NAMES);
|
|
85
|
+
const WRITE_METHODS = new Set(WRITE_METHOD_NAMES);
|
|
86
|
+
function serializeDatabaseError(error) {
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
return {
|
|
89
|
+
name: error.name,
|
|
90
|
+
message: error.message,
|
|
91
|
+
stack: error.stack
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
name: 'Error',
|
|
96
|
+
message: String(error),
|
|
97
|
+
stack: undefined
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isRecord(value) {
|
|
102
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
function isTopLevelSender(event) {
|
|
105
|
+
if (!isRecord(event)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const candidate = event;
|
|
109
|
+
return (candidate.senderFrame !== undefined &&
|
|
110
|
+
candidate.senderFrame === candidate.sender?.mainFrame);
|
|
111
|
+
}
|
|
112
|
+
function parseRequest(value) {
|
|
113
|
+
if (!isRecord(value) || typeof value.type !== 'string') {
|
|
114
|
+
throw new Error('Invalid database IPC request');
|
|
115
|
+
}
|
|
116
|
+
if (value.type === 'close') {
|
|
117
|
+
return { type: 'close' };
|
|
118
|
+
}
|
|
119
|
+
if (value.type === 'open') {
|
|
120
|
+
if (!isRecord(value.options) ||
|
|
121
|
+
typeof value.options.appKey !== 'string' ||
|
|
122
|
+
typeof value.options.userId !== 'string' ||
|
|
123
|
+
!value.options.appKey ||
|
|
124
|
+
!value.options.userId) {
|
|
125
|
+
throw new Error('Invalid database open options');
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
type: 'open',
|
|
129
|
+
options: { appKey: value.options.appKey, userId: value.options.userId }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (value.type === 'read' || value.type === 'write') {
|
|
133
|
+
const allowed = value.type === 'read' ? READ_METHODS : WRITE_METHODS;
|
|
134
|
+
if (typeof value.method !== 'string' ||
|
|
135
|
+
!allowed.has(value.method) ||
|
|
136
|
+
!Array.isArray(value.args)) {
|
|
137
|
+
throw new Error(`Invalid database ${value.type} request`);
|
|
138
|
+
}
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`Unsupported database IPC request: ${value.type}`);
|
|
142
|
+
}
|
|
143
|
+
function registerDatabaseIpc({ ipcMain, service, isTrustedEvent = isTopLevelSender }) {
|
|
144
|
+
ipcMain.handle(DATABASE_CHANNEL, async (event, value) => {
|
|
145
|
+
try {
|
|
146
|
+
if (!isTrustedEvent(event)) {
|
|
147
|
+
throw new Error('Database IPC sender is not trusted');
|
|
148
|
+
}
|
|
149
|
+
const request = parseRequest(value);
|
|
150
|
+
let result;
|
|
151
|
+
if (request.type === 'open') {
|
|
152
|
+
result = await service.open(request.options);
|
|
153
|
+
}
|
|
154
|
+
else if (request.type === 'close') {
|
|
155
|
+
result = await service.close();
|
|
156
|
+
}
|
|
157
|
+
else if (request.type === 'read') {
|
|
158
|
+
result = await service.read(request.method, request.args);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
result = await service.write(request.method, request.args);
|
|
162
|
+
}
|
|
163
|
+
return { ok: true, value: result };
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return { ok: false, error: serializeDatabaseError(error) };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return () => ipcMain.removeHandler(DATABASE_CHANNEL);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
class MainDatabaseService {
|
|
173
|
+
options;
|
|
174
|
+
workerCount;
|
|
175
|
+
pool = [];
|
|
176
|
+
accountId = null;
|
|
177
|
+
constructor(options) {
|
|
178
|
+
this.options = options;
|
|
179
|
+
this.workerCount = options.workerCount ?? 4;
|
|
180
|
+
if (this.workerCount < 2) {
|
|
181
|
+
throw new Error('Database service requires one writer and at least one reader');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async open(options) {
|
|
185
|
+
const accountId = node_crypto.createHash('sha256')
|
|
186
|
+
.update(`${options.appKey}:${options.userId}`)
|
|
187
|
+
.digest('hex');
|
|
188
|
+
if (this.accountId === accountId && this.pool.length > 0) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
await this.close();
|
|
192
|
+
const sqlDirectory = node_path.join(this.options.userDataPath, 'sql');
|
|
193
|
+
node_fs.mkdirSync(sqlDirectory, { recursive: true });
|
|
194
|
+
const dbPath = node_path.join(sqlDirectory, `${accountId}.sqlite`);
|
|
195
|
+
const key = this.options.keyStore.getOrCreate(accountId);
|
|
196
|
+
try {
|
|
197
|
+
for (let index = 0; index < this.workerCount; index += 1) {
|
|
198
|
+
this.pool.push({ client: this.options.workerFactory(index), load: 0 });
|
|
199
|
+
}
|
|
200
|
+
await this.pool[0].client.request({
|
|
201
|
+
type: 'init',
|
|
202
|
+
dbPath,
|
|
203
|
+
key,
|
|
204
|
+
isPrimary: true
|
|
205
|
+
});
|
|
206
|
+
await Promise.all(this.pool.slice(1).map(entry => entry.client.request({ type: 'init', dbPath, key, isPrimary: false })));
|
|
207
|
+
this.accountId = accountId;
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
await this.close();
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async read(method, args) {
|
|
215
|
+
this.assertOpen();
|
|
216
|
+
const readers = this.pool.slice(1);
|
|
217
|
+
const entry = readers.reduce((least, candidate) => candidate.load < least.load ? candidate : least);
|
|
218
|
+
entry.load += 1;
|
|
219
|
+
try {
|
|
220
|
+
return await entry.client.request({ type: 'call', access: 'read', method, args });
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
entry.load -= 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async write(method, args) {
|
|
227
|
+
this.assertOpen();
|
|
228
|
+
const primary = this.pool[0];
|
|
229
|
+
primary.load += 1;
|
|
230
|
+
try {
|
|
231
|
+
return await primary.client.request({ type: 'call', access: 'write', method, args });
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
primary.load -= 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async close() {
|
|
238
|
+
const entries = this.pool.splice(0);
|
|
239
|
+
this.accountId = null;
|
|
240
|
+
await Promise.all(entries.map(async (entry) => {
|
|
241
|
+
try {
|
|
242
|
+
await entry.client.request({ type: 'close' });
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
await entry.client.close();
|
|
246
|
+
}
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
assertOpen() {
|
|
250
|
+
if (!this.accountId || this.pool.length === 0) {
|
|
251
|
+
throw new Error('Database service is not open');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
class ThreadWorkerClient {
|
|
256
|
+
worker;
|
|
257
|
+
sequence = 0;
|
|
258
|
+
pending = new Map();
|
|
259
|
+
constructor(worker) {
|
|
260
|
+
this.worker = worker;
|
|
261
|
+
worker.on('message', (response) => {
|
|
262
|
+
const pending = this.pending.get(response.sequence);
|
|
263
|
+
if (!pending) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.pending.delete(response.sequence);
|
|
267
|
+
if (response.ok) {
|
|
268
|
+
pending.resolve(response.value);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
const error = new Error(response.error?.message ?? 'Database worker failed');
|
|
272
|
+
error.name = response.error?.name ?? 'Error';
|
|
273
|
+
error.stack = response.error?.stack;
|
|
274
|
+
pending.reject(error);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const rejectAll = (error) => {
|
|
278
|
+
for (const pending of this.pending.values()) {
|
|
279
|
+
pending.reject(error);
|
|
280
|
+
}
|
|
281
|
+
this.pending.clear();
|
|
282
|
+
};
|
|
283
|
+
worker.on('error', rejectAll);
|
|
284
|
+
worker.on('exit', code => {
|
|
285
|
+
if (code !== 0) {
|
|
286
|
+
rejectAll(new Error(`Database worker exited with code ${code}`));
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
request(request) {
|
|
291
|
+
const sequence = ++this.sequence;
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
this.pending.set(sequence, { resolve, reject });
|
|
294
|
+
const wrapped = { sequence, request };
|
|
295
|
+
this.worker.postMessage(wrapped);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async close() {
|
|
299
|
+
await this.worker.terminate();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function createThreadWorkerFactory(workerPath) {
|
|
303
|
+
return () => new ThreadWorkerClient(new node_worker_threads.Worker(workerPath));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function initializeDatabaseMain(options = {}) {
|
|
307
|
+
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
308
|
+
throw new Error('Electron safeStorage is unavailable; refusing to persist a database key');
|
|
309
|
+
}
|
|
310
|
+
const userDataPath = options.userDataPath ?? electron.app.getPath('userData');
|
|
311
|
+
const workerPath = options.workerPath ?? node_path.join(__dirname, 'worker.cjs');
|
|
312
|
+
const keyStore = new DatabaseKeyStore(node_path.join(userDataPath, 'sql'), electron.safeStorage);
|
|
313
|
+
const service = new MainDatabaseService({
|
|
314
|
+
userDataPath,
|
|
315
|
+
keyStore,
|
|
316
|
+
workerFactory: createThreadWorkerFactory(workerPath)
|
|
317
|
+
});
|
|
318
|
+
const removeIpcHandler = registerDatabaseIpc({
|
|
319
|
+
ipcMain: electron.ipcMain,
|
|
320
|
+
service,
|
|
321
|
+
isTrustedEvent: options.isTrustedEvent
|
|
322
|
+
});
|
|
323
|
+
const closeBeforeQuit = () => {
|
|
324
|
+
void service.close();
|
|
325
|
+
};
|
|
326
|
+
electron.app.once('before-quit', closeBeforeQuit);
|
|
327
|
+
return {
|
|
328
|
+
service,
|
|
329
|
+
async dispose() {
|
|
330
|
+
removeIpcHandler();
|
|
331
|
+
electron.app.removeListener('before-quit', closeBeforeQuit);
|
|
332
|
+
await service.close();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
exports.DatabaseKeyStore = DatabaseKeyStore;
|
|
338
|
+
exports.MainDatabaseService = MainDatabaseService;
|
|
339
|
+
exports.ThreadWorkerClient = ThreadWorkerClient;
|
|
340
|
+
exports.createThreadWorkerFactory = createThreadWorkerFactory;
|
|
341
|
+
exports.initializeDatabaseMain = initializeDatabaseMain;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var electron = require('electron');
|
|
4
|
+
|
|
5
|
+
const DATABASE_GLOBAL = 'acimDatabase';
|
|
6
|
+
const DATABASE_CHANNEL = 'acim-database:request';
|
|
7
|
+
const READ_METHOD_NAMES = [
|
|
8
|
+
'getMessages',
|
|
9
|
+
'getLatestMessage',
|
|
10
|
+
'getLatestMessages',
|
|
11
|
+
'getDialogLoadedState',
|
|
12
|
+
'getMessageById',
|
|
13
|
+
'getMessageByUId',
|
|
14
|
+
'getMessageIdRange',
|
|
15
|
+
'searchMessages',
|
|
16
|
+
'searchTextMessages',
|
|
17
|
+
'getLegacyMigrationState'
|
|
18
|
+
];
|
|
19
|
+
const WRITE_METHOD_NAMES = [
|
|
20
|
+
'addMessages',
|
|
21
|
+
'clearConversationCache',
|
|
22
|
+
'clearAllCache',
|
|
23
|
+
'removeMessagesByUId',
|
|
24
|
+
'updateMessageReceiptStatus',
|
|
25
|
+
'clearBurnAfterReadingExpiredMessages',
|
|
26
|
+
'upsertMessage',
|
|
27
|
+
'convertToRecallMessages',
|
|
28
|
+
'importLegacyBatch',
|
|
29
|
+
'completeLegacyMigration'
|
|
30
|
+
];
|
|
31
|
+
new Set(READ_METHOD_NAMES);
|
|
32
|
+
new Set(WRITE_METHOD_NAMES);
|
|
33
|
+
|
|
34
|
+
function exposeDatabaseBridge(contextBridge, ipcRenderer) {
|
|
35
|
+
const bridge = {
|
|
36
|
+
request(request) {
|
|
37
|
+
return ipcRenderer.invoke(DATABASE_CHANNEL, request);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
contextBridge.exposeInMainWorld(DATABASE_GLOBAL, bridge);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
exposeDatabaseBridge(electron.contextBridge, electron.ipcRenderer);
|
|
44
|
+
|
|
45
|
+
exports.exposeDatabaseBridge = exposeDatabaseBridge;
|