react-native-mosquito-transport 0.0.51 → 0.0.52

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mosquito-transport",
3
- "version": "0.0.51",
3
+ "version": "0.0.52",
4
4
  "description": "React native javascript sdk for mosquito-transport (https://github.com/brainbehindx/mosquito-transport)",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "guard-object": "^1.1.4",
42
42
  "poke-object": "^1.0.1",
43
43
  "simplify-error": "^1.0.1",
44
- "socket.io-client": "^4.8.1",
44
+ "socket.io-client": "^4.8.3",
45
45
  "subscription-listener": "^1.1.3",
46
46
  "tweetnacl": "^1.0.3"
47
47
  },
@@ -51,4 +51,4 @@
51
51
  "react-native-get-random-values": "*",
52
52
  "react-native-sha256": "*"
53
53
  }
54
- }
54
+ }
@@ -4,7 +4,6 @@ export const Scoped = {
4
4
  PendingIte: 0,
5
5
  AnyProcessIte: 0,
6
6
  IS_CONNECTED: {},
7
- IS_TOKEN_READY: {},
8
7
  InitializedProject: {},
9
8
  ReleaseCacheData: undefined,
10
9
  AuthJWTToken: {},
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import 'react-native-get-random-values';
2
2
  import { deserializeE2E, listenReachableServer, serializeE2E } from "./helpers/peripherals";
3
- import { awaitStore, releaseCacheStore } from "./helpers/utils";
3
+ import { awaitReachableServer, awaitStore, releaseCacheStore } from "./helpers/utils";
4
4
  import { CacheStore, Scoped } from "./helpers/variables";
5
5
  import { MTCollection, batchWrite, onCollectionConnect, trySendPendingWrite } from "./products/database";
6
6
  import { MTStorage } from "./products/storage";
7
- import { ServerReachableListener, TokenRefreshListener } from "./helpers/listeners";
8
- import { initTokenRefresher, listenToken, listenTokenReady, triggerAuthToken } from "./products/auth/accessor";
7
+ import { ServerReachableListener } from "./helpers/listeners";
8
+ import { awaitRefreshToken, initTokenRefresher, listenToken, listenTokenReady, triggerAuthToken } from "./products/auth/accessor";
9
9
  import { TIMESTAMP, DOCUMENT_EXTRACTION, FIND_GEO_JSON, GEO_JSON, TIMESTAMP_OFFSET } from "./products/database/types";
10
10
  import { mfetch } from "./products/http_callable";
11
11
  import { io } from "socket.io-client";
@@ -73,34 +73,49 @@ class RNMT {
73
73
  _from_base: true
74
74
  }
75
75
  });
76
+
76
77
  let connectionIte = 0;
78
+ let chainedPromise;
79
+ const setConnected = c => {
80
+ isConnected = c;
81
+ Scoped.IS_CONNECTED[projectUrl] = isConnected;
82
+ ServerReachableListener.dispatchPersist(projectUrl, isConnected);
83
+ }
84
+
77
85
  const onConnect = () => {
78
86
  ++connectionIte;
79
- isConnected = true;
80
- Scoped.IS_CONNECTED[projectUrl] = true;
87
+ setConnected(true);
81
88
  if (recentToken) updateMountedToken();
82
- ServerReachableListener.dispatchPersist(projectUrl, true);
83
89
  awaitStore().then(() => {
84
90
  if (isConnected) trySendPendingWrite(projectUrl);
85
91
  });
86
92
  };
93
+
87
94
  const onDisconnect = () => {
88
95
  ++connectionIte;
89
- isConnected = isVirtualMachineFocused ? false : null;
90
- Scoped.IS_CONNECTED[projectUrl] = isConnected;
91
- ServerReachableListener.dispatchPersist(projectUrl, isConnected);
96
+ setConnected(isVirtualMachineFocused ? false : null);
92
97
  }
93
98
 
