@roeehrl/tinode-sdk 0.25.1-sqlite.1 → 0.25.1-sqlite.11

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.
@@ -198,6 +198,69 @@ 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
+ // Close existing connection if any
211
+ if (self._db) {
212
+ try {
213
+ await self._db.closeAsync();
214
+ } catch (closeErr) {
215
+ // Ignore close errors - the handle may already be invalid
216
+ }
217
+ self._db = null;
218
+ }
219
+
220
+ // Re-open the database
221
+ self._ready = false;
222
+ await self.initDatabase();
223
+
224
+ return true;
225
+ } catch (err) {
226
+ console.error('[SQLiteStorage] Database recovery failed:', err);
227
+ return false;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Wrapper to execute database operations with automatic recovery.
233
+ * If an operation fails with NullPointerException, attempts recovery and retry.
234
+ * @param {function} operation - Async function to execute
235
+ * @param {string} operationName - Name for logging
236
+ * @returns {Promise<any>} Result of the operation
237
+ */
238
+ async _withRecovery(operation, operationName) {
239
+ const self = this;
240
+ try {
241
+ return await operation();
242
+ } catch (err) {
243
+ // Check if this is a stale database handle error
244
+ const isStaleHandle = err.message && (
245
+ err.message.includes('NullPointerException') ||
246
+ err.message.includes('prepareAsync') ||
247
+ err.message.includes('database is not open') ||
248
+ err.message.includes('SQLiteDatabase')
249
+ );
250
+
251
+ if (isStaleHandle) {
252
+ const recovered = await self._recoverDatabase();
253
+ if (recovered) {
254
+ // Retry the operation once
255
+ return await operation();
256
+ }
257
+ }
258
+
259
+ // Re-throw if not recoverable
260
+ throw err;
261
+ }
262
+ }
263
+
201
264
  // ==================== Topics ====================
202
265
 
203
266
  /**
@@ -207,11 +270,19 @@ export default class SQLiteStorage {
207
270
  */
208
271
  async updTopic(topic) {
209
272
  const self = this;
273
+
274
+ // Skip topics that haven't been confirmed by the server yet.
275
+ // The _new flag is true for topics created locally but not yet subscribed.
276
+ // Only persist after subscribe succeeds and server assigns the real topic name.
277
+ if (topic?._new) {
278
+ return Promise.resolve();
279
+ }
280
+
210
281
  if (!self.isReady()) {
211
282
  return Promise.resolve();
212
283
  }
213
284
 
214
- try {
285
+ return self._withRecovery(async () => {
215
286
  // Get existing topic to merge data
216
287
  const existing = await self._db.getFirstAsync(
217
288
  'SELECT * FROM topics WHERE name = ?',
@@ -220,8 +291,6 @@ export default class SQLiteStorage {
220
291
 
221
292
  const data = self._serializeTopic(existing, topic);
222
293
 
223
- console.log('[SQLiteStorage] updTopic:', data.name, 'seq:', data.seq);
224
-
225
294
  // Use INSERT OR REPLACE for atomic upsert
226
295
  await self._db.runAsync(`
227
296
  INSERT OR REPLACE INTO topics (
@@ -236,11 +305,7 @@ export default class SQLiteStorage {
236
305
  data.defacs, data.creds, data.public, data.trusted, data.private,
237
306
  data._aux, data._deleted, data.tags, data.acs
238
307
  ]);
239
- console.log('[SQLiteStorage] updTopic SUCCESS:', data.name);
240
- } catch (err) {
241
- console.error('[SQLiteStorage] updTopic FAILED:', err.message, 'topic:', topic.name);
242
- throw err;
243
- }
308
+ }, 'updTopic');
244
309
  }
245
310
 
246
311
  /**
@@ -255,15 +320,12 @@ export default class SQLiteStorage {
255
320
  return Promise.resolve();
256
321
  }
257
322
 
258
- try {
323
+ return self._withRecovery(async () => {
259
324
  await self._db.runAsync(
260
325
  'UPDATE topics SET _deleted = ? WHERE name = ?',
261
326
  [deleted ? 1 : 0, name]
262
327
  );
263
- } catch (err) {
264
- self._logger('SQLiteStorage', 'markTopicAsDeleted error:', err);
265
- throw err;
266
- }
328
+ }, 'markTopicAsDeleted');
267
329
  }
268
330
 
269
331
  /**
@@ -277,7 +339,7 @@ export default class SQLiteStorage {
277
339
  return Promise.resolve();
278
340
  }
279
341
 
280
- try {
342
+ return self._withRecovery(async () => {
281
343
  // Delete topic, subscriptions, and messages in a transaction
282
344
  await self._db.withTransactionAsync(async function() {
283
345
  await self._db.runAsync('DELETE FROM topics WHERE name = ?', [name]);
@@ -285,10 +347,7 @@ export default class SQLiteStorage {
285
347
  await self._db.runAsync('DELETE FROM messages WHERE topic = ?', [name]);
286
348
  await self._db.runAsync('DELETE FROM dellog WHERE topic = ?', [name]);
287
349
  });
288
- } catch (err) {
289
- self._logger('SQLiteStorage', 'remTopic error:', err);
290
- throw err;
291
- }
350
+ }, 'remTopic');
292
351
  }
293
352
 
294
353
  /**
@@ -303,7 +362,7 @@ export default class SQLiteStorage {
303
362
  return [];
304
363
  }
305
364
 
306
- try {
365
+ return self._withRecovery(async () => {
307
366
  const rows = await self._db.getAllAsync('SELECT * FROM topics');
308
367
  const topics = rows.map(function(row) {
309
368
  return self._deserializeTopicRow(row);
@@ -316,10 +375,7 @@ export default class SQLiteStorage {
316
375
  }
317
376
 
318
377
  return topics;
319
- } catch (err) {
320
- self._logger('SQLiteStorage', 'mapTopics error:', err);
321
- throw err;
322
- }
378
+ }, 'mapTopics');
323
379
  }
324
380
 
325
381
  /**
@@ -361,15 +417,12 @@ export default class SQLiteStorage {
361
417
  return Promise.resolve();
362
418
  }
363
419
 
364
- try {
420
+ return self._withRecovery(async () => {
365
421
  await self._db.runAsync(
366
422
  'INSERT OR REPLACE INTO users (uid, public) VALUES (?, ?)',
367
423
  [uid, JSON.stringify(pub)]
368
424
  );
369
- } catch (err) {
370
- self._logger('SQLiteStorage', 'updUser error:', err);
371
- throw err;
372
- }
425
+ }, 'updUser');
373
426
  }
374
427
 
375
428
  /**
@@ -383,12 +436,9 @@ export default class SQLiteStorage {
383
436
  return Promise.resolve();
384
437
  }
385
438
 
386
- try {
439
+ return self._withRecovery(async () => {
387
440
  await self._db.runAsync('DELETE FROM users WHERE uid = ?', [uid]);
388
- } catch (err) {
389
- self._logger('SQLiteStorage', 'remUser error:', err);
390
- throw err;
391
- }
441
+ }, 'remUser');
392
442
  }
393
443
 
394
444
  /**
@@ -403,7 +453,7 @@ export default class SQLiteStorage {
403
453
  return [];
404
454
  }
405
455
 
406
- try {
456
+ return self._withRecovery(async () => {
407
457
  const rows = await self._db.getAllAsync('SELECT * FROM users');
408
458
  const users = rows.map(function(row) {
409
459
  return {
@@ -419,10 +469,7 @@ export default class SQLiteStorage {
419
469
  }
420
470
 
421
471
  return users;
422
- } catch (err) {
423
- self._logger('SQLiteStorage', 'mapUsers error:', err);
424
- throw err;
425
- }
472
+ }, 'mapUsers');
426
473
  }
427
474
 
428
475
  /**
@@ -436,7 +483,7 @@ export default class SQLiteStorage {
436
483
  return undefined;
437
484
  }
438
485
 
439
- try {
486
+ return self._withRecovery(async () => {
440
487
  const row = await self._db.getFirstAsync(
441
488
  'SELECT * FROM users WHERE uid = ?',
442
489
  [uid]
@@ -450,10 +497,7 @@ export default class SQLiteStorage {
450
497
  uid: row.uid,
451
498
  public: self._parseJSON(row.public)
452
499
  };
453
- } catch (err) {
454
- self._logger('SQLiteStorage', 'getUser error:', err);
455
- throw err;
456
- }
500
+ }, 'getUser');
457
501
  }
458
502
 
459
503
  // ==================== Subscriptions ====================
@@ -471,7 +515,7 @@ export default class SQLiteStorage {
471
515
  return Promise.resolve();
472
516
  }
473
517
 
474
- try {
518
+ return self._withRecovery(async () => {
475
519
  // Get existing subscription
476
520
  const existing = await self._db.getFirstAsync(
477
521
  'SELECT * FROM subscriptions WHERE topic = ? AND uid = ?',
@@ -484,10 +528,7 @@ export default class SQLiteStorage {
484
528
  'INSERT OR REPLACE INTO subscriptions (topic, uid, updated, mode, read, recv, clear, lastSeen, userAgent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
485
529
  [data.topic, data.uid, data.updated, data.mode, data.read, data.recv, data.clear, data.lastSeen, data.userAgent]
486
530
  );
487
- } catch (err) {
488
- self._logger('SQLiteStorage', 'updSubscription error:', err);
489
- throw err;
490
- }
531
+ }, 'updSubscription');
491
532
  }
492
533
 
493
534
  /**
@@ -503,7 +544,7 @@ export default class SQLiteStorage {
503
544
  return [];
504
545
  }
505
546
 
506
- try {
547
+ return self._withRecovery(async () => {
507
548
  const rows = await self._db.getAllAsync(
508
549
  'SELECT * FROM subscriptions WHERE topic = ?',
509
550
  [topicName]
@@ -520,10 +561,7 @@ export default class SQLiteStorage {
520
561
  }
521
562
 
522
563
  return subs;
523
- } catch (err) {
524
- self._logger('SQLiteStorage', 'mapSubscriptions error:', err);
525
- throw err;
526
- }
564
+ }, 'mapSubscriptions');
527
565
  }
528
566
 
529
567
  // ==================== Messages ====================
@@ -539,41 +577,9 @@ export default class SQLiteStorage {
539
577
  return Promise.resolve();
540
578
  }
541
579
 
542
- try {
580
+ return self._withRecovery(async () => {
543
581
  const data = self._serializeMessage(null, msg);
544
582
 
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
583
  // Build params array explicitly, converting undefined to null for SQLite
578
584
  const params = [
579
585
  data.topic,
@@ -585,19 +591,11 @@ export default class SQLiteStorage {
585
591
  data.content !== undefined ? data.content : null
586
592
  ];
587
593
 
588
- console.log('[SQLiteStorage] addMessage params array:', params.map((p, i) => `[${i}]=${typeof p}:${p === null ? 'null' : p === undefined ? 'undefined' : 'value'}`).join(', '));
589
-
590
594
  await self._db.runAsync(
591
595
  `INSERT OR REPLACE INTO messages (topic, seq, ts, _status, from_uid, head, content) VALUES (?, ?, ?, ?, ?, ?, ?)`,
592
596
  params
593
597
  );
594
- console.log('[SQLiteStorage] addMessage SUCCESS:', data.topic, data.seq);
595
- } catch (err) {
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
- }
598
+ }, 'addMessage');
601
599
  }
602
600
 
603
601
  /**
@@ -613,15 +611,12 @@ export default class SQLiteStorage {
613
611
  return Promise.resolve();
614
612
  }
615
613
 
616
- try {
614
+ return self._withRecovery(async () => {
617
615
  await self._db.runAsync(
618
616
  'UPDATE messages SET _status = ? WHERE topic = ? AND seq = ?',
619
617
  [status, topicName, seq]
620
618
  );
621
- } catch (err) {
622
- self._logger('SQLiteStorage', 'updMessageStatus error:', err);
623
- throw err;
624
- }
619
+ }, 'updMessageStatus');
625
620
  }
626
621
 
627
622
  /**
@@ -637,7 +632,7 @@ export default class SQLiteStorage {
637
632
  return Promise.resolve();
638
633
  }
639
634
 
640
- try {
635
+ return self._withRecovery(async () => {
641
636
  if (!from && !to) {
642
637
  // Delete all messages for topic
643
638
  await self._db.runAsync(
@@ -657,10 +652,7 @@ export default class SQLiteStorage {
657
652
  [topicName, from]
658
653
  );
659
654
  }
660
- } catch (err) {
661
- self._logger('SQLiteStorage', 'remMessages error:', err);
662
- throw err;
663
- }
655
+ }, 'remMessages');
664
656
  }
665
657
 
666
658
  /**
@@ -675,40 +667,29 @@ export default class SQLiteStorage {
675
667
  const self = this;
676
668
  query = query || {};
677
669
 
678
- console.log('[SQLiteStorage] readMessages CALLED:', {
679
- topicName,
680
- query: JSON.stringify(query),
681
- hasCallback: !!callback
682
- });
683
-
684
670
  if (!self.isReady()) {
685
- console.log('[SQLiteStorage] readMessages: DB NOT READY');
686
671
  return [];
687
672
  }
688
673
 
689
- try {
674
+ return self._withRecovery(async () => {
690
675
  var result = [];
691
676
 
692
677
  // Handle individual message ranges
693
678
  if (Array.isArray(query.ranges)) {
694
- console.log('[SQLiteStorage] readMessages: Using RANGES query, ranges:', query.ranges.length);
695
679
  for (var i = 0; i < query.ranges.length; i++) {
696
680
  var range = query.ranges[i];
697
681
  var msgs;
698
682
  if (range.hi) {
699
- console.log('[SQLiteStorage] readMessages: Range', i, '- low:', range.low, 'hi:', range.hi);
700
683
  msgs = await self._db.getAllAsync(
701
684
  'SELECT * FROM messages WHERE topic = ? AND seq >= ? AND seq < ? ORDER BY seq DESC',
702
685
  [topicName, range.low, range.hi]
703
686
  );
704
687
  } else {
705
- console.log('[SQLiteStorage] readMessages: Range', i, '- single seq:', range.low);
706
688
  msgs = await self._db.getAllAsync(
707
689
  'SELECT * FROM messages WHERE topic = ? AND seq = ?',
708
690
  [topicName, range.low]
709
691
  );
710
692
  }
711
- console.log('[SQLiteStorage] readMessages: Range', i, 'returned', msgs.length, 'rows');
712
693
 
713
694
  var deserialized = msgs.map(function(row) {
714
695
  return self._deserializeMessageRow(row);
@@ -720,7 +701,6 @@ export default class SQLiteStorage {
720
701
 
721
702
  result = result.concat(deserialized);
722
703
  }
723
- console.log('[SQLiteStorage] readMessages: RANGES query total result:', result.length);
724
704
  return result;
725
705
  }
726
706
 
@@ -737,32 +717,12 @@ export default class SQLiteStorage {
737
717
  params.push(limit);
738
718
  }
739
719
 
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
720
  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
721
 
760
722
  result = rows.map(function(row) {
761
723
  return self._deserializeMessageRow(row);
762
724
  });
763
725
 
764
- console.log('[SQLiteStorage] readMessages: Returning', result.length, 'messages');
765
-
766
726
  if (callback) {
767
727
  result.forEach(function(msg) {
768
728
  callback.call(context, msg);
@@ -770,11 +730,7 @@ export default class SQLiteStorage {
770
730
  }
771
731
 
772
732
  return result;
773
- } catch (err) {
774
- console.error('[SQLiteStorage] readMessages ERROR:', err);
775
- self._logger('SQLiteStorage', 'readMessages error:', err);
776
- throw err;
777
- }
733
+ }, 'readMessages');
778
734
  }
779
735
 
780
736
  // ==================== Delete Log ====================
@@ -792,7 +748,7 @@ export default class SQLiteStorage {
792
748
  return Promise.resolve();
793
749
  }
794
750
 
795
- try {
751
+ return self._withRecovery(async () => {
796
752
  // Use withTransactionAsync for proper transaction handling
797
753
  await self._db.withTransactionAsync(async function() {
798
754
  for (var i = 0; i < ranges.length; i++) {
@@ -803,10 +759,7 @@ export default class SQLiteStorage {
803
759
  );
804
760
  }
805
761
  });
806
- } catch (err) {
807
- self._logger('SQLiteStorage', 'addDelLog error:', err);
808
- throw err;
809
- }
762
+ }, 'addDelLog');
810
763
  }
811
764
 
812
765
  /**
@@ -823,7 +776,7 @@ export default class SQLiteStorage {
823
776
  return [];
824
777
  }
825
778
 
826
- try {
779
+ return self._withRecovery(async () => {
827
780
  var result = [];
828
781
 
829
782
  // Handle individual message ranges
@@ -868,10 +821,7 @@ export default class SQLiteStorage {
868
821
  }
869
822
 
870
823
  return result;
871
- } catch (err) {
872
- self._logger('SQLiteStorage', 'readDelLog error:', err);
873
- throw err;
874
- }
824
+ }, 'readDelLog');
875
825
  }
876
826
 
877
827
  /**
@@ -885,7 +835,7 @@ export default class SQLiteStorage {
885
835
  return undefined;
886
836
  }
887
837
 
888
- try {
838
+ return self._withRecovery(async () => {
889
839
  const row = await self._db.getFirstAsync(
890
840
  'SELECT * FROM dellog WHERE topic = ? ORDER BY clear DESC LIMIT 1',
891
841
  [topicName]
@@ -901,10 +851,7 @@ export default class SQLiteStorage {
901
851
  low: row.low,
902
852
  hi: row.hi
903
853
  };
904
- } catch (err) {
905
- self._logger('SQLiteStorage', 'maxDelId error:', err);
906
- throw err;
907
- }
854
+ }, 'maxDelId');
908
855
  }
909
856
 
910
857
  // ==================== Private Helper Methods ====================
package/src/topic.js CHANGED
@@ -2105,16 +2105,13 @@ export default class Topic {
2105
2105
 
2106
2106
  // Load most recent messages from persistent cache.
2107
2107
  _loadMessages(db, query) {
2108
- console.log('[Topic] _loadMessages CALLED:', this.name, 'query:', JSON.stringify(query), 'db:', !!db);
2109
2108
  query = query || {};
2110
2109
  query.limit = query.limit || Const.DEFAULT_MESSAGES_PAGE;
2111
2110
 
2112
2111
  // Count of message loaded from DB.
2113
2112
  let count = 0;
2114
- console.log('[Topic] _loadMessages: calling db.readMessages');
2115
2113
  return db.readMessages(this.name, query)
2116
2114
  .then(msgs => {
2117
- console.log('[Topic] _loadMessages: db.readMessages returned', msgs ? msgs.length : 0, 'messages');
2118
2115
  msgs.forEach(data => {
2119
2116
  if (data.seq > this._maxSeq) {
2120
2117
  this._maxSeq = data.seq;
@@ -2126,7 +2123,6 @@ export default class Topic {
2126
2123
  this._maybeUpdateMessageVersionsCache(data);
2127
2124
  });
2128
2125
  count = msgs.length;
2129
- console.log('[Topic] _loadMessages: processed', count, 'messages, _maxSeq:', this._maxSeq, '_minSeq:', this._minSeq);
2130
2126
  })
2131
2127
  .then(_ => db.readDelLog(this.name, query))
2132
2128
  .then(dellog => {
package/types/index.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * @see https://github.com/tinode/tinode-js
12
12
  */
13
13
 
14
- declare module 'tinode-sdk' {
14
+ declare module '@roeehrl/tinode-sdk' {
15
15
  // ==========================================================================
16
16
  // Configuration Types
17
17
  // ==========================================================================
@@ -206,7 +206,8 @@ declare module 'tinode-sdk' {
206
206
 
207
207
  export interface SetDesc {
208
208
  defacs?: DefAcs;
209
- public?: Record<string, unknown>;
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
  }
@@ -264,6 +265,8 @@ declare module 'tinode-sdk' {
264
265
  duration: number;
265
266
  filename?: string;
266
267
  size?: number;
268
+ /** Promise that resolves to the upload URL (for out-of-band uploads) */
269
+ urlPromise?: Promise<string>;
267
270
  }
268
271
 
269
272
  export interface AttachmentDesc {
@@ -659,6 +662,91 @@ declare module 'tinode-sdk' {
659
662
  cancel(): void;
660
663
  }
661
664
 
665
+ // ==========================================================================
666
+ // LargeFileHelperNative (React Native)
667
+ // ==========================================================================
668
+
669
+ /**
670
+ * React Native file upload helper.
671
+ * Uses file URIs instead of Blob objects for compatibility.
672
+ */
673
+ export class LargeFileHelperNative {
674
+ constructor(tinode: Tinode, version?: string);
675
+
676
+ /**
677
+ * Upload a file from a URI (React Native specific).
678
+ * @param uri File URI (e.g., 'file:///path/to/audio.m4a')
679
+ * @param filename Filename for the upload
680
+ * @param mimetype MIME type of the file
681
+ * @param size File size in bytes (optional, for progress calculation)
682
+ * @param avatarFor Topic name if the upload represents an avatar
683
+ * @param onProgress Progress callback (0..1)
684
+ * @param onSuccess Success callback
685
+ * @param onFailure Failure callback
686
+ * @returns Promise resolved with the upload URL
687
+ */
688
+ uploadUri(
689
+ uri: string,
690
+ filename: string,
691
+ mimetype: string,
692
+ size?: number,
693
+ avatarFor?: string,
694
+ onProgress?: (progress: number) => void,
695
+ onSuccess?: (ctrl: ControlMessage) => void,
696
+ onFailure?: (ctrl: ControlMessage | null) => void,
697
+ ): Promise<string>;
698
+
699
+ /**
700
+ * Upload a file from a URI to a specific base URL.
701
+ */
702
+ uploadUriWithBaseUrl(
703
+ baseUrl: string,
704
+ uri: string,
705
+ filename: string,
706
+ mimetype: string,
707
+ size?: number,
708
+ avatarFor?: string,
709
+ onProgress?: (progress: number) => void,
710
+ onSuccess?: (ctrl: ControlMessage) => void,
711
+ onFailure?: (ctrl: ControlMessage | null) => void,
712
+ ): Promise<string>;
713
+
714
+ /**
715
+ * Upload (compatibility method - prefer uploadUri in React Native).
716
+ * Accepts objects with uri property for RN file objects.
717
+ */
718
+ upload(
719
+ data: { uri: string; name?: string; type?: string; size?: number } | File | Blob,
720
+ avatarFor?: string,
721
+ onProgress?: (progress: number) => void,
722
+ onSuccess?: (ctrl: ControlMessage) => void,
723
+ onFailure?: (ctrl: ControlMessage | null) => void,
724
+ ): Promise<string>;
725
+
726
+ /**
727
+ * Get download configuration for use with expo-file-system.
728
+ * @param relativeUrl Relative URL to the file
729
+ * @returns Object with url and headers for download
730
+ */
731
+ getDownloadConfig(relativeUrl: string): {
732
+ url: string;
733
+ headers: Record<string, string>;
734
+ };
735
+
736
+ /**
737
+ * Download (placeholder - use expo-file-system instead).
738
+ */
739
+ download(
740
+ relativeUrl: string,
741
+ filename?: string,
742
+ mimetype?: string,
743
+ onProgress?: (loaded: number) => void,
744
+ onError?: (error: Error | string) => void,
745
+ ): Promise<string>;
746
+
747
+ cancel(): void;
748
+ }
749
+
662
750
  // ==========================================================================
663
751
  // Main Tinode Class
664
752
  // ==========================================================================