@roeehrl/tinode-sdk 0.25.1-sqlite.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/LICENSE +201 -0
- package/README.md +47 -0
- package/package.json +76 -0
- package/src/access-mode.js +567 -0
- package/src/cbuffer.js +244 -0
- package/src/cbuffer.test.js +107 -0
- package/src/comm-error.js +14 -0
- package/src/config.js +71 -0
- package/src/connection.js +537 -0
- package/src/db.js +1021 -0
- package/src/drafty.js +2758 -0
- package/src/drafty.test.js +1600 -0
- package/src/fnd-topic.js +123 -0
- package/src/index.js +29 -0
- package/src/index.native.js +35 -0
- package/src/large-file.js +325 -0
- package/src/me-topic.js +480 -0
- package/src/meta-builder.js +283 -0
- package/src/storage-sqlite.js +1081 -0
- package/src/tinode.js +2382 -0
- package/src/topic.js +2160 -0
- package/src/utils.js +309 -0
- package/src/utils.test.js +456 -0
- package/types/index.d.ts +1227 -0
- package/umd/tinode.dev.js +6856 -0
- package/umd/tinode.dev.js.map +1 -0
- package/umd/tinode.prod.js +2 -0
- package/umd/tinode.prod.js.map +1 -0
package/src/cbuffer.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file In-memory sorted cache of objects.
|
|
3
|
+
*
|
|
4
|
+
* @copyright 2015-2025 Tinode LLC.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* In-memory sorted cache of objects.
|
|
10
|
+
*
|
|
11
|
+
* @class CBuffer
|
|
12
|
+
* @memberof Tinode
|
|
13
|
+
* @protected
|
|
14
|
+
*
|
|
15
|
+
* @param {function} compare custom comparator of objects. Takes two parameters <code>a</code> and <code>b</code>;
|
|
16
|
+
* returns <code>-1</code> if <code>a < b</code>, <code>0</code> if <code>a == b</code>, <code>1</code> otherwise.
|
|
17
|
+
* @param {boolean} unique enforce element uniqueness: when <code>true</code> replace existing element with a new
|
|
18
|
+
* one on conflict; when <code>false</code> keep both elements.
|
|
19
|
+
*/
|
|
20
|
+
export default class CBuffer {
|
|
21
|
+
#comparator = undefined;
|
|
22
|
+
#unique = false;
|
|
23
|
+
buffer = [];
|
|
24
|
+
|
|
25
|
+
constructor(compare_, unique_) {
|
|
26
|
+
this.#comparator = compare_ || ((a, b) => {
|
|
27
|
+
return a === b ? 0 : a < b ? -1 : 1;
|
|
28
|
+
});
|
|
29
|
+
this.#unique = unique_;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#findNearest(elem, arr, exact) {
|
|
33
|
+
let start = 0;
|
|
34
|
+
let end = arr.length - 1;
|
|
35
|
+
let pivot = 0;
|
|
36
|
+
let diff = 0;
|
|
37
|
+
let found = false;
|
|
38
|
+
|
|
39
|
+
while (start <= end) {
|
|
40
|
+
pivot = (start + end) / 2 | 0;
|
|
41
|
+
diff = this.#comparator(arr[pivot], elem);
|
|
42
|
+
if (diff < 0) {
|
|
43
|
+
start = pivot + 1;
|
|
44
|
+
} else if (diff > 0) {
|
|
45
|
+
end = pivot - 1;
|
|
46
|
+
} else {
|
|
47
|
+
found = true;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (found) {
|
|
52
|
+
return {
|
|
53
|
+
idx: pivot,
|
|
54
|
+
exact: true
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (exact) {
|
|
58
|
+
return {
|
|
59
|
+
idx: -1
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Not exact - insertion point
|
|
63
|
+
return {
|
|
64
|
+
idx: diff < 0 ? pivot + 1 : pivot
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Insert element into a sorted array.
|
|
69
|
+
#insertSorted(elem, arr) {
|
|
70
|
+
const found = this.#findNearest(elem, arr, false);
|
|
71
|
+
const count = (found.exact && this.#unique) ? 1 : 0;
|
|
72
|
+
arr.splice(found.idx, count, elem);
|
|
73
|
+
return arr;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get an element at the given position.
|
|
78
|
+
* @memberof Tinode.CBuffer#
|
|
79
|
+
* @param {number} at - Position to fetch from.
|
|
80
|
+
* @returns {Object} Element at the given position or <code>undefined</code>.
|
|
81
|
+
*/
|
|
82
|
+
getAt(at) {
|
|
83
|
+
return this.buffer[at];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convenience method for getting the last element from the buffer.
|
|
88
|
+
* @memberof Tinode.CBuffer#
|
|
89
|
+
* @param {function} filter - optional filter to apply to elements. If filter is provided, the search
|
|
90
|
+
* for the last element starts from the end of the buffer and goes backwards until the filter returns true.
|
|
91
|
+
* @returns {Object} The last element in the buffer or <code>undefined</code> if buffer is empty.
|
|
92
|
+
*/
|
|
93
|
+
getLast(filter) {
|
|
94
|
+
return filter ?
|
|
95
|
+
this.buffer.findLast(filter) :
|
|
96
|
+
this.buffer[this.buffer.length - 1];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Insert new element(s) to the buffer at the correct position according to the sort method.
|
|
101
|
+
* Variadic: takes one or more arguments. If an array is passed as a single argument, its
|
|
102
|
+
* elements are inserted individually.
|
|
103
|
+
* @memberof Tinode.CBuffer#
|
|
104
|
+
*
|
|
105
|
+
* @param {...Object|Array} - One or more objects to insert.
|
|
106
|
+
*/
|
|
107
|
+
put() {
|
|
108
|
+
let insert;
|
|
109
|
+
// inspect arguments: if array, insert its elements, if one or more non-array arguments, insert them one by one
|
|
110
|
+
if (arguments.length == 1 && Array.isArray(arguments[0])) {
|
|
111
|
+
insert = arguments[0];
|
|
112
|
+
} else {
|
|
113
|
+
insert = arguments;
|
|
114
|
+
}
|
|
115
|
+
for (let idx in insert) {
|
|
116
|
+
this.#insertSorted(insert[idx], this.buffer);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Remove element at the given position.
|
|
122
|
+
* @memberof Tinode.CBuffer#
|
|
123
|
+
* @param {number} at - Position to delete at.
|
|
124
|
+
* @returns {Object} Element at the given position or <code>undefined</code>.
|
|
125
|
+
*/
|
|
126
|
+
delAt(at) {
|
|
127
|
+
at |= 0;
|
|
128
|
+
let r = this.buffer.splice(at, 1);
|
|
129
|
+
if (r && r.length > 0) {
|
|
130
|
+
return r[0];
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Remove elements between two positions.
|
|
137
|
+
* @memberof Tinode.CBuffer#
|
|
138
|
+
* @param {number} since - Position to delete from (inclusive).
|
|
139
|
+
* @param {number} before - Position to delete to (exclusive).
|
|
140
|
+
*
|
|
141
|
+
* @returns {Array} array of removed elements (could be zero length).
|
|
142
|
+
*/
|
|
143
|
+
delRange(since, before) {
|
|
144
|
+
return this.buffer.splice(since, before - since);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Return the number of elements the buffer holds.
|
|
149
|
+
* @memberof Tinode.CBuffer#
|
|
150
|
+
* @return {number} Number of elements in the buffer.
|
|
151
|
+
*/
|
|
152
|
+
length() {
|
|
153
|
+
return this.buffer.length;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Reset the buffer discarding all elements
|
|
158
|
+
* @memberof Tinode.CBuffer#
|
|
159
|
+
*/
|
|
160
|
+
reset() {
|
|
161
|
+
this.buffer = [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Callback for iterating contents of buffer. See {@link Tinode.CBuffer#forEach}.
|
|
166
|
+
* @callback ForEachCallbackType
|
|
167
|
+
* @memberof Tinode.CBuffer#
|
|
168
|
+
* @param {Object} elem - Current element of the buffer.
|
|
169
|
+
* @param {Object} prev - Previous element of the buffer.
|
|
170
|
+
* @param {Object} next - Next element of the buffer.
|
|
171
|
+
* @param {number} index - Index of the current element.
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Apply given <code>callback</code> to all elements of the buffer.
|
|
176
|
+
* @memberof Tinode.CBuffer#
|
|
177
|
+
*
|
|
178
|
+
* @param {Tinode.ForEachCallbackType} callback - Function to call for each element.
|
|
179
|
+
* @param {number} startIdx - Optional index to start iterating from (inclusive), default: 0.
|
|
180
|
+
* @param {number} beforeIdx - Optional index to stop iterating before (exclusive), default: length of the buffer.
|
|
181
|
+
* @param {Object} context - calling context (i.e. value of <code>this</code> in callback)
|
|
182
|
+
*/
|
|
183
|
+
forEach(callback, startIdx, beforeIdx, context) {
|
|
184
|
+
startIdx = Math.max(0, startIdx | 0);
|
|
185
|
+
beforeIdx = Math.min(beforeIdx || this.buffer.length, this.buffer.length);
|
|
186
|
+
|
|
187
|
+
for (let i = startIdx; i < beforeIdx; i++) {
|
|
188
|
+
callback.call(context, this.buffer[i],
|
|
189
|
+
(i > startIdx ? this.buffer[i - 1] : undefined),
|
|
190
|
+
(i < beforeIdx - 1 ? this.buffer[i + 1] : undefined), i);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Find element in buffer using buffer's comparison function.
|
|
196
|
+
* @memberof Tinode.CBuffer#
|
|
197
|
+
*
|
|
198
|
+
* @param {Object} elem - element to find.
|
|
199
|
+
* @param {boolean=} nearest - when true and exact match is not found, return the nearest element (insertion point).
|
|
200
|
+
* @returns {number} index of the element in the buffer or -1.
|
|
201
|
+
*/
|
|
202
|
+
find(elem, nearest) {
|
|
203
|
+
const {
|
|
204
|
+
idx
|
|
205
|
+
} = this.#findNearest(elem, this.buffer, !nearest);
|
|
206
|
+
return idx;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Callback for filtering the buffer. See {@link Tinode.CBuffer#filter}.
|
|
211
|
+
* @callback FilterCallbackType
|
|
212
|
+
* @memberof Tinode.CBuffer#
|
|
213
|
+
* @param {Object} elem - Current element of the buffer.
|
|
214
|
+
* @param {number} index - Index of the current element.
|
|
215
|
+
* @returns {boolen} <code>true</code> to keep the element, <code>false</code> to remove.
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Remove all elements that do not pass the test implemented by the provided callback function.
|
|
220
|
+
* @memberof Tinode.CBuffer#
|
|
221
|
+
*
|
|
222
|
+
* @param {Tinode.FilterCallbackType} callback - Function to call for each element.
|
|
223
|
+
* @param {Object} context - calling context (i.e. value of <code>this</code> in the callback)
|
|
224
|
+
*/
|
|
225
|
+
filter(callback, context) {
|
|
226
|
+
let count = 0;
|
|
227
|
+
for (let i = 0; i < this.buffer.length; i++) {
|
|
228
|
+
if (callback.call(context, this.buffer[i], i)) {
|
|
229
|
+
this.buffer[count] = this.buffer[i];
|
|
230
|
+
count++;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.buffer.splice(count);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Check if buffer is empty.
|
|
239
|
+
* @returns {boolean} <code>true</code> if the buffer is empty, <code>false</code> otherwise.
|
|
240
|
+
*/
|
|
241
|
+
isEmpty() {
|
|
242
|
+
return this.buffer.length == 0;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import CBuffer from './cbuffer';
|
|
2
|
+
|
|
3
|
+
describe('CBuffer', () => {
|
|
4
|
+
let buffer;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
buffer = new CBuffer((a, b) => a - b, true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('should insert elements in sorted order', () => {
|
|
11
|
+
buffer.put(3, 1, 2);
|
|
12
|
+
expect(buffer.buffer).toEqual([1, 2, 3]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should insert array elements in sorted order', () => {
|
|
16
|
+
buffer.put([3, 1, 2]);
|
|
17
|
+
expect(buffer.buffer).toEqual([1, 2, 3]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should get element at given position', () => {
|
|
21
|
+
buffer.put(1, 2, 3);
|
|
22
|
+
expect(buffer.getAt(1)).toBe(2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('should get the last element', () => {
|
|
26
|
+
buffer.put(1, 2, 3);
|
|
27
|
+
expect(buffer.getLast()).toBe(3);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should get the last element with filter', () => {
|
|
31
|
+
buffer.put(1, 2, 3);
|
|
32
|
+
expect(buffer.getLast(x => x < 3)).toBe(2);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('should delete element at given position', () => {
|
|
36
|
+
buffer.put(1, 2, 3);
|
|
37
|
+
expect(buffer.delAt(1)).toBe(2);
|
|
38
|
+
expect(buffer.buffer).toEqual([1, 3]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('should delete elements in range', () => {
|
|
42
|
+
buffer.put(1, 2, 3, 4, 5);
|
|
43
|
+
expect(buffer.delRange(1, 4)).toEqual([2, 3, 4]);
|
|
44
|
+
expect(buffer.buffer).toEqual([1, 5]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should return the length of the buffer', () => {
|
|
48
|
+
buffer.put(1, 2, 3);
|
|
49
|
+
expect(buffer.length()).toBe(3);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('should reset the buffer', () => {
|
|
53
|
+
buffer.put(1, 2, 3);
|
|
54
|
+
buffer.reset();
|
|
55
|
+
expect(buffer.buffer).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should iterate over elements with forEach', () => {
|
|
59
|
+
buffer.put(1, 2, 3);
|
|
60
|
+
const result = [];
|
|
61
|
+
buffer.forEach((elem, prev, next, index) => {
|
|
62
|
+
result.push({
|
|
63
|
+
elem,
|
|
64
|
+
prev,
|
|
65
|
+
next,
|
|
66
|
+
index
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
expect(result).toEqual([{
|
|
70
|
+
elem: 1,
|
|
71
|
+
prev: undefined,
|
|
72
|
+
next: 2,
|
|
73
|
+
index: 0
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
elem: 2,
|
|
77
|
+
prev: 1,
|
|
78
|
+
next: 3,
|
|
79
|
+
index: 1
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
elem: 3,
|
|
83
|
+
prev: 2,
|
|
84
|
+
next: undefined,
|
|
85
|
+
index: 2
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should find element in buffer', () => {
|
|
91
|
+
buffer.put(1, 2, 3);
|
|
92
|
+
expect(buffer.find(2)).toBe(1);
|
|
93
|
+
expect(buffer.find(4)).toBe(-1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('should filter elements in buffer', () => {
|
|
97
|
+
buffer.put(1, 2, 3, 4, 5);
|
|
98
|
+
buffer.filter(x => x % 2 === 0);
|
|
99
|
+
expect(buffer.buffer).toEqual([2, 4]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should check if buffer is empty', () => {
|
|
103
|
+
expect(buffer.isEmpty()).toBe(true);
|
|
104
|
+
buffer.put(1);
|
|
105
|
+
expect(buffer.isEmpty()).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Throwable error with numeric error code.
|
|
3
|
+
*
|
|
4
|
+
* @copyright 2015-2023 Tinode LLC.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
export default class CommError extends Error {
|
|
9
|
+
constructor(message, code) {
|
|
10
|
+
super(`${message} (${code})`);
|
|
11
|
+
this.name = 'CommError';
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Global constants and configuration parameters.
|
|
3
|
+
*
|
|
4
|
+
* @copyright 2015-2025 Tinode LLC
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
PACKAGE_VERSION
|
|
10
|
+
} from '../version.js';
|
|
11
|
+
|
|
12
|
+
// Global constants
|
|
13
|
+
export const PROTOCOL_VERSION = '0'; // Major component of the version, e.g. '0' in '0.17.1'.
|
|
14
|
+
export const VERSION = PACKAGE_VERSION || '0.25';
|
|
15
|
+
export const LIBRARY = 'tinodejs/' + VERSION;
|
|
16
|
+
|
|
17
|
+
// Topic name prefixes.
|
|
18
|
+
export const TOPIC_NEW = 'new';
|
|
19
|
+
export const TOPIC_NEW_CHAN = 'nch';
|
|
20
|
+
export const TOPIC_ME = 'me';
|
|
21
|
+
export const TOPIC_FND = 'fnd';
|
|
22
|
+
export const TOPIC_SYS = 'sys';
|
|
23
|
+
export const TOPIC_SLF = 'slf';
|
|
24
|
+
export const TOPIC_CHAN = 'chn';
|
|
25
|
+
export const TOPIC_GRP = 'grp';
|
|
26
|
+
export const TOPIC_P2P = 'p2p';
|
|
27
|
+
export const USER_NEW = 'new';
|
|
28
|
+
|
|
29
|
+
// Starting value of a locally-generated seqId used for pending messages.
|
|
30
|
+
export const LOCAL_SEQID = 0xFFFFFFF;
|
|
31
|
+
|
|
32
|
+
// Status codes.
|
|
33
|
+
export const MESSAGE_STATUS_NONE = 0; // Status not assigned.
|
|
34
|
+
export const MESSAGE_STATUS_QUEUED = 10; // Local ID assigned, in progress to be sent.
|
|
35
|
+
export const MESSAGE_STATUS_SENDING = 20; // Transmission started.
|
|
36
|
+
export const MESSAGE_STATUS_FAILED = 30; // At least one attempt was made to send the message.
|
|
37
|
+
export const MESSAGE_STATUS_FATAL = 40; // Message sending failed and it should not be retried.
|
|
38
|
+
export const MESSAGE_STATUS_SENT = 50; // Delivered to the server.
|
|
39
|
+
export const MESSAGE_STATUS_RECEIVED = 60; // Received by the client.
|
|
40
|
+
export const MESSAGE_STATUS_READ = 70; // Read by the user.
|
|
41
|
+
export const MESSAGE_STATUS_TO_ME = 80; // The message is received from another user.
|
|
42
|
+
|
|
43
|
+
// Reject unresolved futures after this many milliseconds.
|
|
44
|
+
export const EXPIRE_PROMISES_TIMEOUT = 5_000;
|
|
45
|
+
// Periodicity of garbage collection of unresolved futures.
|
|
46
|
+
export const EXPIRE_PROMISES_PERIOD = 1_000;
|
|
47
|
+
|
|
48
|
+
// Delay before acknowledging that a message was recived.
|
|
49
|
+
export const RECV_TIMEOUT = 100;
|
|
50
|
+
|
|
51
|
+
// Default number of messages to pull into memory from persistent cache.
|
|
52
|
+
export const DEFAULT_MESSAGES_PAGE = 24;
|
|
53
|
+
|
|
54
|
+
// Unicode DEL character indicating data was deleted.
|
|
55
|
+
export const DEL_CHAR = '\u2421';
|
|
56
|
+
|
|
57
|
+
// Maximum number of pinnned messages;
|
|
58
|
+
export const MAX_PINNED_COUNT = 5;
|
|
59
|
+
|
|
60
|
+
// Tag prefixes for alias, email, phone.
|
|
61
|
+
export const TAG_ALIAS = 'alias:';
|
|
62
|
+
export const TAG_EMAIL = 'email:';
|
|
63
|
+
export const TAG_PHONE = 'tel:';
|
|
64
|
+
|
|
65
|
+
// Parameters of exponential backoff for reconnect attempts.
|
|
66
|
+
// 2000 milliseconds, minimum delay between reconnects.
|
|
67
|
+
export const BACKOFF_BASE = 2_000;
|
|
68
|
+
// Maximum delay between reconnects 2^10 * 2000 ~ 34 minutes.
|
|
69
|
+
export const BACKOFF_MAX_ITER = 10;
|
|
70
|
+
// Random jitter between reconnection attempts, 0.3 -> 30% jitter.
|
|
71
|
+
export const BACKOFF_JITTER = 0.3;
|