94
99
  const manualCheckConnection = () => {
100
+ if (chainedPromise) return;
95
101
  const ref = ++connectionIte;
96
- fetch(_areYouOk(projectUrl), { credentials: 'omit' }).then(async r => {
102
+ const signal = new AbortController();
103
+ const timer = setTimeout(() => {
104
+ signal.abort();
105
+ }, 7000);
106
+ chainedPromise = fetch(_areYouOk(projectUrl), { credentials: 'omit', signal }).then(async r => {
107
+ clearTimeout(timer);
108
+ chainedPromise = undefined;
97
109
  if ((await r.json()).status === 'yes') {
98
110
  if (ref === connectionIte) onConnect();
99
111
  } else throw null;
100
112
  }).catch(() => {
113
+ clearTimeout(timer);
114
+ chainedPromise = undefined;
101
115
  if (ref === connectionIte) onDisconnect();
102
116
  });
103
117
  }
118
+
104
119
  manualCheckConnection();
105
120
 
106
121
  socket.on('_signal_signout', () => {
@@ -112,7 +127,7 @@ class RNMT {
112
127
  manualCheckConnection();
113
128
  });
114
129
 
115
- AppState.addEventListener('change', (s) => {
130
+ AppState.addEventListener('change', s => {
116
131
  isVirtualMachineFocused = s === 'active';
117
132
  manualCheckConnection();
118
133
  });
@@ -125,10 +140,6 @@ class RNMT {
125
140
  recentToken = token;
126
141
  if (isConnected) updateMountedToken();
127
142
  }, projectUrl);
128
-
129
- TokenRefreshListener.listenTo(projectUrl, v => {
130
- Scoped.IS_TOKEN_READY[projectUrl] = v;
131
- });
132
143
  }
133
144
  }
134
145
 
@@ -181,16 +192,31 @@ class RNMT {
181
192
  _listenUserVerification
182
193
  ].map(v => [v(), v(true)]).flat();
183
194
 
184
- const makeSocketCallback = () =>
185
- new Promise(resolve => {
186
- socketReadyCallback = resolve;
187
- });
188
-
189
195
  let socketReadyCallback,
190
- socketReadyPromise = makeSocketCallback(),
196
+ socketReadyPromise,
191
197
  socketListenerList = [],
192
198
  socketListenerIte = 0;
193
199
 
200
+ const makeSocketCallback = () => {
201
+ const prevCallback = socketReadyCallback;
202
+
203
+ socketReadyPromise = new Promise((resolve, reject) => {
204
+
205
+ socketReadyCallback = [
206
+ () => {
207
+ prevCallback?.[0]?.();
208
+ resolve();
209
+ },
210
+ () => {
211
+ prevCallback?.[1]?.();
212
+ reject();
213
+ }
214
+ ];
215
+ });
216
+ }
217
+
218
+ makeSocketCallback();
219
+
194
220
  /**
195
221
  * @type {import('socket.io-client').Socket}
196
222
  */
@@ -225,67 +251,88 @@ class RNMT {
225
251
  }] : []);
226
252
  };
227
253
 
