react-native-mosquito-transport 0.0.18 → 0.0.21

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.
Files changed (30) hide show
  1. package/.jshintignore +4 -0
  2. package/.jshintrc +16 -0
  3. package/README.md +75 -1
  4. package/TODO +10 -1
  5. package/example/ios/MosquitodbExample.xcodeproj/project.pbxproj +6 -5
  6. package/example/ios/MosquitodbExample.xcworkspace/contents.xcworkspacedata +10 -0
  7. package/example/ios/MosquitodbExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  8. package/example/ios/MosquitodbExample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  9. package/example/ios/MosquitodbExample.xcworkspace/xcuserdata/anthony.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  10. package/example/ios/MosquitodbExample.xcworkspace/xcuserdata/anthony.xcuserdatad/WorkspaceSettings.xcsettings +14 -0
  11. package/ios/Mosquitodb.swift +14 -1
  12. package/package.json +15 -14
  13. package/src/helpers/engine_api.js +39 -0
  14. package/src/helpers/peripherals.js +73 -127
  15. package/src/helpers/utils.js +48 -19
  16. package/src/helpers/values.js +8 -47
  17. package/src/helpers/variables.js +14 -6
  18. package/src/index.d.ts +103 -43
  19. package/src/index.js +198 -121
  20. package/src/products/auth/accessor.js +97 -36
  21. package/src/products/auth/index.js +151 -82
  22. package/src/products/database/accessor.js +720 -223
  23. package/src/products/database/bson.js +16 -0
  24. package/src/products/database/counter.js +16 -0
  25. package/src/products/database/index.js +303 -190
  26. package/src/products/database/types.js +1 -1
  27. package/src/products/database/validator.js +517 -254
  28. package/src/products/http_callable/index.js +111 -106
  29. package/src/products/storage/index.js +97 -88
  30. package/src/helpers/EngineApi.js +0 -33
package/src/index.js CHANGED
@@ -1,20 +1,20 @@
1
1
  import 'react-native-get-random-values';
2
- import { IS_WHOLE_NUMBER, deserializeE2E, listenReachableServer, serializeE2E } from "./helpers/peripherals";
3
- import { releaseCacheStore } from "./helpers/utils";
4
- import { Scoped } from "./helpers/variables";
5
- import { MTAuth } from "./products/auth";
6
- import { MTCollection, batchWrite } from "./products/database";
2
+ import { deserializeE2E, listenReachableServer, serializeE2E } from "./helpers/peripherals";
3
+ import { awaitStore, releaseCacheStore } from "./helpers/utils";
4
+ import { CacheStore, Scoped } from "./helpers/variables";
5
+ import { MTCollection, batchWrite, trySendPendingWrite } from "./products/database";
7
6
  import { MTStorage } from "./products/storage";
8
7
  import { ServerReachableListener, TokenRefreshListener } from "./helpers/listeners";
9
- import { initTokenRefresher, listenToken, listenTokenReady, parseToken, triggerAuthToken } from "./products/auth/accessor";
8
+ import { initTokenRefresher, listenToken, listenTokenReady, triggerAuthToken } from "./products/auth/accessor";
10
9
  import { TIMESTAMP, DOCUMENT_EXTRACTION, FIND_GEO_JSON, GEO_JSON } from "./products/database/types";
11
10
  import { mfetch } from "./products/http_callable";
12
11
  import { io } from "socket.io-client";
13
- import { validateCollectionPath } from "./products/database/validator";
14
- import { AUTH_PROVIDER_ID, CACHE_PROTOCOL, Regexs } from "./helpers/values";
15
- import { trySendPendingWrite } from "./products/database/accessor";
16
- import EngineApi from './helpers/EngineApi';
17
- import { parse, stringify } from 'json-buffer';
12
+ import { AUTH_PROVIDER_ID, CACHE_PROTOCOL } from "./helpers/values";
13
+ import EngineApi from './helpers/engine_api';
14
+ import { Validator } from 'guard-object';
15
+ import cloneDeep from 'lodash.clonedeep';
16
+ import { Buffer } from 'buffer';
17
+ import MTAuth, { purgePendingToken } from './products/auth';
18
18
 
