@roeehrl/tinode-sdk 0.25.1-sqlite.5 → 0.25.1-sqlite.7
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/package.json +4 -2
- package/src/connection.js +14 -0
- package/src/db.js +8 -0
- package/src/storage-sqlite.js +112 -154
- package/types/index.d.ts +2 -1
- package/umd/tinode.dev.js +109 -3
- package/umd/tinode.dev.js.map +1 -1
- package/umd/tinode.prod.js +1 -1
- package/umd/tinode.prod.js.map +1 -1
- package/version.js +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roeehrl/tinode-sdk",
|
|
3
3
|
"description": "Tinode SDK fork with Storage interface for SQLite persistence in React Native",
|
|
4
|
-
"version": "0.25.1-sqlite.
|
|
4
|
+
"version": "0.25.1-sqlite.7",
|
|
5
5
|
"types": "./types/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"format": "js-beautify -r src/*.js",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"vers": "echo \"export const PACKAGE_VERSION = \\\"`node -p -e \"require('./package.json').version\"`\\\";\" > version.js",
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
|
-
"browserslist": [
|
|
16
|
+
"browserslist": [
|
|
17
|
+
"defaults"
|
|
18
|
+
],
|
|
17
19
|
"repository": {
|
|
18
20
|
"type": "git",
|
|
19
21
|
"url": "git+https://github.com/roeehrl/tinode-js.git"
|
package/src/connection.js
CHANGED
|
@@ -489,6 +489,20 @@ export default class Connection {
|
|
|
489
489
|
if (this.#socket && (this.#socket.readyState == this.#socket.OPEN)) {
|
|
490
490
|
this.#socket.send(msg);
|
|
491
491
|
} else {
|
|
492
|
+
// Socket is not connected - trigger disconnect callback if not already done
|
|
493
|
+
// This handles the case where the server dies but onclose hasn't fired yet
|
|
494
|
+
if (this.onDisconnect) {
|
|
495
|
+
// Use setTimeout to avoid blocking the throw
|
|
496
|
+
setTimeout(() => {
|
|
497
|
+
if (this.onDisconnect) {
|
|
498
|
+
this.onDisconnect(new CommError(NETWORK_ERROR_TEXT, NETWORK_ERROR), NETWORK_ERROR);
|
|
499
|
+
}
|
|
500
|
+
}, 0);
|
|
501
|
+
}
|
|
502
|
+
// Clean up the socket reference if it exists but isn't connected
|
|
503
|
+
if (this.#socket) {
|
|
504
|
+
this.#socket = null;
|
|
505
|
+
}
|
|
492
506
|
throw new Error("Websocket is not connected");
|
|
493
507
|
}
|
|
494
508
|
};
|
package/src/db.js
CHANGED
|
@@ -220,6 +220,14 @@ export default class DB {
|
|
|
220
220
|
* @returns {Promise} promise resolved/rejected on operation completion.
|
|
221
221
|
*/
|
|
222
222
|
updTopic(topic) {
|
|
223
|
+
// Skip topics that haven't been confirmed by the server yet.
|
|
224
|
+
// The _new flag is true for topics created locally but not yet subscribed.
|
|
225
|
+
// Only persist after subscribe succeeds and server assigns the real topic name.
|
|
226
|
+
if (topic?._new) {
|
|
227
|
+
console.log('[DB] updTopic DEFERRED - topic not yet confirmed by server:', topic.name);
|
|
228
|
+
return Promise.resolve();
|
|
229
|
+
}
|
|
230
|
+
|
|
223
231
|
console.log('[DB] updTopic CALLED:', topic?.name, 'shouldDelegate:', this.#shouldDelegate());
|
|
224
232
|
// Delegate to custom storage if set
|
|
225
233
|
if (this.#shouldDelegate()) {
|
package/src/storage-sqlite.js
CHANGED
|
@@ -198,6 +198,75 @@ export default class SQLiteStorage {
|
|
|
198
198
|
return this._ready && this._db !== null;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Attempt to recover the database connection if it becomes stale.
|
|
203
|
+
* This handles cases where the native database handle becomes invalid
|
|
204
|
+
* after app lifecycle events or reconnections.
|
|
205
|
+
* @returns {Promise<boolean>} True if recovery succeeded.
|
|
206
|
+
*/
|
|
207
|
+
async _recoverDatabase() {
|
|
208
|
+
const self = this;
|
|
209
|
+
try {
|
|
210
|
+
console.log('[SQLiteStorage] Attempting database recovery...');
|
|
211
|
+
|
|
212
|
+
// Close existing connection if any
|
|
213
|
+
if (self._db) {
|
|
214
|
+
try {
|
|
215
|
+
await self._db.closeAsync();
|
|
216
|
+
} catch (closeErr) {
|
|
217
|
+
// Ignore close errors - the handle may already be invalid
|
|
218
|
+
console.log('[SQLiteStorage] Close during recovery failed (expected):', closeErr.message);
|
|
219
|
+
}
|
|
220
|
+
self._db = null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Re-open the database
|
|
224
|
+
self._ready = false;
|
|
225
|
+
await self.initDatabase();
|
|
226
|
+
|
|
227
|
+
console.log('[SQLiteStorage] Database recovery successful');
|
|
228
|
+
return true;
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.error('[SQLiteStorage] Database recovery failed:', err);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Wrapper to execute database operations with automatic recovery.
|
|
237
|
+
* If an operation fails with NullPointerException, attempts recovery and retry.
|
|
238
|
+
* @param {function} operation - Async function to execute
|
|
239
|
+
* @param {string} operationName - Name for logging
|
|
240
|
+
* @returns {Promise<any>} Result of the operation
|
|
241
|
+
*/
|
|
242
|
+
async _withRecovery(operation, operationName) {
|
|
243
|
+
const self = this;
|
|
244
|
+
try {
|
|
245
|
+
return await operation();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
// Check if this is a stale database handle error
|
|
248
|
+
const isStaleHandle = err.message && (
|
|
249
|
+
err.message.includes('NullPointerException') ||
|
|
250
|
+
err.message.includes('prepareAsync') ||
|
|
251
|
+
err.message.includes('database is not open') ||
|
|
252
|
+
err.message.includes('SQLiteDatabase')
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (isStaleHandle) {
|
|
256
|
+
console.warn('[SQLiteStorage]', operationName, 'failed with stale handle, attempting recovery...');
|
|
257
|
+
const recovered = await self._recoverDatabase();
|
|
258
|
+
if (recovered) {
|
|
259
|
+
// Retry the operation once
|
|
260
|
+
console.log('[SQLiteStorage] Retrying', operationName, 'after recovery...');
|
|
261
|
+
return await operation();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Re-throw if not recoverable
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
201
270
|
// ==================== Topics ====================
|
|
202
271
|
|
|
203
272
|
/**
|
|
@@ -207,11 +276,20 @@ export default class SQLiteStorage {
|
|
|
207
276
|
*/
|
|
208
277
|
async updTopic(topic) {
|
|
209
278
|
const self = this;
|
|
279
|
+
|
|
280
|
+
// Skip topics that haven't been confirmed by the server yet.
|
|
281
|
+
// The _new flag is true for topics created locally but not yet subscribed.
|
|
282
|
+
// Only persist after subscribe succeeds and server assigns the real topic name.
|
|
283
|
+
if (topic?._new) {
|
|
284
|
+
console.log('[SQLiteStorage] updTopic DEFERRED - topic not yet confirmed by server:', topic.name);
|
|
285
|
+
return Promise.resolve();
|
|
286
|
+
}
|
|
287
|
+
|
|
210
288
|
if (!self.isReady()) {
|
|
211
289
|
return Promise.resolve();
|
|
212
290
|
}
|
|
213
291
|
|
|
214
|
-
|
|
292
|
+
return self._withRecovery(async () => {
|
|
215
293
|
// Get existing topic to merge data
|
|
216
294
|
const existing = await self._db.getFirstAsync(
|
|
217
295
|
'SELECT * FROM topics WHERE name = ?',
|
|
@@ -237,10 +315,7 @@ export default class SQLiteStorage {
|
|
|
237
315
|
data._aux, data._deleted, data.tags, data.acs
|
|
238
316
|
]);
|
|
239
317
|
console.log('[SQLiteStorage] updTopic SUCCESS:', data.name);
|
|
240
|
-
}
|
|
241
|
-
console.error('[SQLiteStorage] updTopic FAILED:', err.message, 'topic:', topic.name);
|
|
242
|
-
throw err;
|
|
243
|
-
}
|
|
318
|
+
}, 'updTopic');
|
|
244
319
|
}
|
|
245
320
|
|
|
246
321
|
/**
|
|
@@ -255,15 +330,12 @@ export default class SQLiteStorage {
|
|
|
255
330
|
return Promise.resolve();
|
|
256
331
|
}
|
|
257
332
|
|
|
258
|
-
|
|
333
|
+
return self._withRecovery(async () => {
|
|
259
334
|
await self._db.runAsync(
|
|
260
335
|
'UPDATE topics SET _deleted = ? WHERE name = ?',
|
|
261
336
|
[deleted ? 1 : 0, name]
|
|
262
337
|
);
|
|
263
|
-
}
|
|
264
|
-
self._logger('SQLiteStorage', 'markTopicAsDeleted error:', err);
|
|
265
|
-
throw err;
|
|
266
|
-
}
|
|
338
|
+
}, 'markTopicAsDeleted');
|
|
267
339
|
}
|
|
268
340
|
|
|
269
341
|
/**
|
|
@@ -277,7 +349,7 @@ export default class SQLiteStorage {
|
|
|
277
349
|
return Promise.resolve();
|
|
278
350
|
}
|
|
279
351
|
|
|
280
|
-
|
|
352
|
+
return self._withRecovery(async () => {
|
|
281
353
|
// Delete topic, subscriptions, and messages in a transaction
|
|
282
354
|
await self._db.withTransactionAsync(async function() {
|
|
283
355
|
await self._db.runAsync('DELETE FROM topics WHERE name = ?', [name]);
|
|
@@ -285,10 +357,7 @@ export default class SQLiteStorage {
|
|
|
285
357
|
await self._db.runAsync('DELETE FROM messages WHERE topic = ?', [name]);
|
|
286
358
|
await self._db.runAsync('DELETE FROM dellog WHERE topic = ?', [name]);
|
|
287
359
|
});
|
|
288
|
-
}
|
|
289
|
-
self._logger('SQLiteStorage', 'remTopic error:', err);
|
|
290
|
-
throw err;
|
|
291
|
-
}
|
|
360
|
+
}, 'remTopic');
|
|
292
361
|
}
|
|
293
362
|
|
|
294
363
|
/**
|
|
@@ -303,7 +372,7 @@ export default class SQLiteStorage {
|
|
|
303
372
|
return [];
|
|
304
373
|
}
|
|
305
374
|
|
|
306
|
-
|
|
375
|
+
return self._withRecovery(async () => {
|
|
307
376
|
const rows = await self._db.getAllAsync('SELECT * FROM topics');
|
|
308
377
|
const topics = rows.map(function(row) {
|
|
309
378
|
return self._deserializeTopicRow(row);
|
|
@@ -316,10 +385,7 @@ export default class SQLiteStorage {
|
|
|
316
385
|
}
|
|
317
386
|
|
|
318
387
|
return topics;
|
|
319
|
-
}
|
|
320
|
-
self._logger('SQLiteStorage', 'mapTopics error:', err);
|
|
321
|
-
throw err;
|
|
322
|
-
}
|
|
388
|
+
}, 'mapTopics');
|
|
323
389
|
}
|
|
324
390
|
|
|
325
391
|
/**
|
|
@@ -361,15 +427,12 @@ export default class SQLiteStorage {
|
|
|
361
427
|
return Promise.resolve();
|
|
362
428
|
}
|
|
363
429
|
|
|
364
|
-
|
|
430
|
+
return self._withRecovery(async () => {
|
|
365
431
|
await self._db.runAsync(
|
|
366
432
|
'INSERT OR REPLACE INTO users (uid, public) VALUES (?, ?)',
|
|
367
433
|
[uid, JSON.stringify(pub)]
|
|
368
434
|
);
|
|
369
|
-
}
|
|
370
|
-
self._logger('SQLiteStorage', 'updUser error:', err);
|
|
371
|
-
throw err;
|
|
372
|
-
}
|
|
435
|
+
}, 'updUser');
|
|
373
436
|
}
|
|
374
437
|
|
|
375
438
|
/**
|
|
@@ -383,12 +446,9 @@ export default class SQLiteStorage {
|
|
|
383
446
|
return Promise.resolve();
|
|
384
447
|
}
|
|
385
448
|
|
|
386
|
-
|
|
449
|
+
return self._withRecovery(async () => {
|
|
387
450
|
await self._db.runAsync('DELETE FROM users WHERE uid = ?', [uid]);
|
|
388
|
-
}
|
|
389
|
-
self._logger('SQLiteStorage', 'remUser error:', err);
|
|
390
|
-
throw err;
|
|
391
|
-
}
|
|
451
|
+
}, 'remUser');
|
|
392
452
|
}
|
|
393
453
|
|
|
394
454
|
/**
|
|
@@ -403,7 +463,7 @@ export default class SQLiteStorage {
|
|
|
403
463
|
return [];
|
|
404
464
|
}
|
|
405
465
|
|
|
406
|
-
|
|
466
|
+
return self._withRecovery(async () => {
|
|
407
467
|
const rows = await self._db.getAllAsync('SELECT * FROM users');
|
|
408
468
|
const users = rows.map(function(row) {
|
|
409
469
|
return {
|
|
@@ -419,10 +479,7 @@ export default class SQLiteStorage {
|
|
|
419
479
|
}
|
|
420
480
|
|
|
421
481
|
return users;
|
|
422
|
-
}
|
|
423
|
-
self._logger('SQLiteStorage', 'mapUsers error:', err);
|
|
424
|
-
throw err;
|
|
425
|
-
}
|
|
482
|
+
}, 'mapUsers');
|
|
426
483
|
}
|
|
427
484
|
|
|
428
485
|
/**
|
|
@@ -436,7 +493,7 @@ export default class SQLiteStorage {
|
|
|
436
493
|
return undefined;
|
|
437
494
|
}
|
|
438
495
|
|
|
439
|
-
|
|
496
|
+
return self._withRecovery(async () => {
|
|
440
497
|
const row = await self._db.getFirstAsync(
|
|
441
498
|
'SELECT * FROM users WHERE uid = ?',
|
|
442
499
|
[uid]
|
|
@@ -450,10 +507,7 @@ export default class SQLiteStorage {
|
|
|
450
507
|
uid: row.uid,
|
|
451
508
|
public: self._parseJSON(row.public)
|
|
452
509
|
};
|
|
453
|
-
}
|
|
454
|
-
self._logger('SQLiteStorage', 'getUser error:', err);
|
|
455
|
-
throw err;
|
|
456
|
-
}
|
|
510
|
+
}, 'getUser');
|
|
457
511
|
}
|
|
458
512
|
|
|
459
513
|
// ==================== Subscriptions ====================
|
|
@@ -471,7 +525,7 @@ export default class SQLiteStorage {
|
|
|
471
525
|
return Promise.resolve();
|
|
472
526
|
}
|
|
473
527
|
|
|
474
|
-
|
|
528
|
+
return self._withRecovery(async () => {
|
|
475
529
|
// Get existing subscription
|
|
476
530
|
const existing = await self._db.getFirstAsync(
|
|
477
531
|
'SELECT * FROM subscriptions WHERE topic = ? AND uid = ?',
|
|
@@ -484,10 +538,7 @@ export default class SQLiteStorage {
|
|
|
484
538
|
'INSERT OR REPLACE INTO subscriptions (topic, uid, updated, mode, read, recv, clear, lastSeen, userAgent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
485
539
|
[data.topic, data.uid, data.updated, data.mode, data.read, data.recv, data.clear, data.lastSeen, data.userAgent]
|
|
486
540
|
);
|
|
487
|
-
}
|
|
488
|
-
self._logger('SQLiteStorage', 'updSubscription error:', err);
|
|
489
|
-
throw err;
|
|
490
|
-
}
|
|
541
|
+
}, 'updSubscription');
|
|
491
542
|
}
|
|
492
543
|
|
|
493
544
|
/**
|
|
@@ -503,7 +554,7 @@ export default class SQLiteStorage {
|
|
|
503
554
|
return [];
|
|
504
555
|
}
|
|
505
556
|
|
|
506
|
-
|
|
557
|
+
return self._withRecovery(async () => {
|
|
507
558
|
const rows = await self._db.getAllAsync(
|
|
508
559
|
'SELECT * FROM subscriptions WHERE topic = ?',
|
|
509
560
|
[topicName]
|
|
@@ -520,10 +571,7 @@ export default class SQLiteStorage {
|
|
|
520
571
|
}
|
|
521
572
|
|
|
522
573
|
return subs;
|
|
523
|
-
}
|
|
524
|
-
self._logger('SQLiteStorage', 'mapSubscriptions error:', err);
|
|
525
|
-
throw err;
|
|
526
|
-
}
|
|
574
|
+
}, 'mapSubscriptions');
|
|
527
575
|
}
|
|
528
576
|
|
|
529
577
|
// ==================== Messages ====================
|
|
@@ -539,41 +587,9 @@ export default class SQLiteStorage {
|
|
|
539
587
|
return Promise.resolve();
|
|
540
588
|
}
|
|
541
589
|
|
|
542
|
-
|
|
590
|
+
return self._withRecovery(async () => {
|
|
543
591
|
const data = self._serializeMessage(null, msg);
|
|
544
592
|
|
|
545
|
-
// Debug: log all values with their types
|
|
546
|
-
console.log('[SQLiteStorage] addMessage PARAMS:', JSON.stringify({
|
|
547
|
-
topic: {
|
|
548
|
-
value: data.topic,
|
|
549
|
-
type: typeof data.topic
|
|
550
|
-
},
|
|
551
|
-
seq: {
|
|
552
|
-
value: data.seq,
|
|
553
|
-
type: typeof data.seq
|
|
554
|
-
},
|
|
555
|
-
ts: {
|
|
556
|
-
value: data.ts,
|
|
557
|
-
type: typeof data.ts
|
|
558
|
-
},
|
|
559
|
-
_status: {
|
|
560
|
-
value: data._status,
|
|
561
|
-
type: typeof data._status
|
|
562
|
-
},
|
|
563
|
-
from: {
|
|
564
|
-
value: data.from,
|
|
565
|
-
type: typeof data.from
|
|
566
|
-
},
|
|
567
|
-
head: {
|
|
568
|
-
value: data.head ? data.head.substring(0, 50) : null,
|
|
569
|
-
type: typeof data.head
|
|
570
|
-
},
|
|
571
|
-
content: {
|
|
572
|
-
value: data.content ? data.content.substring(0, 50) : null,
|
|
573
|
-
type: typeof data.content
|
|
574
|
-
}
|
|
575
|
-
}));
|
|
576
|
-
|
|
577
593
|
// Build params array explicitly, converting undefined to null for SQLite
|
|
578
594
|
const params = [
|
|
579
595
|
data.topic,
|
|
@@ -585,19 +601,12 @@ export default class SQLiteStorage {
|
|
|
585
601
|
data.content !== undefined ? data.content : null
|
|
586
602
|
];
|
|
587
603
|
|
|
588
|
-
console.log('[SQLiteStorage] addMessage params array:', params.map((p, i) => `[${i}]=${typeof p}:${p === null ? 'null' : p === undefined ? 'undefined' : 'value'}`).join(', '));
|
|
589
|
-
|
|
590
604
|
await self._db.runAsync(
|
|
591
605
|
`INSERT OR REPLACE INTO messages (topic, seq, ts, _status, from_uid, head, content) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
592
606
|
params
|
|
593
607
|
);
|
|
594
608
|
console.log('[SQLiteStorage] addMessage SUCCESS:', data.topic, data.seq);
|
|
595
|
-
}
|
|
596
|
-
console.error('[SQLiteStorage] addMessage FAILED:', err);
|
|
597
|
-
console.error('[SQLiteStorage] addMessage FAILED err.message:', err.message);
|
|
598
|
-
console.error('[SQLiteStorage] addMessage FAILED err.stack:', err.stack);
|
|
599
|
-
throw err;
|
|
600
|
-
}
|
|
609
|
+
}, 'addMessage');
|
|
601
610
|
}
|
|
602
611
|
|
|
603
612
|
/**
|
|
@@ -613,15 +622,12 @@ export default class SQLiteStorage {
|
|
|
613
622
|
return Promise.resolve();
|
|
614
623
|
}
|
|
615
624
|
|
|
616
|
-
|
|
625
|
+
return self._withRecovery(async () => {
|
|
617
626
|
await self._db.runAsync(
|
|
618
627
|
'UPDATE messages SET _status = ? WHERE topic = ? AND seq = ?',
|
|
619
628
|
[status, topicName, seq]
|
|
620
629
|
);
|
|
621
|
-
}
|
|
622
|
-
self._logger('SQLiteStorage', 'updMessageStatus error:', err);
|
|
623
|
-
throw err;
|
|
624
|
-
}
|
|
630
|
+
}, 'updMessageStatus');
|
|
625
631
|
}
|
|
626
632
|
|
|
627
633
|
/**
|
|
@@ -637,7 +643,7 @@ export default class SQLiteStorage {
|
|
|
637
643
|
return Promise.resolve();
|
|
638
644
|
}
|
|
639
645
|
|
|
640
|
-
|
|
646
|
+
return self._withRecovery(async () => {
|
|
641
647
|
if (!from && !to) {
|
|
642
648
|
// Delete all messages for topic
|
|
643
649
|
await self._db.runAsync(
|
|
@@ -657,10 +663,7 @@ export default class SQLiteStorage {
|
|
|
657
663
|
[topicName, from]
|
|
658
664
|
);
|
|
659
665
|
}
|
|
660
|
-
}
|
|
661
|
-
self._logger('SQLiteStorage', 'remMessages error:', err);
|
|
662
|
-
throw err;
|
|
663
|
-
}
|
|
666
|
+
}, 'remMessages');
|
|
664
667
|
}
|
|
665
668
|
|
|
666
669
|
/**
|
|
@@ -675,40 +678,29 @@ export default class SQLiteStorage {
|
|
|
675
678
|
const self = this;
|
|
676
679
|
query = query || {};
|
|
677
680
|
|
|
678
|
-
console.log('[SQLiteStorage] readMessages CALLED:', {
|
|
679
|
-
topicName,
|
|
680
|
-
query: JSON.stringify(query),
|
|
681
|
-
hasCallback: !!callback
|
|
682
|
-
});
|
|
683
|
-
|
|
684
681
|
if (!self.isReady()) {
|
|
685
|
-
console.log('[SQLiteStorage] readMessages: DB NOT READY');
|
|
686
682
|
return [];
|
|
687
683
|
}
|
|
688
684
|
|
|
689
|
-
|
|
685
|
+
return self._withRecovery(async () => {
|
|
690
686
|
var result = [];
|
|
691
687
|
|
|
692
688
|
// Handle individual message ranges
|
|
693
689
|
if (Array.isArray(query.ranges)) {
|
|
694
|
-
console.log('[SQLiteStorage] readMessages: Using RANGES query, ranges:', query.ranges.length);
|
|
695
690
|
for (var i = 0; i < query.ranges.length; i++) {
|
|
696
691
|
var range = query.ranges[i];
|
|
697
692
|
var msgs;
|
|
698
693
|
if (range.hi) {
|
|
699
|
-
console.log('[SQLiteStorage] readMessages: Range', i, '- low:', range.low, 'hi:', range.hi);
|
|
700
694
|
msgs = await self._db.getAllAsync(
|
|
701
695
|
'SELECT * FROM messages WHERE topic = ? AND seq >= ? AND seq < ? ORDER BY seq DESC',
|
|
702
696
|
[topicName, range.low, range.hi]
|
|
703
697
|
);
|
|
704
698
|
} else {
|
|
705
|
-
console.log('[SQLiteStorage] readMessages: Range', i, '- single seq:', range.low);
|
|
706
699
|
msgs = await self._db.getAllAsync(
|
|
707
700
|
'SELECT * FROM messages WHERE topic = ? AND seq = ?',
|
|
708
701
|
[topicName, range.low]
|
|
709
702
|
);
|
|
710
703
|
}
|
|
711
|
-
console.log('[SQLiteStorage] readMessages: Range', i, 'returned', msgs.length, 'rows');
|
|
712
704
|
|
|
713
705
|
var deserialized = msgs.map(function(row) {
|
|
714
706
|
return self._deserializeMessageRow(row);
|
|
@@ -720,7 +712,6 @@ export default class SQLiteStorage {
|
|
|
720
712
|
|
|
721
713
|
result = result.concat(deserialized);
|
|
722
714
|
}
|
|
723
|
-
console.log('[SQLiteStorage] readMessages: RANGES query total result:', result.length);
|
|
724
715
|
return result;
|
|
725
716
|
}
|
|
726
717
|
|
|
@@ -737,32 +728,12 @@ export default class SQLiteStorage {
|
|
|
737
728
|
params.push(limit);
|
|
738
729
|
}
|
|
739
730
|
|
|
740
|
-
console.log('[SQLiteStorage] readMessages: SQL:', sql);
|
|
741
|
-
console.log('[SQLiteStorage] readMessages: params:', JSON.stringify(params));
|
|
742
|
-
|
|
743
|
-
// DEBUG: First check what's actually in the database for this topic
|
|
744
|
-
var allMsgsForTopic = await self._db.getAllAsync(
|
|
745
|
-
'SELECT topic, seq, ts FROM messages WHERE topic = ? ORDER BY seq DESC',
|
|
746
|
-
[topicName]
|
|
747
|
-
);
|
|
748
|
-
console.log('[SQLiteStorage] readMessages: DEBUG - ALL messages in DB for topic:', allMsgsForTopic.length,
|
|
749
|
-
allMsgsForTopic.map(m => m.seq).join(','));
|
|
750
|
-
|
|
751
731
|
var rows = await self._db.getAllAsync(sql, params);
|
|
752
|
-
console.log('[SQLiteStorage] readMessages: Raw rows returned:', rows.length);
|
|
753
|
-
if (rows.length > 0) {
|
|
754
|
-
console.log('[SQLiteStorage] readMessages: First row seq:', rows[0].seq, 'topic:', rows[0].topic);
|
|
755
|
-
if (rows.length > 1) {
|
|
756
|
-
console.log('[SQLiteStorage] readMessages: Last row seq:', rows[rows.length - 1].seq);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
732
|
|
|
760
733
|
result = rows.map(function(row) {
|
|
761
734
|
return self._deserializeMessageRow(row);
|
|
762
735
|
});
|
|
763
736
|
|
|
764
|
-
console.log('[SQLiteStorage] readMessages: Returning', result.length, 'messages');
|
|
765
|
-
|
|
766
737
|
if (callback) {
|
|
767
738
|
result.forEach(function(msg) {
|
|
768
739
|
callback.call(context, msg);
|
|
@@ -770,11 +741,7 @@ export default class SQLiteStorage {
|
|
|
770
741
|
}
|
|
771
742
|
|
|
772
743
|
return result;
|
|
773
|
-
}
|
|
774
|
-
console.error('[SQLiteStorage] readMessages ERROR:', err);
|
|
775
|
-
self._logger('SQLiteStorage', 'readMessages error:', err);
|
|
776
|
-
throw err;
|
|
777
|
-
}
|
|
744
|
+
}, 'readMessages');
|
|
778
745
|
}
|
|
779
746
|
|
|
780
747
|
// ==================== Delete Log ====================
|
|
@@ -792,7 +759,7 @@ export default class SQLiteStorage {
|
|
|
792
759
|
return Promise.resolve();
|
|
793
760
|
}
|
|
794
761
|
|
|
795
|
-
|
|
762
|
+
return self._withRecovery(async () => {
|
|
796
763
|
// Use withTransactionAsync for proper transaction handling
|
|
797
764
|
await self._db.withTransactionAsync(async function() {
|
|
798
765
|
for (var i = 0; i < ranges.length; i++) {
|
|
@@ -803,10 +770,7 @@ export default class SQLiteStorage {
|
|
|
803
770
|
);
|
|
804
771
|
}
|
|
805
772
|
});
|
|
806
|
-
}
|
|
807
|
-
self._logger('SQLiteStorage', 'addDelLog error:', err);
|
|
808
|
-
throw err;
|
|
809
|
-
}
|
|
773
|
+
}, 'addDelLog');
|
|
810
774
|
}
|
|
811
775
|
|
|
812
776
|
/**
|
|
@@ -823,7 +787,7 @@ export default class SQLiteStorage {
|
|
|
823
787
|
return [];
|
|
824
788
|
}
|
|
825
789
|
|
|
826
|
-
|
|
790
|
+
return self._withRecovery(async () => {
|
|
827
791
|
var result = [];
|
|
828
792
|
|
|
829
793
|
// Handle individual message ranges
|
|
@@ -868,10 +832,7 @@ export default class SQLiteStorage {
|
|
|
868
832
|
}
|
|
869
833
|
|
|
870
834
|
return result;
|
|
871
|
-
}
|
|
872
|
-
self._logger('SQLiteStorage', 'readDelLog error:', err);
|
|
873
|
-
throw err;
|
|
874
|
-
}
|
|
835
|
+
}, 'readDelLog');
|
|
875
836
|
}
|
|
876
837
|
|
|
877
838
|
/**
|
|
@@ -885,7 +846,7 @@ export default class SQLiteStorage {
|
|
|
885
846
|
return undefined;
|
|
886
847
|
}
|
|
887
848
|
|
|
888
|
-
|
|
849
|
+
return self._withRecovery(async () => {
|
|
889
850
|
const row = await self._db.getFirstAsync(
|
|
890
851
|
'SELECT * FROM dellog WHERE topic = ? ORDER BY clear DESC LIMIT 1',
|
|
891
852
|
[topicName]
|
|
@@ -901,10 +862,7 @@ export default class SQLiteStorage {
|
|
|
901
862
|
low: row.low,
|
|
902
863
|
hi: row.hi
|
|
903
864
|
};
|
|
904
|
-
}
|
|
905
|
-
self._logger('SQLiteStorage', 'maxDelId error:', err);
|
|
906
|
-
throw err;
|
|
907
|
-
}
|
|
865
|
+
}, 'maxDelId');
|
|
908
866
|
}
|
|
909
867
|
|
|
910
868
|
// ==================== Private Helper Methods ====================
|
package/types/index.d.ts
CHANGED
|
@@ -206,7 +206,8 @@ declare module '@roeehrl/tinode-sdk' {
|
|
|
206
206
|
|
|
207
207
|
export interface SetDesc {
|
|
208
208
|
defacs?: DefAcs;
|
|
209
|
-
|
|
209
|
+
/** Public data. For fnd topic, can be a search query string. */
|
|
210
|
+
public?: Record<string, unknown> | string;
|
|
210
211
|
trusted?: Record<string, unknown>;
|
|
211
212
|
private?: Record<string, unknown>;
|
|
212
213
|
}
|