228
- const emit = ({ timeout, promise, emittion: emittionx }) => new Promise(async (resolve, reject) => {
229
- const [route, ...emittion] = emittionx;
254
+ const emit = ({ timeout, promise, emittion: emittionx }) =>
255
+ new Promise(async (resolve, reject) => {
256
+ const [route, ...emittion] = emittionx;
230
257
 
231
- if (typeof route !== 'string')
232
- throw `expected ${promise ? 'emitWithAck' : 'emit'} first argument to be a string type`;
258
+ if (typeof route !== 'string')
259
+ throw `expected ${promise ? 'emitWithAck' : 'emit'} first argument to be a string type`;
233
260
 
234
- if (restrictedRoute.includes(route))
235
- throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
261
+ if (restrictedRoute.includes(route))
262
+ throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
263
+
264
+ let hasResolved, stime = Date.now();
236
265
 
237
- let hasResolved, stime = Date.now();
266
+ const timer = timeout ? setTimeout(() => {
267
+ hasResolved = true;
268
+ reject(new Error('emittion timeout'));
269
+ }, timeout) : undefined;
238
270
 
239
- const timer = timeout ? setTimeout(() => {
240
- hasResolved = true;
241
- reject(new Error('emittion timeout'));
242
- }, timeout) : undefined;
271
+ await socketReadyPromise;
272
+ if (hasResolved) return;
273
+ clearTimeout(timer);
243
274
 
244
- await socketReadyPromise;
245
- if (hasResolved) return;
246
- clearTimeout(timer);
275
+ try {
276
+ const thisSocket = timeout ? socket.timeout(Math.max(timeout - (Date.now() - stime), 0)) : socket;
247
277
 
248
- try {
249
- const thisSocket = timeout ? socket.timeout(Math.max(timeout - (Date.now() - stime), 0)) : socket;
278
+ const lastEmit = emittion.slice(-1)[0];
279
+ const hasEmitable = typeof lastEmit === 'function';
280
+ const [mit, not_encrypted] = encloseSocketArguments(hasEmitable ? emittion.slice(0, -1) : emittion);
250
281
 
251
- const lastEmit = emittion.slice(-1)[0];
252
- const hasEmitable = typeof lastEmit === 'function';
253
- const [mit, not_encrypted] = encloseSocketArguments(hasEmitable ? emittion.slice(0, -1) : emittion);
282
+ const [reqBuilder, [privateKey]] = uglify ? await serializeE2E(mit, undefined, serverE2E_PublicKey) : [undefined, []];
254
283
 
255
- const [reqBuilder, [privateKey]] = uglify ? await serializeE2E(mit, undefined, serverE2E_PublicKey) : [undefined, []];
284
+ if (hasEmitable && promise)
285
+ throw 'emitWithAck cannot have function in it argument';
256
286
 
257
- if (hasEmitable && promise)
258
- throw 'emitWithAck cannot have function in it argument';
287
+ const result = await thisSocket[promise ? 'emitWithAck' : 'emit'](route,
288
+ [uglify ? reqBuilder : mit, not_encrypted],
289
+ ...hasEmitable ? [async function () {
290
+ const [[args, not_encrypted]] = [...arguments];
291
+ let res;
259
292
 
260
- const result = await thisSocket[promise ? 'emitWithAck' : 'emit'](route,
261
- [uglify ? reqBuilder : mit, not_encrypted],
262
- ...hasEmitable ? [async function () {
263
- const [[args, not_encrypted]] = [...arguments];
264
- let res;
293
+ if (uglify) {
294
+ res = await deserializeE2E(args, serverE2E_PublicKey, privateKey);
295
+ } else res = args;
265
296
 
266
- if (uglify) {
267
- res = await deserializeE2E(args, serverE2E_PublicKey, privateKey);
268
- } else res = args;
297
+ lastEmit(...discloseSocketArguments([res, not_encrypted]));
298
+ }] : []
299
+ );
300
+ if (promise && result) {
301
+ resolve(discloseSocketArguments([uglify ? await deserializeE2E(result[0], serverE2E_PublicKey, privateKey) : result[0], result[1]])[0]);
302
+ } else resolve();
303
+ } catch (e) {
304
+ reject(e);
305
+ }
306
+ });
269
307
 
270
- lastEmit(...discloseSocketArguments([res, not_encrypted]));
271
- }] : []
272
- );
273
- if (promise && result) {
274
- resolve(discloseSocketArguments([uglify ? await deserializeE2E(result[0], serverE2E_PublicKey, privateKey) : result[0], result[1]])[0]);
275
- } else resolve();
276
- } catch (e) {
277
- reject(e);
308
+ let initIte = 0;
309
+ let foregroundListener;
310
+ const clearForegroundListener = () => {
311
+ if (!foregroundListener) return;
312
+ foregroundListener.remove();
313
+ foregroundListener = undefined;
314
+ }
315
+
316
+ const clearSocket = () => {
317
+ if (socket) {
318
+ socket.close();
319
+ socket = undefined;
278
320
  }
279
- });
321
+ }
280
322
 
281
323
  const init = async () => {
324
+ clearForegroundListener();
282
325
  if (hasCancelled) return;
326
+ const instance_id = ++initIte;
327
+
283
328
  const mtoken = disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl];
284
329
  const [reqBuilder, [privateKey]] = uglify ? await serializeE2E({ a_extras: authHandshake }, mtoken, serverE2E_PublicKey) : [null, []];
285
330
 
286
331
  const getWsPrefix = url => url.startsWith('https') ? 'wss' : 'ws';
287
332
  const wsUrl = overidenUrl ? `${getWsPrefix(overidenUrl)}://${overidenUrl.split('://')[1]}` : `${wsPrefix}://${projectUrl.split('://')[1]}`;
288
333
 
334
+ if (instance_id !== initIte) return;
335
+
289
336
  socket = io(wsUrl, {
290
337
  transports: ['websocket', 'polling', 'flashsocket'],
291
338
  extraHeaders,
@@ -295,32 +342,88 @@ class RNMT {
295
342
  } : {
296
343
  ...mtoken ? { mtoken } : {},
297
344
  a_extras: authHandshake
345
+ },
346
+ reconnection: false
347
+ });
348
+
349
+ const reconnect = (timeout) => {
350
+ if (initIte !== instance_id || hasCancelled) return;
351
+
352
+ makeSocketCallback();
353
+ const reloadIntance = async () => {
354
+ if (!disableAuth) await awaitRefreshToken(projectUrl);
355
+ if (initIte === instance_id) remountInit();
356
+ }
357
+
358
+ if (AppState.currentState === 'active') {
359
+ setTimeout(() => {
360
+ awaitReachableServer(projectUrl).then(reloadIntance);
361
+ }, timeout);
362
+ } else {
363
+ foregroundListener = AppState.addEventListener('change', s => {
364
+ if (s === 'active') {
365
+ clearForegroundListener();
366
+ reloadIntance();
367
+ }
368
+ });
298
369
  }
370
+ }
371
+
372
+ let wasHandled;
373
+ socket.on('connect', () => {
374
+ if (initIte !== instance_id) return;
375
+ socketReadyCallback[0]();
376
+ socketReadyCallback = undefined;
377
+ });
378
+
379
+ socket.on('connect_error', () => {
380
+ if (initIte !== instance_id || wasHandled) return;
381
+ wasHandled = true;
382
+ clearSocket();
383
+ reconnect(3000);
299
384
  });
300
- clientPrivateKey = privateKey;
301
385
 
302
- socketReadyCallback();
386
+ socket.on('disconnect', r => {
387
+ if (initIte !== instance_id || wasHandled) return;
388
+ wasHandled = true;
389
+ clearSocket();
390
+ if (r === 'io client disconnect') return;
391
+ if (r === 'io server disconnect') {
392
+ resultant.destroy();
393
+ } else reconnect(0);
394
+ });
395
+
396
+ clientPrivateKey = privateKey;
303
397
  socketListenerList.forEach(([_, method, route, callback]) => {
304
398
  socket[method](route, callback);
305
399
  });
306
400
  }