19
19
  const {
20
20
  _listenCollection,
@@ -24,69 +24,86 @@ const {
24
24
  _listenUserVerification
25
25
  } = EngineApi;
26
26
 
27
+ // https://socket.io/docs/v3/emit-cheatsheet/#reserved-events
28
+ const reservedEventName = [
29
+ 'connect',
30
+ 'connect_error',
31
+ 'disconnect',
32
+ 'disconnecting',
33
+ 'newListener',
34
+ 'removeListener'
35
+ ];
36
+
27
37
  class RNMT {
28
38
  constructor(config) {
29
39
  validateMTConfig(config, this);
30
40
  this.config = {
31
41
  ...config,
32
- uglify: config.enableE2E_Encryption,
33
- apiUrl: config.projectUrl,
34
- projectUrl: config.projectUrl.split('/').slice(0, -1).join('/')
42
+ serverE2E_PublicKey: config.serverE2E_PublicKey && new Uint8Array(Buffer.from(config.serverE2E_PublicKey, 'base64')),
43
+ castBSON: config.castBSON === undefined || config.castBSON,
44
+ maxRetries: config.maxRetries || 3,
45
+ uglify: config.enableE2E_Encryption
35
46
  };
36
- const { projectUrl } = this.config;
47
+ const { projectUrl, extraHeaders } = this.config;
37
48
 
38
49
  this.config.secureUrl = projectUrl.startsWith('https');
39
50
  this.config.baseUrl = projectUrl.split('://')[1];
40
51
  this.config.wsPrefix = this.config.secureUrl ? 'wss' : 'ws';
41
52
 
42
53
  if (!Scoped.ReleaseCacheData)
43
- throw `releaseCache must be called before creating any ${this.constructor.name} instance`;
54
+ throw `initializeCache must be called before creating any ${this.constructor.name} instance`;
44
55
 
45
56
  if (!Scoped.InitializedProject[projectUrl]) {
46
- Scoped.InitializedProject[projectUrl] = true;
57
+ Scoped.InitializedProject[projectUrl] = cloneDeep(this.config);
47
58
  Scoped.LastTokenRefreshRef[projectUrl] = 0;
48
59
  triggerAuthToken(projectUrl);
49
60
  initTokenRefresher({ ...this.config }, true);
50
61
 
51
- const disconnectionGlich = {};
52
- let socket, lastUid, lastSocketProcess = 0, hasInited;
53
-
54
- listenToken((token, thisInited) => {
55
- const user = token && parseToken(token);
62
+ let isConnected,
63
+ lastSentToken,
64
+ queuedToken;
56
65
 
57
- const thisUid = (user?.uid || null);
58
- if (lastUid === thisUid && (hasInited || !thisInited)) return;
59
- if (!hasInited) hasInited = thisInited;
60
- lastUid = thisUid;
66
+ const socket = io(`${this.config.wsPrefix}://${this.config.baseUrl}`, {
67
+ transports: ['websocket', 'polling', 'flashsocket'],
68
+ extraHeaders,
69
+ auth: {
70
+ _m_internal: true,
71
+ _from_base: true
72
+ }
73
+ });
61
74
 
62
- if (lastSocketProcess) disconnectionGlich[lastSocketProcess] = true;
63
- if (socket) socket.close();
75
+ socket.on('_signal_signout', () => {
76
+ this.auth().signOut();
77
+ });
64
78
 
65
- const thisProcess = ++lastSocketProcess;
66
- socket = io(`${this.config.wsPrefix}://${projectUrl.split('://')[1]}`, {
67
- auth: {
68
- _m_internal: true,
69
- _from_base: true,
70
- atoken: token
71
- }
79
+ socket.on('connect', () => {
80
+ isConnected = true;
81
+ Scoped.IS_CONNECTED[projectUrl] = true;
82
+ if (queuedToken) updateMountedToken(queuedToken.token);
83
+ ServerReachableListener.dispatch(projectUrl, true);
84
+ awaitStore().then(() => {
85
+ trySendPendingWrite(projectUrl);
72
86
  });
87
+ });
73
88
 
74
- socket.on('_signal_signout', () => {
75
- this.auth().signOut();
76
- });
89
+ socket.on('disconnect', () => {
90
+ isConnected = false;
91
+ Scoped.IS_CONNECTED[projectUrl] = false;
92
+ ServerReachableListener.dispatch(projectUrl, false);
93
+ });
77
94
 
78
- socket.on('connect', () => {
79
- ServerReachableListener.dispatch(projectUrl, true);
80
- });
81
- socket.on('disconnect', () => {
82
- if (!disconnectionGlich[thisProcess])
83
- ServerReachableListener.dispatch(projectUrl, false);
84
- });
85
- }, projectUrl);
95
+ const updateMountedToken = (token) => {
96
+ if ((lastSentToken || null) !== (token || null)) {
97
+ socket.emit('_update_mounted_user', token || null);
98
+ lastSentToken = token;
99
+ }
100
+ queuedToken = undefined;
101
+ };
86
102
 
87
- listenReachableServer(c => {
88
- Scoped.IS_CONNECTED[projectUrl] = c;
89
- if (c) trySendPendingWrite();
103
+ listenToken(token => {
104
+ if (isConnected) {
105
+ updateMountedToken(token);
106
+ } else queuedToken = { token };
90
107
  }, projectUrl);
91
108
 
92
109
  TokenRefreshListener.listenTo(projectUrl, v => {
@@ -95,25 +112,29 @@ class RNMT {
95
112
  }
96
113
  }
97
114
 
98
- static releaseCache(prop) {
115
+ static initializeCache(prop) {
99
116
  if (Scoped.ReleaseCacheData) throw `calling ${this.name}() multiple times is prohibited`;
100
117
  validateReleaseCacheProp({ ...prop });
101
118
  Scoped.ReleaseCacheData = { ...prop };
102
119
  releaseCacheStore({ ...prop });
120
+ // purge residue tokens
121
+ awaitStore().then(() => {
122
+ Object.keys(CacheStore.PendingAuthPurge).forEach(k => {
123
+ purgePendingToken(k);
124
+ });
125
+ });
103
126
  }
104
127
 
105
128
  getDatabase = (dbName, dbUrl) => ({
106
129
  collection: (path) => new MTCollection({
107
130
  ...this.config,
108
131
  path,
109
- ...(dbName ? { dbName } : {}),
110
- ...(dbUrl ? { dbUrl } : {})
132
+ ...dbName ? { dbName } : {},
133
+ ...dbUrl ? { dbUrl } : {}
111
134
  })
112
135
  });
113
- collection = (path) => {
114
- validateCollectionPath(path);
115
- return new MTCollection({ ...this.config, path });
116
- }
136
+
137
+ collection = (path) => new MTCollection({ ...this.config, path });
117
138
  batchWrite = (map, configx) => batchWrite({ ...this.config }, map, configx);
118
139
  auth = () => new MTAuth({ ...this.config });
119
140
  storage = () => new MTStorage({ ...this.config });
@@ -121,8 +142,8 @@ class RNMT {
121
142
  listenReachableServer = (callback) => listenReachableServer(callback, this.config.projectUrl);
122
143
 
123
144
  getSocket = (configOpts) => {
124
- const { disableAuth, authHandshake } = configOpts || {},
125
- { projectUrl, uglify, accessKey, serverE2E_PublicKey, wsPrefix } = this.config;
145
+ const { disableAuth, authHandshake } = configOpts || {};
146
+ const { projectUrl, uglify, accessKey, serverE2E_PublicKey, wsPrefix, extraHeaders } = this.config;
126
147
 
127
148
  const restrictedRoute = [
128
149
  _listenCollection,
@@ -130,12 +151,14 @@ class RNMT {
130
151
  _startDisconnectWriteTask,
131
152
  _cancelDisconnectWriteTask,
132
153
  _listenUserVerification
133
- ];
154
+ ].map(v => [v(), v(true)]).flat();
134
155
 
135
- let socketReadyCallback,
136
- makeSocketCallback = () => new Promise(resolve => {
156
+ const makeSocketCallback = () =>
157
+ new Promise(resolve => {
137
158
  socketReadyCallback = resolve;
138
- }),
159
+ });
160
+
161
+ let socketReadyCallback,
139
162
  socketReadyPromise = makeSocketCallback(),
140
163
  socketListenerList = [],
141
164
  socketListenerIte = 0;
@@ -145,25 +168,31 @@ class RNMT {
145
168
  tokenListener,
146
169
  clientPrivateKey;
147
170
 
148
- const listenerCallback = (callback) => function () {
149
- const [args, ...restArgs] = [...arguments];
171
+ const listenerCallback = (route, callback) => async function () {
172
+ if (reservedEventName.includes(route)) {
173
+ callback?.(...[...arguments]);
174
+ return;
175
+ }
176
+
177
+ const [[args, not_encrypted], emitable] = [...arguments];
150
178
  let res;
151
179
 
152
180
  if (uglify) {
153
- res = parse(deserializeE2E(args, serverE2E_PublicKey, clientPrivateKey));
181
+ res = await deserializeE2E(args, serverE2E_PublicKey, clientPrivateKey);
154
182
  } else res = args;
183
+ const sortedArgs = discloseSocketArguments([res, not_encrypted]);
155
184
 
156
- callback?.(...res || [], ...typeof restArgs[0] === 'function' ? [function () {
157
- const args = [...arguments];
185
+ callback?.(...sortedArgs, ...typeof emitable === 'function' ? [async function () {
186
+ const [args, not_encrypted] = encloseSocketArguments([...arguments]);
158
187
  let res;
159
188
 
160
189
  if (uglify) {
161
- res = serializeE2E(stringify(args), undefined, serverE2E_PublicKey)[0];
190
+ res = (await serializeE2E(args, undefined, serverE2E_PublicKey))[0];
162
191
  } else res = args;
163
192
 
164
- restArgs[0](res);
193
+ emitable([res, not_encrypted]);
165
194
  }] : []);
166
- }
195
+ };
167
196
 
168
197
  const emit = ({ timeout, promise, emittion: emittionx }) => new Promise(async (resolve, reject) => {
169
198
  const [route, ...emittion] = emittionx;
@@ -176,41 +205,43 @@ class RNMT {
176
205
 
177
206
  let hasResolved, stime = Date.now();
178
207
 
179
- const timer = isNaN(timeout) ? undefined : setTimeout(() => {
208
+ const timer = timeout ? setTimeout(() => {
180
209
  hasResolved = true;
181
210
  reject(new Error('emittion timeout'));
182
- }, timeout);
211
+ }, timeout) : undefined;
183
212
 
184
213
  await socketReadyPromise;
185
214
  if (hasResolved) return;
186
215
  clearTimeout(timer);
187
216
 
188
217
  try {
189
- const h = isNaN(timeout) ? socket : socket.timeout(timeout - (Date.now() - stime));
218
+ const thisSocket = timeout ? socket.timeout(Math.max(timeout - (Date.now() - stime), 0)) : socket;
190
219
 
191
- const lastEmit = emittion.slice(-1)[0],
192
- mit = typeof lastEmit === 'function' ? emittion.slice(0, -1) : emittion;
220
+ const lastEmit = emittion.slice(-1)[0];
221
+ const hasEmitable = typeof lastEmit === 'function';
222
+ const [mit, not_encrypted] = encloseSocketArguments(hasEmitable ? emittion.slice(0, -1) : emittion);
193
223
 
194
- const [reqBuilder, [privateKey]] = uglify ? serializeE2E(stringify(mit), undefined, serverE2E_PublicKey) : [undefined, []];
224
+ const [reqBuilder, [privateKey]] = uglify ? await serializeE2E(mit, undefined, serverE2E_PublicKey) : [undefined, []];
195
225
 
196
- if (typeof lastEmit === 'function' && promise)
197
- throw 'emitWithAck cannot have function in it parameter';
226
+ if (hasEmitable && promise)
227
+ throw 'emitWithAck cannot have function in it argument';
198
228
 
199
- const p = await h[promise ? 'emitWithAck' : 'emit'](route,
200
- ...uglify ? [reqBuilder] : [mit],
201
- ...typeof lastEmit === 'function' ? [function () {
202
- const args = [...arguments][0];
229
+ const result = await thisSocket[promise ? 'emitWithAck' : 'emit'](route,
230
+ [uglify ? reqBuilder : mit, not_encrypted],
231
+ ...hasEmitable ? [async function () {
232
+ const [[args, not_encrypted]] = [...arguments];
203
233
  let res;
204
234
 
205
235
  if (uglify) {
206
- res = parse(deserializeE2E(args, serverE2E_PublicKey, privateKey));
236
+ res = await deserializeE2E(args, serverE2E_PublicKey, privateKey);
207
237
  } else res = args;
208
238
 
209
- lastEmit(...res || []);
239
+ lastEmit(...discloseSocketArguments([res, not_encrypted]));
210
240
  }] : []
211
241
  );
212
-
213
- resolve((promise && p) ? uglify ? parse(deserializeE2E(p, serverE2E_PublicKey, privateKey))[0] : p[0] : undefined);
242
+ if (promise && result) {
243
+ resolve(discloseSocketArguments([uglify ? await deserializeE2E(result[0], serverE2E_PublicKey, privateKey) : result[0], result[1]])[0]);
244
+ } else resolve();
214
245
  } catch (e) {
215
246
  reject(e);
216
247
  }
@@ -219,12 +250,14 @@ class RNMT {
219
250
  const init = async () => {
220
251
  if (hasCancelled) return;
221
252
  const mtoken = disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl];
222
- const [reqBuilder, [privateKey]] = uglify ? serializeE2E({ accessKey, a_extras: authHandshake }, mtoken, serverE2E_PublicKey) : [null, []];
253
+ const [reqBuilder, [privateKey]] = uglify ? await serializeE2E({ accessKey, a_extras: authHandshake }, mtoken, serverE2E_PublicKey) : [null, []];
223
254
 
224
255
  socket = io(`${wsPrefix}://${projectUrl.split('://')[1]}`, {
256
+ transports: ['websocket', 'polling', 'flashsocket'],
257
+ extraHeaders,
225
258
  auth: uglify ? {
226
259
  ugly: true,
227
- e2e: reqBuilder
260
+ e2e: reqBuilder.toString('base64')
228
261
  } : {
229
262
  ...mtoken ? { mtoken } : {},
230
263
  a_extras: authHandshake,
@@ -259,15 +292,20 @@ class RNMT {
259
292
  }
260
293
 
261
294
  return {
262
- timeout: (timeout) => ({
263
- emitWithAck: function () {
264
- return emit({
265
- timeout,
266
- promise: true,
267
- emittion: [...arguments]
268
- });
269
- }
270
- }),
295
+ timeout: (timeout) => {
296
+ if (timeout !== undefined && !Validator.POSITIVE_INTEGER(timeout))
297
+ throw `expected a positive integer for timeout but got ${timeout}`;
298
+
299
+ return {
300
+ emitWithAck: function () {
301
+ return emit({
302
+ timeout,
303
+ promise: true,
304
+ emittion: [...arguments]
305
+ });
306
+ }
307
+ };
308
+ },
271
309
  emit: function () { emit({ emittion: [...arguments] }) },
272
310
  emitWithAck: function () {
273
311
  return emit({
@@ -279,7 +317,7 @@ class RNMT {
279
317
  if (restrictedRoute.includes(route))
280
318
  throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
281
319
  const ref = ++socketListenerIte,
282
- listener = listenerCallback(callback);
320
+ listener = listenerCallback(route, callback);
283
321
 
284
322
  socketListenerList.push([ref, 'on', route, listener]);
285
323
  if (socket) socket.on(route, listener);
@@ -293,7 +331,7 @@ class RNMT {
293
331
  if (restrictedRoute.includes(route))
294
332
  throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
295
333
  const ref = ++socketListenerIte,
296
- listener = listenerCallback(callback);
334
+ listener = listenerCallback(route, callback);
297
335
 
298
336
  socketListenerList.push([ref, 'once', route, listener]);
299
337
  if (socket) socket.once(route, listener);
@@ -311,10 +349,31 @@ class RNMT {
311
349
  }
312
350
  }
313
351
  }
352
+ };
314
353
 
315
- wipeDatabaseCache = () => {
316
-
354
+ class DoNotEncrypt {
355
+ constructor(value) {
356
+ this.value = value;
317
357
  }
358
+ };
359
+
360
+ const encloseSocketArguments = (args) => {
361
+ const [encrypted, unencrypted] = [{}, {}];
362
+
363
+ args.forEach((v, i) => {
364
+ if (v instanceof DoNotEncrypt) {
365
+ unencrypted[i] = v.value;
366
+ } else encrypted[i] = v;
367
+ });
368
+ return [encrypted, unencrypted];
369
+ }
370
+
371
+ const discloseSocketArguments = (args = []) => {
372
+ return args.map((obj, i) => Object.entries(obj).map(v => i ? [v[0], new DoNotEncrypt(v[1])] : v)).flat()
373
+ .sort((a, b) => (a[0] * 1) - (b[0] * 1)).map((v, i) => {
374
+ if (v[0] * 1 !== i) throw 'corrupted socket arguments';
375
+ return v[1];
376
+ });
318
377
  }
319
378
 
320
379
  const validateReleaseCacheProp = (prop) => {
@@ -333,6 +392,11 @@ const validateReleaseCacheProp = (prop) => {
333
392
  throw `Invalid value supplied to "io.${k}", expected a function but got "${v}"`;
334
393
  } else throw `Unexpected property named "io.${k}"`;
335
394
  });
395
+ } else if (k === 'promoteCache') {
396
+ if (typeof v !== 'boolean') throw 'promoteCache should be a boolean';
397
+ } else if (k === 'heapMemory') {
398
+ if (typeof v !== 'number' || v <= 0)
399
+ throw `Invalid value supplied to heapMemory, value must be an integer greater than zero`;
336
400
  } else throw `Unexpected property named ${k}`;
337
401
  });
338
402
 
@@ -342,19 +406,15 @@ const validateReleaseCacheProp = (prop) => {
342
406
  const validator = {
343
407
  dbName: (v) => {
344
408
  if (typeof v !== 'string' || !v.trim())
345
- throw `Invalid value supplied to dbName, value must be string and greater than one`;
409
+ throw `Invalid value supplied to dbName, value must be a non-empty string`;
346
410
  },
347
411
  dbUrl: (v) => {
348
412
  if (typeof v !== 'string' || !v.trim())
349
- throw `Invalid value supplied to dbUrl, value must be string and greater than one`;
350
- },
351
- heapMemory: (v) => {
352
- if (typeof v !== 'number' || v <= 0)
353
- throw `Invalid value supplied to heapMemory, value must be number and greater than zero`;
413
+ throw `Invalid value supplied to dbUrl, value must be a non-empty string`;
354
414
  },
355
415
  projectUrl: (v) => {
356
- if (typeof v !== 'string' || !Regexs.LINK().test(v.trim()))
357
- throw `Invalid value supplied to projectUrl, value must be a string and greater than one`;
416
+ if (typeof v !== 'string' || (!Validator.HTTPS(v) && !Validator.HTTP(v)))
417
+ throw `Expected "projectUrl" to be valid https or http link but got "${v}"`;
358
418
  },
359
419
  disableCache: (v) => {
360
420
  if (typeof v !== 'boolean')
@@ -362,40 +422,57 @@ const validator = {
362
422
  },
363
423
  accessKey: (v) => {
364
424
  if (typeof v !== 'string' || !v.trim())
365
- throw `Invalid value supplied to accessKey, value must be a string and greater than one`;
425
+ throw `Invalid value supplied to accessKey, value must be a non-empty string`;
366
426
  },
367
427
  maxRetries: (v) => {
368
- if (typeof v !== 'number' || v <= 0 || !IS_WHOLE_NUMBER(v))
369
- throw `Invalid value supplied to maxRetries, value must be whole number and greater than zero`;
428
+ if (v <= 0 || !Validator.POSITIVE_INTEGER(v))
429
+ throw `Invalid value supplied to maxRetries, value must be positive integer greater than zero`;
370
430
  },
371
431
  enableE2E_Encryption: (v) => {
372
432
  if (typeof v !== 'boolean')
373
433
  throw `Invalid value supplied to enableE2E_Encryption, value must be a boolean`;
374
434
  },
435
+ castBSON: v => {
436
+ if (typeof v !== 'boolean')
437
+ throw `Invalid value supplied to castBSON, value must be a boolean`;
438
+ },
439
+ borrowToken: v => {
440
+ if (typeof v !== 'string' || (!Validator.HTTPS(v) && !Validator.HTTP(v)))
441
+ throw `Expected "borrowToken" to be valid https or http link but got "${v}"`;
442
+ },
375
443
  serverE2E_PublicKey: (v) => {
376
444
  if (typeof v !== 'string' || !v.trim())
377
- throw `Invalid value supplied to serverETE_PublicKey, value must be string and greater than one`;
445
+ throw `Invalid value supplied to serverETE_PublicKey, value must be a non-empty string`;
446
+ },
447
+ extraHeaders: v => {
448
+ if (!Validator.OBJECT(v)) throw '"extraHeaders" must be an object';
449
+ const reservedHeaders = ['mtoken', 'mosquito-token', 'init-content-type', 'content-type', 'authorization', 'uglified'];
450
+
451
+ Object.entries(v).forEach(([k, v]) => {
452
+ if (typeof v !== 'string') throw `expected a string at extraHeaders.${k} but got "${v}"`;
453
+ if (reservedHeaders.includes(v.toLowerCase()))
454
+ throw `extraHeaders must not include any reserved props which are: ${reservedHeaders}`;
455
+ });
378
456
  }
379
457
  };
380
458
 
381
459
  const validateMTConfig = (config, that) => {
382
- if (typeof config !== 'object') throw `${that.constructor.name} config is not an object`;
383
- const h = Object.keys(config);
384
-
385
- for (let i = 0; i < h.length; i++) {
386
- const k = h[i];
460
+ if (!Validator.OBJECT(config))
461
+ throw `${that.constructor.name} config is not an object`;
387
462
 
463
+ for (const [k, v] of Object.entries(config)) {
388
464
  if (!validator[k]) throw `Unexpected property named ${k}`;
389
- validator[k](config[k]);
465
+ validator[k](v);
390
466
  }
391
467
 
392
468
  if (config.enableE2E_Encryption && !config.serverE2E_PublicKey)
393
469
  throw '"serverE2E_PublicKey" is missing, enabling end-to-end encryption requires a public encryption key from the server';
394
- if (!config['projectUrl']) throw `projectUrl is a required property in ${that.constructor.name}() constructor`;
395
- if (!config['accessKey']) throw `accessKey is a required property in ${that.constructor.name}() constructor`;
470
+ if (!config.projectUrl) throw `projectUrl is a required property in ${that.constructor.name}() constructor`;
471
+ if (!config.accessKey) throw `accessKey is a required property in ${that.constructor.name}() constructor`;
396
472
  }
397
473
 
398
474
  export {
475
+ DoNotEncrypt,
399
476
  TIMESTAMP,
400
477
  DOCUMENT_EXTRACTION,
401
478
  FIND_GEO_JSON,