react-native-ble-mesh 1.0.3 → 1.1.0
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 +26 -8
- package/src/MeshNetwork.js +787 -0
- package/src/index.d.ts +386 -4
- package/src/index.js +56 -3
- package/src/mesh/index.js +5 -1
- package/src/mesh/monitor/NetworkMonitor.js +543 -0
- package/src/mesh/monitor/index.js +14 -0
- package/src/mesh/store/StoreAndForwardManager.js +476 -0
- package/src/mesh/store/index.js +12 -0
- package/src/service/BatteryOptimizer.js +497 -0
- package/src/service/EmergencyManager.js +370 -0
- package/src/service/index.js +10 -0
- package/src/utils/compression.js +456 -0
- package/src/utils/index.js +9 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview LZ4 Message Compression for BLE Mesh Network
|
|
5
|
+
* @module utils/compression
|
|
6
|
+
*
|
|
7
|
+
* Provides automatic LZ4 compression for payloads > threshold bytes.
|
|
8
|
+
* Achieves 40-60% bandwidth reduction for text messages.
|
|
9
|
+
*
|
|
10
|
+
* Note: This is a pure JavaScript LZ4 implementation optimized for
|
|
11
|
+
* React Native environments where native modules may not be available.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { ValidationError, MeshError } = require('../errors');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default compression configuration
|
|
18
|
+
* @constant {Object}
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_CONFIG = Object.freeze({
|
|
21
|
+
/** Minimum payload size to compress (bytes) */
|
|
22
|
+
threshold: 100,
|
|
23
|
+
/** Hash table size for compression (power of 2) */
|
|
24
|
+
hashTableSize: 4096,
|
|
25
|
+
/** Minimum match length */
|
|
26
|
+
minMatch: 4,
|
|
27
|
+
/** Maximum match search distance */
|
|
28
|
+
maxSearchDistance: 65535,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Message compression manager with LZ4 algorithm.
|
|
33
|
+
*
|
|
34
|
+
* @class MessageCompressor
|
|
35
|
+
* @example
|
|
36
|
+
* const compressor = new MessageCompressor({ threshold: 100 });
|
|
37
|
+
*
|
|
38
|
+
* const { data, compressed } = compressor.compress(payload);
|
|
39
|
+
* if (compressed) {
|
|
40
|
+
* // Send compressed data with IS_COMPRESSED flag
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* const original = compressor.decompress(data, compressed);
|
|
44
|
+
*/
|
|
45
|
+
class MessageCompressor {
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new MessageCompressor instance.
|
|
48
|
+
* @param {Object} [options={}] - Compression options
|
|
49
|
+
* @param {number} [options.threshold=100] - Min size to compress
|
|
50
|
+
*/
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this._config = { ...DEFAULT_CONFIG, ...options };
|
|
53
|
+
this._stats = {
|
|
54
|
+
compressionAttempts: 0,
|
|
55
|
+
successfulCompressions: 0,
|
|
56
|
+
decompressions: 0,
|
|
57
|
+
bytesIn: 0,
|
|
58
|
+
bytesOut: 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Compresses a payload if it exceeds the threshold.
|
|
64
|
+
* @param {Uint8Array} payload - Payload to compress
|
|
65
|
+
* @returns {{ data: Uint8Array, compressed: boolean }} Result
|
|
66
|
+
*/
|
|
67
|
+
compress(payload) {
|
|
68
|
+
if (!(payload instanceof Uint8Array)) {
|
|
69
|
+
throw ValidationError.invalidType('payload', payload, 'Uint8Array');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Don't compress if below threshold
|
|
73
|
+
if (payload.length < this._config.threshold) {
|
|
74
|
+
return { data: payload, compressed: false };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this._stats.compressionAttempts++;
|
|
78
|
+
this._stats.bytesIn += payload.length;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const compressed = this._lz4Compress(payload);
|
|
82
|
+
|
|
83
|
+
// Only use compressed if it's actually smaller
|
|
84
|
+
if (compressed.length < payload.length) {
|
|
85
|
+
this._stats.successfulCompressions++;
|
|
86
|
+
this._stats.bytesOut += compressed.length;
|
|
87
|
+
return { data: compressed, compressed: true };
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// Log compression error at debug level for troubleshooting
|
|
91
|
+
if (typeof console !== 'undefined' && console.debug) {
|
|
92
|
+
console.debug('Compression failed, using uncompressed:', error.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this._stats.bytesOut += payload.length;
|
|
97
|
+
return { data: payload, compressed: false };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Decompresses a payload.
|
|
102
|
+
* @param {Uint8Array} payload - Payload to decompress
|
|
103
|
+
* @param {boolean} wasCompressed - Whether payload was compressed
|
|
104
|
+
* @returns {Uint8Array} Decompressed data
|
|
105
|
+
*/
|
|
106
|
+
decompress(payload, wasCompressed) {
|
|
107
|
+
if (!wasCompressed) {
|
|
108
|
+
return payload;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!(payload instanceof Uint8Array)) {
|
|
112
|
+
throw ValidationError.invalidType('payload', payload, 'Uint8Array');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this._stats.decompressions++;
|
|
116
|
+
return this._lz4Decompress(payload);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Gets compression ratio for a payload.
|
|
121
|
+
* @param {Uint8Array} original - Original payload
|
|
122
|
+
* @param {Uint8Array} compressed - Compressed payload
|
|
123
|
+
* @returns {number} Compression ratio (0-100%)
|
|
124
|
+
*/
|
|
125
|
+
getCompressionRatio(original, compressed) {
|
|
126
|
+
if (original.length === 0) return 0;
|
|
127
|
+
return (1 - compressed.length / original.length) * 100;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Gets compression statistics.
|
|
132
|
+
* @returns {Object} Statistics
|
|
133
|
+
*/
|
|
134
|
+
getStats() {
|
|
135
|
+
const ratio = this._stats.bytesIn > 0
|
|
136
|
+
? (1 - this._stats.bytesOut / this._stats.bytesIn) * 100
|
|
137
|
+
: 0;
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
...this._stats,
|
|
141
|
+
averageCompressionRatio: ratio,
|
|
142
|
+
compressionRate: this._stats.compressionAttempts > 0
|
|
143
|
+
? this._stats.successfulCompressions / this._stats.compressionAttempts
|
|
144
|
+
: 0,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resets statistics.
|
|
150
|
+
*/
|
|
151
|
+
resetStats() {
|
|
152
|
+
this._stats = {
|
|
153
|
+
compressionAttempts: 0,
|
|
154
|
+
successfulCompressions: 0,
|
|
155
|
+
decompressions: 0,
|
|
156
|
+
bytesIn: 0,
|
|
157
|
+
bytesOut: 0,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* LZ4 compression implementation.
|
|
163
|
+
* @param {Uint8Array} input - Input data
|
|
164
|
+
* @returns {Uint8Array} Compressed data
|
|
165
|
+
* @private
|
|
166
|
+
*/
|
|
167
|
+
_lz4Compress(input) {
|
|
168
|
+
const inputLen = input.length;
|
|
169
|
+
|
|
170
|
+
// Worst case: no compression + header + margin
|
|
171
|
+
const output = new Uint8Array(inputLen + Math.ceil(inputLen / 255) + 16);
|
|
172
|
+
let outputPos = 0;
|
|
173
|
+
|
|
174
|
+
// Write original size (4 bytes, little-endian)
|
|
175
|
+
output[outputPos++] = inputLen & 0xff;
|
|
176
|
+
output[outputPos++] = (inputLen >> 8) & 0xff;
|
|
177
|
+
output[outputPos++] = (inputLen >> 16) & 0xff;
|
|
178
|
+
output[outputPos++] = (inputLen >> 24) & 0xff;
|
|
179
|
+
|
|
180
|
+
// Hash table for finding matches
|
|
181
|
+
// Using Knuth's multiplicative hash constant (2654435761) for good distribution
|
|
182
|
+
const hashTable = new Int32Array(this._config.hashTableSize);
|
|
183
|
+
hashTable.fill(-1);
|
|
184
|
+
|
|
185
|
+
let anchor = 0;
|
|
186
|
+
let inputPos = 0;
|
|
187
|
+
|
|
188
|
+
while (inputPos < inputLen - 4) {
|
|
189
|
+
// Calculate hash
|
|
190
|
+
const hash = this._hash4(input, inputPos);
|
|
191
|
+
const matchPos = hashTable[hash];
|
|
192
|
+
hashTable[hash] = inputPos;
|
|
193
|
+
|
|
194
|
+
// Check if we have a match
|
|
195
|
+
if (matchPos >= 0 && inputPos - matchPos <= this._config.maxSearchDistance) {
|
|
196
|
+
// Verify match at least starts correctly
|
|
197
|
+
if (input[matchPos] === input[inputPos] &&
|
|
198
|
+
input[matchPos + 1] === input[inputPos + 1] &&
|
|
199
|
+
input[matchPos + 2] === input[inputPos + 2] &&
|
|
200
|
+
input[matchPos + 3] === input[inputPos + 3]) {
|
|
201
|
+
|
|
202
|
+
// Extend the match
|
|
203
|
+
let matchLen = 4;
|
|
204
|
+
while (inputPos + matchLen < inputLen &&
|
|
205
|
+
input[matchPos + matchLen] === input[inputPos + matchLen]) {
|
|
206
|
+
matchLen++;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (matchLen >= this._config.minMatch) {
|
|
210
|
+
// Write literals before match
|
|
211
|
+
const literalLen = inputPos - anchor;
|
|
212
|
+
const offset = inputPos - matchPos;
|
|
213
|
+
|
|
214
|
+
outputPos = this._writeSequence(output, outputPos, input, anchor, literalLen, matchLen, offset);
|
|
215
|
+
|
|
216
|
+
inputPos += matchLen;
|
|
217
|
+
anchor = inputPos;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
inputPos++;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Write remaining literals (final block with no match)
|
|
227
|
+
const literalLen = inputLen - anchor;
|
|
228
|
+
if (literalLen > 0) {
|
|
229
|
+
outputPos = this._writeFinalLiterals(output, outputPos, input, anchor, literalLen);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return output.subarray(0, outputPos);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* LZ4 decompression implementation.
|
|
237
|
+
* @param {Uint8Array} input - Compressed data
|
|
238
|
+
* @returns {Uint8Array} Decompressed data
|
|
239
|
+
* @private
|
|
240
|
+
*/
|
|
241
|
+
_lz4Decompress(input) {
|
|
242
|
+
// Read original size
|
|
243
|
+
const originalSize = input[0] | (input[1] << 8) | (input[2] << 16) | (input[3] << 24);
|
|
244
|
+
|
|
245
|
+
if (originalSize <= 0 || originalSize > 100 * 1024 * 1024) {
|
|
246
|
+
throw new MeshError(
|
|
247
|
+
'Invalid compressed data: size header out of range',
|
|
248
|
+
'E900',
|
|
249
|
+
{ originalSize, maxAllowed: 100 * 1024 * 1024 }
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const output = new Uint8Array(originalSize);
|
|
254
|
+
let inputPos = 4;
|
|
255
|
+
let outputPos = 0;
|
|
256
|
+
|
|
257
|
+
while (inputPos < input.length && outputPos < originalSize) {
|
|
258
|
+
// Read token
|
|
259
|
+
const token = input[inputPos++];
|
|
260
|
+
let literalLen = token >> 4;
|
|
261
|
+
let matchLen = token & 0x0f;
|
|
262
|
+
|
|
263
|
+
// Read extended literal length
|
|
264
|
+
if (literalLen === 15) {
|
|
265
|
+
let byte;
|
|
266
|
+
do {
|
|
267
|
+
byte = input[inputPos++];
|
|
268
|
+
literalLen += byte;
|
|
269
|
+
} while (byte === 255);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Copy literals
|
|
273
|
+
for (let i = 0; i < literalLen && outputPos < originalSize; i++) {
|
|
274
|
+
output[outputPos++] = input[inputPos++];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check if we're at the end (last sequence has no match)
|
|
278
|
+
if (outputPos >= originalSize || inputPos >= input.length) {
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Read offset (2 bytes, little-endian)
|
|
283
|
+
const offset = input[inputPos] | (input[inputPos + 1] << 8);
|
|
284
|
+
inputPos += 2;
|
|
285
|
+
|
|
286
|
+
if (offset === 0) {
|
|
287
|
+
throw new MeshError(
|
|
288
|
+
'Invalid offset in compressed data: zero offset not allowed',
|
|
289
|
+
'E900',
|
|
290
|
+
{ inputPos, outputPos }
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Read extended match length
|
|
295
|
+
matchLen += 4; // Minimum match is 4
|
|
296
|
+
if ((token & 0x0f) === 15) {
|
|
297
|
+
let byte;
|
|
298
|
+
do {
|
|
299
|
+
byte = input[inputPos++];
|
|
300
|
+
matchLen += byte;
|
|
301
|
+
} while (byte === 255);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Copy match
|
|
305
|
+
const matchStart = outputPos - offset;
|
|
306
|
+
for (let i = 0; i < matchLen && outputPos < originalSize; i++) {
|
|
307
|
+
output[outputPos++] = output[matchStart + i];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return output.subarray(0, outputPos);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Calculates hash for 4 bytes using Knuth's multiplicative hash.
|
|
316
|
+
*
|
|
317
|
+
* Uses the constant 2654435761 (0x9E3779B1), which is derived from the
|
|
318
|
+
* golden ratio: floor(2^32 / φ) where φ ≈ 1.618033988749895.
|
|
319
|
+
* This constant provides excellent distribution properties for hash tables,
|
|
320
|
+
* minimizing clustering and collisions. It's widely used in LZ4 and other
|
|
321
|
+
* compression algorithms.
|
|
322
|
+
*
|
|
323
|
+
* Reference: Donald Knuth, "The Art of Computer Programming", Vol. 3
|
|
324
|
+
*
|
|
325
|
+
* @param {Uint8Array} data - Data buffer
|
|
326
|
+
* @param {number} pos - Position to read 4 bytes from
|
|
327
|
+
* @returns {number} Hash value in range [0, hashTableSize)
|
|
328
|
+
* @private
|
|
329
|
+
*/
|
|
330
|
+
_hash4(data, pos) {
|
|
331
|
+
const val = data[pos] | (data[pos + 1] << 8) |
|
|
332
|
+
(data[pos + 2] << 16) | (data[pos + 3] << 24);
|
|
333
|
+
// Knuth's multiplicative hash: multiply by golden ratio constant
|
|
334
|
+
// The >>> 0 ensures unsigned 32-bit arithmetic
|
|
335
|
+
return ((val * 2654435761) >>> 0) % this._config.hashTableSize;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Writes a complete LZ4 sequence (literals + match).
|
|
340
|
+
* @param {Uint8Array} output - Output buffer
|
|
341
|
+
* @param {number} pos - Current position
|
|
342
|
+
* @param {Uint8Array} input - Input data
|
|
343
|
+
* @param {number} literalStart - Start of literals in input
|
|
344
|
+
* @param {number} literalLen - Length of literals
|
|
345
|
+
* @param {number} matchLen - Match length
|
|
346
|
+
* @param {number} offset - Match offset
|
|
347
|
+
* @returns {number} New position
|
|
348
|
+
* @private
|
|
349
|
+
*/
|
|
350
|
+
_writeSequence(output, pos, input, literalStart, literalLen, matchLen, offset) {
|
|
351
|
+
// Adjust match length (minimum is 4, stored as matchLen - 4)
|
|
352
|
+
const adjustedMatchLen = matchLen - 4;
|
|
353
|
+
|
|
354
|
+
// Build token
|
|
355
|
+
const literalToken = Math.min(literalLen, 15);
|
|
356
|
+
const matchToken = Math.min(adjustedMatchLen, 15);
|
|
357
|
+
output[pos++] = (literalToken << 4) | matchToken;
|
|
358
|
+
|
|
359
|
+
// Write extended literal length
|
|
360
|
+
if (literalLen >= 15) {
|
|
361
|
+
let remaining = literalLen - 15;
|
|
362
|
+
while (remaining >= 255) {
|
|
363
|
+
output[pos++] = 255;
|
|
364
|
+
remaining -= 255;
|
|
365
|
+
}
|
|
366
|
+
output[pos++] = remaining;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Copy literals
|
|
370
|
+
for (let i = 0; i < literalLen; i++) {
|
|
371
|
+
output[pos++] = input[literalStart + i];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Write offset (little-endian)
|
|
375
|
+
output[pos++] = offset & 0xff;
|
|
376
|
+
output[pos++] = (offset >> 8) & 0xff;
|
|
377
|
+
|
|
378
|
+
// Write extended match length
|
|
379
|
+
if (adjustedMatchLen >= 15) {
|
|
380
|
+
let remaining = adjustedMatchLen - 15;
|
|
381
|
+
while (remaining >= 255) {
|
|
382
|
+
output[pos++] = 255;
|
|
383
|
+
remaining -= 255;
|
|
384
|
+
}
|
|
385
|
+
output[pos++] = remaining;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return pos;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Writes final literals block (no match following).
|
|
393
|
+
* This is the last sequence in the compressed data.
|
|
394
|
+
* @param {Uint8Array} output - Output buffer
|
|
395
|
+
* @param {number} pos - Current position
|
|
396
|
+
* @param {Uint8Array} input - Input data
|
|
397
|
+
* @param {number} start - Start position in input
|
|
398
|
+
* @param {number} len - Length to write
|
|
399
|
+
* @returns {number} New position
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
_writeFinalLiterals(output, pos, input, start, len) {
|
|
403
|
+
// Token with literal length only (match length = 0)
|
|
404
|
+
const literalToken = Math.min(len, 15);
|
|
405
|
+
output[pos++] = literalToken << 4;
|
|
406
|
+
|
|
407
|
+
// Write extended literal length
|
|
408
|
+
if (len >= 15) {
|
|
409
|
+
let remaining = len - 15;
|
|
410
|
+
while (remaining >= 255) {
|
|
411
|
+
output[pos++] = 255;
|
|
412
|
+
remaining -= 255;
|
|
413
|
+
}
|
|
414
|
+
output[pos++] = remaining;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Copy literals
|
|
418
|
+
for (let i = 0; i < len; i++) {
|
|
419
|
+
output[pos++] = input[start + i];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return pos;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Singleton instance for simple usage
|
|
428
|
+
* @type {MessageCompressor}
|
|
429
|
+
*/
|
|
430
|
+
const defaultCompressor = new MessageCompressor();
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Compresses data using default settings.
|
|
434
|
+
* @param {Uint8Array} payload - Data to compress
|
|
435
|
+
* @returns {{ data: Uint8Array, compressed: boolean }} Result
|
|
436
|
+
*/
|
|
437
|
+
function compress(payload) {
|
|
438
|
+
return defaultCompressor.compress(payload);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Decompresses data.
|
|
443
|
+
* @param {Uint8Array} payload - Data to decompress
|
|
444
|
+
* @param {boolean} wasCompressed - Whether data was compressed
|
|
445
|
+
* @returns {Uint8Array} Decompressed data
|
|
446
|
+
*/
|
|
447
|
+
function decompress(payload, wasCompressed) {
|
|
448
|
+
return defaultCompressor.decompress(payload, wasCompressed);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
module.exports = {
|
|
452
|
+
MessageCompressor,
|
|
453
|
+
compress,
|
|
454
|
+
decompress,
|
|
455
|
+
DEFAULT_CONFIG,
|
|
456
|
+
};
|
package/src/utils/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const LRUCache = require('./LRUCache');
|
|
|
15
15
|
const RateLimiter = require('./RateLimiter');
|
|
16
16
|
const retry = require('./retry');
|
|
17
17
|
const base64 = require('./base64');
|
|
18
|
+
const compression = require('./compression');
|
|
18
19
|
|
|
19
20
|
module.exports = {
|
|
20
21
|
// Byte manipulation
|
|
@@ -63,6 +64,11 @@ module.exports = {
|
|
|
63
64
|
validateFunction: validation.validateFunction,
|
|
64
65
|
validateObject: validation.validateObject,
|
|
65
66
|
|
|
67
|
+
// Compression
|
|
68
|
+
MessageCompressor: compression.MessageCompressor,
|
|
69
|
+
compress: compression.compress,
|
|
70
|
+
decompress: compression.decompress,
|
|
71
|
+
|
|
66
72
|
// Classes
|
|
67
73
|
EventEmitter,
|
|
68
74
|
LRUCache,
|
|
@@ -78,5 +84,7 @@ module.exports = {
|
|
|
78
84
|
uuid,
|
|
79
85
|
time,
|
|
80
86
|
validation,
|
|
81
|
-
base64
|
|
87
|
+
base64,
|
|
88
|
+
compression
|
|
82
89
|
};
|
|
90
|
+
|