307
401
 
402
+ const remountInit = () => {
403
+ makeSocketCallback();
404
+ if (socket) {
405
+ ++initIte;
406
+ clearSocket();
407
+ }
408
+ init();
409
+ }
410
+
308
411
  if (disableAuth) {
309
412
  init();
310
413
  } else {
311
414
  let lastTokenStatus;
312
415
 
313
- tokenListener = listenTokenReady(status => {
314
- if (lastTokenStatus === (status || false)) return;
315
-
316
- if (status) {
317
- init();
416
+ tokenListener = listenTokenReady(ready => {
417
+ if (lastTokenStatus === (ready || false)) return;
418
+ if (ready) {
419
+ remountInit();
318
420
  } else {
319
- socket?.close?.();
320
- socket = undefined;
321
- socketReadyPromise = makeSocketCallback();
421
+ makeSocketCallback();
422
+ ++initIte;
423
+ clearForegroundListener();
424
+ clearSocket();
322
425
  }
323
- lastTokenStatus = status || false;
426
+ lastTokenStatus = ready || false;
324
427
  }, projectUrl);
325
428
  }
326
429
 
@@ -377,8 +480,14 @@ class RNMT {
377
480
  destroy: () => {
378
481
  hasCancelled = true;
379
482
  tokenListener?.();
380
- if (socket) socket.close();
483
+ clearForegroundListener();
484
+ clearSocket();
381
485
  socketListenerList = [];
486
+ if (!socketReadyCallback) {
487
+ makeSocketCallback();
488
+ }
489
+ socketReadyCallback[1]('socket already disconnected');
490
+ socketReadyCallback = undefined;
382
491
  }
383
492
  };
384
493
 
@@ -56,7 +56,7 @@ export const injectEmulatedAuth = async (config, emulatedURL) => {
56
56
  export const parseToken = (token) => JSON.parse(decodeBinary(token.split('.')[1]));
57
57
 
58
58
  export const triggerAuthToken = async (projectUrl, isInit) => {
59
- await awaitStore();
59
+ if (!Scoped.IsStoreReady) await awaitStore();
60
60
  AuthTokenListener.dispatchPersist(projectUrl, CacheStore.AuthStore[projectUrl]?.token || null, isInit);
61
61
  };
62
62
 
@@ -97,15 +97,16 @@ export const initTokenRefresher = async ({ config, forceRefresh, justCheck }) =>
97
97
 
98
98
  if (token) {
99
99
  const rizz = () => {
100
- const runningProcess = Scoped.TokenRefreshProcess[projectUrl];
101
- if (runningProcess) return runningProcess;
102
-
103
- Scoped.TokenRefreshProcess[projectUrl] =
104
- refreshToken(config, maxRetries, forceRefresh);
105
-
106
- Scoped.TokenRefreshProcess[projectUrl].finally(() => {
107
- delete Scoped.TokenRefreshProcess[projectUrl];
108
- });
100
+ let runningProcess = Scoped.TokenRefreshProcess[projectUrl];
101
+ if (!runningProcess) {
102
+ runningProcess = refreshToken(config, maxRetries, forceRefresh);
103
+ Scoped.TokenRefreshProcess[projectUrl] = runningProcess;
104
+
105
+ Scoped.TokenRefreshProcess[projectUrl].finally(() => {
106
+ delete Scoped.TokenRefreshProcess[projectUrl];
107
+ });
108
+ }
109
+ return runningProcess;
109
110
  }
110
111
 
111
112
  if (await hasTokenExpire(projectUrl) || forceRefresh) {
@@ -120,11 +121,11 @@ export const initTokenRefresher = async ({ config, forceRefresh, justCheck }) =>
120
121
  clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
121
122
  Scoped.TokenRefreshTimer[projectUrl] = setInterval(async () => {
122
123
  const iteRef = ++lastIte;
123
- if (iteRef !== lastIte || !(await hasTokenExpire(projectUrl))) return;
124
+ if (!(await hasTokenExpire(projectUrl)) || iteRef !== lastIte) return;
124
125
  clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
125
126
  notifyAuthReady();
126
127
  rizz();
127
- }, 9000);
128
+ }, 7000);
128
129
  }
129
130
  }
130
131
  } else {
@@ -149,9 +150,10 @@ const updateTokenTimestamp = async (projectUrl, token) => {
149
150
  };
150
151
  }
151
152
 
152
- export const getEmulatedLinks = (projectUrl) => Object.entries(CacheStore.EmulatedAuth)
153
- .filter(([_, v]) => v === projectUrl)
154
- .map(v => v[0]);
153
+ export const getEmulatedLinks = (projectUrl) =>
154
+ Object.entries(CacheStore.EmulatedAuth)
155
+ .filter(([_, v]) => v === projectUrl)
156
+ .map(v => v[0]);
155
157
 
156
158
  const refreshToken = (builder, remainRetries = 1, isForceRefresh) =>
157
159
  new Promise(async (resolve, reject) => {
@@ -171,15 +173,20 @@ const refreshToken = (builder, remainRetries = 1, isForceRefresh) =>
171
173
 
172
174
  const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
173
175
 
176
+ if (!CacheStore.AuthStore[projectUrl]) {
177
+ reject(simplifyError('token_not_mounted', 'No refresh token was mounted or has been recently removed').simpleError);
178
+ return;
179
+ }
180
+
174
181
  CacheStore.AuthStore[projectUrl].token = f.result.token;
175
182
  Scoped.AuthJWTToken[projectUrl] = f.result.token;
176
183
  await updateTokenTimestamp(projectUrl, f.result.token);
177
184
 
178
185
  resolve(f.result.token);
179
- const isInit = !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh;
186
+ const isInit = !Scoped.InitiatedForcedToken[projectUrl];
180
187
 
181
188
  triggerAuthToken(projectUrl, isInit);
182
- if (isForceRefresh) Scoped.InitiatedForcedToken[projectUrl] = true;
189
+ if (isInit) Scoped.InitiatedForcedToken[projectUrl] = true;
183
190
 
184
191
  getEmulatedLinks(projectUrl).forEach(v => {
185
192
  CacheStore.AuthStore[v] = basicClone(CacheStore.AuthStore[projectUrl]);
@@ -185,13 +185,14 @@ const doCustomSignin = (builder, email, password) => new Promise(async (resolve,
185
185
 
186
186
  const r = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
187
187
 
188
+ revokeAuthIntance(builder, thisAuthStore);
189
+ injectFreshToken(builder, r.result);
190
+
188
191
  resolve({
189
192
  user: parseToken(r.result.token),
190
193
  token: r.result.token,
191
194
  refreshToken: r.result.refreshToken
192
195
  });
193
- revokeAuthIntance(builder, thisAuthStore);
194
- await injectFreshToken(builder, r.result);
195
196
  } catch (e) {
196
197
  reject(simplifyCaughtError(e).simpleError);
197
198
  }
@@ -218,14 +219,15 @@ const doCustomSignup = (builder, email, password, name, metadata) => new Promise
218
219
 
219
220
  const r = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
220
221
 
222
+ revokeAuthIntance(builder, thisAuthStore);
223
+ injectFreshToken(builder, r.result);
224
+
221
225
  resolve({
222
226
  user: parseToken(r.result.token),
223
227
  token: r.result.token,
224
228
  refreshToken: r.result.refreshToken,
225
229
  isNewUser: !!r.result.isNewUser
226
230
  });
227
- revokeAuthIntance(builder, thisAuthStore);
228
- await injectFreshToken(builder, r.result);
229
231
  } catch (e) {
230
232
  reject(simplifyCaughtError(e).simpleError);
231
233
  }
@@ -249,7 +251,10 @@ const clearCacheForSignout = (builder, disposeEmulated) => {
249
251
 
250
252
  purgeCache(projectUrl, true);
251
253
  if (disposeEmulated) getEmulatedLinks(projectUrl).forEach(e => purgeCache(e));
252
- initTokenRefresher({ config: builder });
254
+
255
+ setTimeout(() => {
256
+ initTokenRefresher({ config: builder });
257
+ }, 600);
253
258
  };
254
259
 
255
260
  export const doSignOut = async (builder) => {
@@ -330,14 +335,15 @@ const doProviderSignin = (builder, token, metadata, endpointer) => new Promise(a
330
335
 
331
336
  const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
332
337
 
338
+ revokeAuthIntance(builder, thisAuthStore);
339
+ injectFreshToken(builder, f.result);
340
+
333
341
  resolve({
334
342
  user: parseToken(f.result.token),
335
343
  token: f.result.token,
336
344
  refreshToken: f.result.refreshToken,
337
345
  isNewUser: f.result.isNewUser
338
346
  });
339
- revokeAuthIntance(builder, thisAuthStore);
340
- await injectFreshToken(builder, f.result);
341
347
  } catch (e) {
342
348
  reject(simplifyCaughtError(e).simpleError);
343
349
  }
@@ -2,17 +2,18 @@ import { io } from "socket.io-client";
2
2
  import EngineApi from "../../helpers/engine_api";
3
3
  import { DatabaseRecordsListener } from "../../helpers/listeners";
4
4
  import { deserializeE2E, listenReachableServer, niceTry, serializeE2E } from "../../helpers/peripherals";
5
- import { awaitStore, buildFetchInterface, buildFetchResult, getReachableServer, updateCacheStore } from "../../helpers/utils";
5
+ import { awaitReachableServer, awaitStore, buildFetchInterface, buildFetchResult, getReachableServer, updateCacheStore } from "../../helpers/utils";
6
6
  import { CacheStore, Scoped } from "../../helpers/variables";
7
7
  import { addPendingWrites, generateRecordID, getCountQuery, getRecord, insertCountQuery, insertRecord, listenQueryEntry, removePendingWrite, validateWriteValue } from "./accessor";
8
8
  import { validateCollectionName, validateFilter, validateFindConfig, validateFindObject, validateListenFindConfig } from "./validator";
9
- import { awaitRefreshToken, listenToken } from "../auth/accessor";
9
+ import { awaitRefreshToken, listenTokenReady } from "../auth/accessor";
10
10
  import { DELIVERY, RETRIEVAL } from "../../helpers/values";
11
11
  import { ObjectId } from "../../vendor/bson";
12
12
  import { guardObject, Validator } from "guard-object";
13
13
  import { simplifyCaughtError } from "simplify-error";
14
14
  import { deserializeBSON, serializeToBase64 } from "./bson";
15
15
  import { basicClone } from "../../helpers/basic_clone";
16
+ import { AppState } from "react-native";
16
17
 
17
18
  export class MTCollection {
18
19
  constructor(config) {
@@ -149,11 +150,13 @@ const listenDocument = (callback, onError, builder, config) => {
149
150
  validateFilter(findOne || find);
150
151
  validateCollectionName(path);
151
152
 
153
+ /**
154
+ * @type {import('socket.io-client').Socket}
155
+ */
156
+ let socket;
152
157
  let hasCancelled,
153
158
  hasRespond,
154
159
  cacheListener,
155
- socket,
156
- lastToken = Scoped.AuthJWTToken[projectUrl] || null,
157
160
  lastInitRef = 0,
158
161
  connectedListener,
159
162
  lastSnapshot;
@@ -184,7 +187,23 @@ const listenDocument = (callback, onError, builder, config) => {
184
187
  });
185
188
  }
186
189
 
190
+ let foregroundListener;
191
+ const clearForegroundListener = () => {
192
+ if (!foregroundListener) return;
193
+ foregroundListener.remove();
194
+ foregroundListener = undefined;
195
+ }
196
+
197
+ const clearSocket = () => {
198
+ if (socket) {
199
+ socket.close();
200
+ socket = undefined;
201
+ }
202
+ }
203
+
187
204
  const init = async () => {
205
+ clearForegroundListener();
206
+
188
207
  const processID = ++lastInitRef;
189
208
  if (!disableAuth) await awaitRefreshToken(projectUrl);
190
209
  if (hasCancelled || processID !== lastInitRef) return;
@@ -207,6 +226,7 @@ const listenDocument = (callback, onError, builder, config) => {
207
226
  const [encPlate, [privateKey]] = uglify ? await serializeE2E({ _body: authObj }, mtoken, serverE2E_PublicKey) : ['', []];
208
227
 
209
228
  if (hasCancelled || processID !== lastInitRef) return;
229
+
210
230
  socket = io(`${wsPrefix}://${baseUrl}`, {
211
231
  transports: ['websocket', 'polling', 'flashsocket'],
212
232
  extraHeaders,
@@ -217,7 +237,8 @@ const listenDocument = (callback, onError, builder, config) => {
217
237
  },
218
238
  _m_internal: true,
219
239
  _m_route: (findOne ? _listenDocument : _listenCollection)(uglify)
220
- }
240
+ },
241
+ reconnection: false
221
242
  });
222
243
 
223
244
  socket.on('mSnapshot', async ([err, snapshot]) => {
@@ -234,18 +255,71 @@ const listenDocument = (callback, onError, builder, config) => {
234
255
  if (shouldCache) insertRecord(builder, config, await accessId, snapshot, episode);
235
256
  }
236
257
  });
237
- };
238
258
 
239
- init();
259
+ const reconnect = (timeout) => {
260
+ if (processID !== lastInitRef || hasCancelled) return;
240
261
 
241
- const tokenListener = listenToken(t => {
242
- if ((t || null) !== lastToken) {
243
- socket?.close?.();
244
- socket = undefined;
245
- init();
262
+ const reloadIntance = async () => {
263
+ if (processID !== lastInitRef && !hasCancelled) remountInit();
264
+ }
265
+
266
+ if (AppState.currentState === 'active') {
267
+ setTimeout(() => {
268
+ awaitReachableServer(projectUrl).then(reloadIntance);
269
+ }, timeout);
270
+ } else {
271
+ foregroundListener = AppState.addEventListener('change', s => {
272
+ if (s === 'active') {
273
+ clearForegroundListener();
274
+ reloadIntance();
275
+ }
276
+ });
277
+ }
246
278
  }
247
- lastToken = t || null;
248
- }, projectUrl);
279
+
280
+ let wasHandled;
281
+ socket.on('connect_error', () => {
282
+ if (processID !== lastInitRef || wasHandled) return;
283
+ wasHandled = true;
284
+ clearSocket();
285
+ reconnect(3000);
286
+ });
287
+
288
+ socket.on('disconnect', r => {
289
+ if (processID !== lastInitRef || wasHandled) return;
290
+ wasHandled = true;
291
+ clearSocket();
292
+ if (r === 'io client disconnect' || r === 'io server disconnect') return;
293
+ reconnect(0);
294
+ });
295
+ };
296
+
297
+ const remountInit = () => {
298
+ if (socket) {
299
+ ++lastInitRef;
300
+ clearSocket();
301
+ }
302
+ init();
303
+ }
304
+
305
+ let lastTokenStatus;
306
+ let tokenListener;
307
+
308
+ if (disableAuth) {
309
+ init();
310
+ } else {
311
+ tokenListener = listenTokenReady(ready => {
312
+ if (lastTokenStatus === (ready || false)) return;
313
+ if (ready) {
314
+ remountInit();
315
+ } else {
316
+ ++lastInitRef;
317
+ clearForegroundListener();
318
+ clearSocket();
319
+ }
320
+ lastTokenStatus = ready || false;
321
+ }, projectUrl);
322
+ }
249
323
 
250
324
  return () => {
251
325
  if (hasCancelled) return;
@@ -253,7 +327,8 @@ const listenDocument = (callback, onError, builder, config) => {
253
327
  connectedListener?.();
254
328
  cacheListener?.();
255
329
  tokenListener?.();
256
- if (socket) socket.close();
330
+ clearForegroundListener();
331
+ clearSocket();
257
332
  }
258
333
  };
259
334
 
@@ -279,18 +354,29 @@ const initOnDisconnectionTask = ({ builder, connectData, disconnectData }) => {
279
354
  }
280
355
  });
281
356
 
282
- let hasCancelled,
283
- /**
284
- * @type {import('socket.io-client').Socket}
285
- */
286
- socket,
287
- lastToken = Scoped.AuthJWTToken[projectUrl] || null,
357
+ /**
358
+ * @type {import('socket.io-client').Socket}
359
+ */
360
+ let socket,
361
+ hasCancelled,
288
362
  lastInitRef = 0;
289
363
 
364
+ let foregroundListener;
365
+ const clearForegroundListener = () => {
366
+ if (!foregroundListener) return;
367
+ foregroundListener.remove();
368
+ foregroundListener = undefined;
369
+ }
370
+
371
+ const clearSocket = () => {
372
+ if (socket) {
373
+ socket.close();
374
+ socket = undefined;
375
+ }
376
+ }
377
+
290
378
  const init = async () => {
291
379
  const processID = ++lastInitRef;
292
- if (!disableAuth) await awaitRefreshToken(projectUrl);
293
- if (hasCancelled || processID !== lastInitRef) return;
294
380
 
295
381
  const mtoken = disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl];
296
382
  const makeObj = (d) => ({
@@ -307,39 +393,95 @@ const initOnDisconnectionTask = ({ builder, connectData, disconnectData }) => {
307
393
  ...dbUrl ? { dbUrl } : undefined
308
394
  };
309
395
 
396
+ const uglifyData = uglify && (await serializeE2E({ _body: authObj }, mtoken, serverE2E_PublicKey))[0].toString('base64');
397
+
398
+ if (hasCancelled || processID !== lastInitRef) return;
399
+
310
400
  socket = io(`${wsPrefix}://${baseUrl}`, {
311
401
  transports: ['websocket', 'polling', 'flashsocket'],
312
402
  extraHeaders,
313
403
  auth: {
314
- ...uglify ? {
315
- e2e: (await serializeE2E({ _body: authObj }, mtoken, serverE2E_PublicKey))[0].toString('base64')
316
- } : {
404
+ ...uglify ? { e2e: uglifyData } : {
317
405
  ...mtoken ? { mtoken } : {},
318
406
  _body: authObj
319
407
  },
320
408
  _m_internal: true,
321
409
  _m_route: _startDisconnectWriteTask(uglify)
410
+ },
411
+ reconnection: false
412
+ });
413
+
414
+ const reconnect = (timeout) => {
415
+ if (processID !== lastInitRef || hasCancelled) return;
416
+
417
+ const reloadIntance = async () => {
418
+ if (!disableAuth) await awaitRefreshToken(projectUrl);
419
+ if (processID !== lastInitRef && !hasCancelled) remountInit();
322
420
  }
421
+
422
+ if (AppState.currentState === 'active') {
423
+ setTimeout(() => {
424
+ awaitReachableServer(projectUrl).then(reloadIntance);
425
+ }, timeout);
426
+ } else {
427
+ foregroundListener = AppState.addEventListener('change', s => {
428
+ if (s === 'active') {
429
+ clearForegroundListener();
430
+ reloadIntance();
431
+ }
432
+ });
433
+ }
434
+ }
435
+
436
+ let wasHandled;
437
+ socket.on('connect_error', () => {
438
+ if (processID !== lastInitRef || wasHandled) return;
439
+ wasHandled = true;
440
+ clearSocket();
441
+ reconnect(3000);
323
442
  });
324
- };
325
443
 
326
- init();
444
+ socket.on('disconnect', r => {
445
+ if (processID !== lastInitRef || wasHandled) return;
446
+ wasHandled = true;
447
+ clearSocket();
448
+ if (r === 'io client disconnect' || r === 'io server disconnect') return;
449
+ reconnect(0);
450
+ });
451
+ };
327
452
 
328
- const tokenListener = disableAuth ? undefined : listenToken(async t => {
329
- if ((t || null) !== lastToken) {
330
- if (socket) {
331
- socket.close();
332
- socket = undefined;
333
- setTimeout(init, 500);
334
- } else init();
453
+ const remountInit = () => {
454
+ if (socket) {
455
+ ++lastInitRef;
456
+ clearSocket();
335
457
  }
336
- lastToken = t;
337
- }, projectUrl);
458
+ init();
459
+ }
460
+
461
+ let lastTokenStatus;
462
+ let tokenListener;
463
+
464
+ if (disableAuth) {
465
+ init();
466
+ } else {
467
+ tokenListener = listenTokenReady(ready => {
468
+ if (lastTokenStatus === (ready || false)) return;
469
+ if (ready) {
470
+ remountInit();
471
+ } else {
472
+ ++lastInitRef;
473
+ clearForegroundListener();
474
+ clearSocket();
475
+ }
476
+ lastTokenStatus = ready || false;
477
+ }, projectUrl);
478
+ }
338
479
 
339
480
  return () => {
340
481
  if (hasCancelled) return;
341
482
  hasCancelled = true;
342
483
  tokenListener?.();
484
+ clearForegroundListener();
343
485
  if (socket) {
344
486
  const thisSocket = socket;
345
487
  return niceTry(() => thisSocket.timeout(5000).emitWithAck(_cancelDisconnectWriteTask(uglify))).finally(() => {
@@ -184,7 +184,9 @@ export const mfetch = async (input = '', init, config) => {
184
184
  )
185
185
  };
186
186
 
187
- if (shouldCache) insertFetchResources(projectUrl, reqId, resObj);
187
+ if (shouldCache) {
188
+ if (status === 200) insertFetchResources(projectUrl, reqId, resObj);
189
+ }
188
190
 
189
191
  finalize(resObj);
190
192
  } catch (e) {