react-native-mosquito-transport 0.0.48 → 0.0.50

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 (96) hide show
  1. package/CONTRIBUTING.md +37 -19
  2. package/MosquitoTransport.podspec +20 -0
  3. package/android/build.gradle +32 -42
  4. package/android/src/main/AndroidManifest.xml +1 -3
  5. package/android/src/main/java/com/mosquitotransport/MosquitoTransportModule.kt +101 -0
  6. package/android/src/main/java/com/mosquitotransport/MosquitoTransportPackage.kt +31 -0
  7. package/android/src/main/java/com/mosquitotransport/utils/FileUploader.kt +106 -0
  8. package/android/src/main/java/com/mosquitotransport/utils/UploadCallback.kt +7 -0
  9. package/ios/MosquitoTransport-Bridging-Header.h +1 -0
  10. package/ios/MosquitoTransport.h +6 -0
  11. package/ios/MosquitoTransport.mm +81 -0
  12. package/ios/{Mosquitodb.swift → MosquitoTransport.swift} +39 -42
  13. package/package.json +10 -2
  14. package/src/NativeMosquitoTransport.js +24 -0
  15. package/src/helpers/fs_manager.js +1 -1
  16. package/src/helpers/utils.js +2 -4
  17. package/src/helpers/variables.js +2 -1
  18. package/src/index.d.ts +1 -1
  19. package/src/index.js +15 -10
  20. package/src/products/auth/accessor.js +97 -76
  21. package/src/products/auth/index.js +7 -6
  22. package/src/products/database/index.js +1 -0
  23. package/src/products/storage/index.js +9 -23
  24. package/.jshintignore +0 -4
  25. package/.jshintrc +0 -16
  26. package/TODO +0 -35
  27. package/android/gradle.properties +0 -5
  28. package/android/src/main/java/com/mosquitodb/MosquitodbModule.java +0 -82
  29. package/android/src/main/java/com/mosquitodb/MosquitodbPackage.java +0 -28
  30. package/android/src/main/java/com/mosquitodb/utils/FileUploader.java +0 -101
  31. package/android/src/main/java/com/mosquitodb/utils/UploadCallback.java +0 -7
  32. package/example/.bundle/config +0 -2
  33. package/example/.node-version +0 -1
  34. package/example/.watchmanconfig +0 -1
  35. package/example/Gemfile +0 -6
  36. package/example/android/app/build.gradle +0 -170
  37. package/example/android/app/debug.keystore +0 -0
  38. package/example/android/app/proguard-rules.pro +0 -10
  39. package/example/android/app/src/debug/AndroidManifest.xml +0 -13
  40. package/example/android/app/src/debug/java/com/mosquitodbexample/ReactNativeFlipper.java +0 -75
  41. package/example/android/app/src/main/AndroidManifest.xml +0 -25
  42. package/example/android/app/src/main/java/com/mosquitodbexample/MainActivity.java +0 -35
  43. package/example/android/app/src/main/java/com/mosquitodbexample/MainApplication.java +0 -62
  44. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -36
  45. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  46. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  47. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  48. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  49. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  50. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  51. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  52. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  53. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  54. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  55. package/example/android/app/src/main/res/values/strings.xml +0 -3
  56. package/example/android/app/src/main/res/values/styles.xml +0 -9
  57. package/example/android/app/src/release/java/com/mosquitodbexample/ReactNativeFlipper.java +0 -20
  58. package/example/android/build.gradle +0 -21
  59. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  60. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -5
  61. package/example/android/gradle.properties +0 -44
  62. package/example/android/gradlew +0 -234
  63. package/example/android/gradlew.bat +0 -89
  64. package/example/android/settings.gradle +0 -4
  65. package/example/app.json +0 -4
  66. package/example/babel.config.js +0 -17
  67. package/example/index.js +0 -5
  68. package/example/ios/.xcode.env +0 -11
  69. package/example/ios/File.swift +0 -6
  70. package/example/ios/MosquitodbExample/AppDelegate.h +0 -6
  71. package/example/ios/MosquitodbExample/AppDelegate.mm +0 -36
  72. package/example/ios/MosquitodbExample/Images.xcassets/AppIcon.appiconset/Contents.json +0 -53
  73. package/example/ios/MosquitodbExample/Images.xcassets/Contents.json +0 -6
  74. package/example/ios/MosquitodbExample/Info.plist +0 -55
  75. package/example/ios/MosquitodbExample/LaunchScreen.storyboard +0 -47
  76. package/example/ios/MosquitodbExample/main.m +0 -10
  77. package/example/ios/MosquitodbExample-Bridging-Header.h +0 -3
  78. package/example/ios/MosquitodbExample.xcodeproj/project.pbxproj +0 -703
  79. package/example/ios/MosquitodbExample.xcodeproj/xcshareddata/xcschemes/MosquitodbExample.xcscheme +0 -88
  80. package/example/ios/MosquitodbExample.xcworkspace/contents.xcworkspacedata +0 -10
  81. package/example/ios/MosquitodbExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  82. package/example/ios/MosquitodbExample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +0 -8
  83. package/example/ios/MosquitodbExample.xcworkspace/xcuserdata/anthony.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  84. package/example/ios/MosquitodbExample.xcworkspace/xcuserdata/anthony.xcuserdatad/WorkspaceSettings.xcsettings +0 -14
  85. package/example/ios/MosquitodbExampleTests/Info.plist +0 -24
  86. package/example/ios/MosquitodbExampleTests/MosquitodbExampleTests.m +0 -66
  87. package/example/ios/Podfile +0 -60
  88. package/example/metro.config.js +0 -40
  89. package/example/package.json +0 -22
  90. package/example/react-native.config.js +0 -10
  91. package/example/src/App.tsx +0 -31
  92. package/ios/Mosquitodb-Bridging-Header.h +0 -2
  93. package/ios/Mosquitodb.m +0 -22
  94. package/ios/Mosquitodb.xcodeproj/project.pbxproj +0 -283
  95. package/ios/Mosquitodb.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -4
  96. package/react-native-mosquitodb.podspec +0 -35
@@ -1,52 +1,34 @@
1
1
  import React
2
2
 
3
- @objc(Mosquitodb)
4
- class Mosquitodb: RCTEventEmitter, URLSessionDataDelegate {
3
+ @objc
4
+ public protocol NativeMosquitoTransportImplDelegate {
5
+ func sendEvent(name: String, body: Any)
6
+ }
7
+
8
+ @objc(NativeMosquitoTransportImpl)
9
+ public class NativeMosquitoTransportImpl: NSObject, URLSessionDataDelegate {
10
+ // Add property for the Objective-C bridge
11
+ @objc public weak var delegate: NativeMosquitoTransportImplDelegate? = nil
5
12
 
6
13
  public override init() {
7
14
  super.init()
8
15
  }
9
-
10
- override public static func requiresMainQueueSetup() -> Bool {
11
- return true;
12
- }
13
-
14
- @objc(supportedEvents)
15
- override public func supportedEvents() -> [String] {
16
- return [
17
- "mt-uploading-progress",
18
- "mt-uploading-status",
19
- "mt-download-progress",
20
- "mt-download-status"
21
- ]
16
+
17
+ private func emitNewEvent(name: String, body: Any? = nil) {
18
+ delegate?.sendEvent(name: name, body: body ?? [])
22
19
  }
23
20
 
24
- var uploadTask: [String: MosquitodbUploadTask] = [:]
25
- var downloadTask: [String: MosquitodbDownloadTask] = [:]
26
-
27
- @objc(downloadFile:)
28
- func downloadFile(options: [String: Any]) -> Void {
29
- let processID = options["processID"] as! String
30
- downloadTask[processID] = MosquitodbDownloadTask()
31
- downloadTask[processID]?.downloadFile(options: options, completion: { res in
32
- let status = res![0] as? String
33
-
34
- self.sendEvent(withName: status, body: res![1])
35
- if status == "mt-download-status" {
36
- self.downloadTask.removeValue(forKey: processID)
37
- }
38
- })
39
- }
21
+ var uploadTask: [String: MosquitoTransportUploadTask] = [:]
40
22
 
41
23
  @objc(uploadFile:)
42
- func uploadFile(options: [String: Any]) -> Void {
24
+ public func uploadFile(options: [String: Any]) -> Void {
43
25
  let processID = options["processID"] as! String
44
26
 
45
- uploadTask[processID] = MosquitodbUploadTask()
27
+ uploadTask[processID] = MosquitoTransportUploadTask()
46
28
  uploadTask[processID]?.uploadFile(options: options, completion: { res in
47
29
  let status = res![0] as? String
48
30
 
49
- self.sendEvent(withName: status, body: res![1])
31
+ self.emitNewEvent(name: status!, body: res![1])
50
32
  if status == "mt-uploading-status" {
51
33
  self.uploadTask.removeValue(forKey: processID)
52
34
  }
@@ -54,28 +36,43 @@ class Mosquitodb: RCTEventEmitter, URLSessionDataDelegate {
54
36
  }
55
37
 
56
38
  @objc(cancelUpload:)
57
- func cancelUpload (processID: String)-> Void {
39
+ public func cancelUpload (processID: String)-> Void {
58
40
  uploadTask[processID]?.cancelUpload()
59
41
  }
60
42
 
43
+ var downloadTask: [String: MosquitoTransportDownloadTask] = [:]
44
+
45
+ @objc(downloadFile:)
46
+ public func downloadFile(options: [String: Any]) -> Void {
47
+ let processID = options["processID"] as! String
48
+ downloadTask[processID] = MosquitoTransportDownloadTask()
49
+ downloadTask[processID]?.downloadFile(options: options, completion: { res in
50
+ let status = res![0] as? String
51
+
52
+ self.emitNewEvent(name: status!, body: res![1])
53
+ if status == "mt-download-status" {
54
+ self.downloadTask.removeValue(forKey: processID)
55
+ }
56
+ })
57
+ }
58
+
61
59
  @objc(cancelDownload:)
62
- func cancelDownload (processID: String)-> Void {
60
+ public func cancelDownload (processID: String)-> Void {
63
61
  downloadTask[processID]?.cancelDownload()
64
62
  }
65
63
 
66
64
  @objc(pauseDownload:)
67
- func pauseDownload (processID: String)-> Void {
65
+ public func pauseDownload (processID: String)-> Void {
68
66
  downloadTask[processID]?.pauseDownload()
69
67
  }
70
68
 
71
69
  @objc(resumeDownload:)
72
- func resumeDownload (processID: String)-> Void {
70
+ public func resumeDownload (processID: String)-> Void {
73
71
  downloadTask[processID]?.resumeDownload()
74
72
  }
75
73
  }
76
74
 
77
-
78
- class MosquitodbUploadTask: NSObject, URLSessionDataDelegate {
75
+ class MosquitoTransportUploadTask: NSObject, URLSessionDataDelegate {
79
76
 
80
77
  var mainProcessID: String = ""
81
78
  var mainTask : URLSessionUploadTask? = nil
@@ -177,7 +174,7 @@ class MosquitodbUploadTask: NSObject, URLSessionDataDelegate {
177
174
  }
178
175
 
179
176
 
180
- class MosquitodbDownloadTask: NSObject, URLSessionDownloadDelegate {
177
+ class MosquitoTransportDownloadTask: NSObject, URLSessionDownloadDelegate {
181
178
 
182
179
  var mainProcessID: String = ""
183
180
  var mainTask : URLSessionDownloadTask? = nil
@@ -208,7 +205,7 @@ class MosquitodbDownloadTask: NSObject, URLSessionDownloadDelegate {
208
205
  mainOptions = options
209
206
  trigger = completion
210
207
  task.resume()
211
- print("MosquitodbDownloadTask started:\(mainProcessID)")
208
+ print("MosquitoTransportDownloadTask started:\(mainProcessID)")
212
209
  }
213
210
 
214
211
  func cancelDownload ()-> Void {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mosquito-transport",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
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",
@@ -26,6 +26,14 @@
26
26
  "url": "https://github.com/brainbehindx/react-native-mosquito-transport/issues"
27
27
  },
28
28
  "homepage": "https://github.com/brainbehindx/react-native-mosquito-transport#readme",
29
+ "codegenConfig": {
30
+ "name": "MosquitoTransportSpec",
31
+ "type": "modules",
32
+ "jsSrcsDir": "src",
33
+ "android": {
34
+ "javaPackageName": "com.mosquitotransport"
35
+ }
36
+ },
29
37
  "dependencies": {
30
38
  "@turf/turf": "^7.2.0",
31
39
  "buffer": "^6.0.3",
@@ -43,4 +51,4 @@
43
51
  "react-native-get-random-values": "*",
44
52
  "react-native-sha256": "*"
45
53
  }
46
- }
54
+ }
@@ -0,0 +1,24 @@
1
+ // @flow
2
+ import type { TurboModule } from 'react-native';
3
+ import { type UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
4
+ import { TurboModuleRegistry } from 'react-native';
5
+
6
+ export interface Spec extends TurboModule {
7
+ // storage
8
+ uploadFile(option: UnsafeObject): void;
9
+ cancelUpload(process_id: string): void;
10
+ downloadFile(option: UnsafeObject): void;
11
+ cancelDownload(process_id: string): void;
12
+ pauseDownload(process_id: string): void;
13
+ resumeDownload(process_id: string): void;
14
+
15
+ // utils
16
+ getSystemUptime(): Promise<number>;
17
+
18
+ // event listeners
19
+ // readonly onMessage?: EventEmitter<{ message: string }>;
20
+ addListener(eventName: string): void;
21
+ removeListeners(count: number): void;
22
+ }
23
+
24
+ export default TurboModuleRegistry.getEnforcing<Spec>('MosquitoTransport');
@@ -4,7 +4,7 @@ import { Platform } from "react-native";
4
4
  import { Dirs, FileSystem } from "react-native-file-access";
5
5
  import { Buffer } from "buffer";
6
6
 
7
- const PARENT_FOLDER = `${Platform.OS === 'android' ? Dirs.DocumentDir.split('/').slice(0, -1).join('/') : Dirs.MainBundleDir}/mosquito_base`;
7
+ const PARENT_FOLDER = `${Platform.OS === 'android' ? Dirs.DocumentDir.split('/').slice(0, -1).join('/') : Dirs.LibraryDir}/mosquito_base`;
8
8
 
9
9
  /**
10
10
  * this method linearize read/write for individual access_id on the file system ensuring consistency across concurrent operations
@@ -134,21 +134,19 @@ export const releaseCacheStore = async (builder) => {
134
134
  Scoped.AuthJWTToken[key] = value?.token;
135
135
  });
136
136
  Scoped.IsStoreReady = true;
137
- StoreReadyListener.dispatchPersist('_', 'ready');
137
+ StoreReadyListener.dispatchPersist('_', true);
138
138
  setTimeout(() => {
139
139
  if (tobePurged.length) updateCacheStore(tobePurged);
140
140
  }, 0);
141
141
  };
142
142
 
143
- export const getPrefferTime = () => Date.now() + (Scoped.serverTimeOffset || 0);
144
-
145
143
  export const awaitStore = () => new Promise(resolve => {
146
144
  if (Scoped.IsStoreReady) {
147
145
  resolve();
148
146
  return;
149
147
  }
150
148
  const l = StoreReadyListener.listenToPersist('_', t => {
151
- if (t === 'ready') {
149
+ if (t) {
152
150
  resolve();
153
151
  l();
154
152
  }
@@ -10,7 +10,8 @@ export const Scoped = {
10
10
  AuthJWTToken: {},
11
11
  IsStoreReady: false,
12
12
  TokenRefreshTimer: {},
13
- LastTokenRefreshRef: {},
13
+ TokenRefreshProcess: {},
14
+ TokenTimestamping: {},
14
15
  StorageProcessID: 0,
15
16
  InitiatedForcedToken: {},
16
17
  PendingFetchCollective: {},
package/src/index.d.ts CHANGED
@@ -347,7 +347,7 @@ interface GetConfig {
347
347
  retrieval?: Retrieval;
348
348
  /**
349
349
  * - 0: returns data that may have been internally updated locally with updateOne, updateMany, mergeOne, deleteOne, deleteMany, putOne, replaceOne
350
- * - 1: returns exact data which was cached in the last query process
350
+ * - 1: returns exact remote data which was cached in the last query process
351
351
  *
352
352
  * @defaults - 0
353
353
  *
package/src/index.js CHANGED
@@ -16,6 +16,7 @@ import { Buffer } from 'buffer';
16
16
  import MTAuth, { purgePendingToken } from './products/auth';
17
17
  import { BSON } from "./vendor/bson";
18
18
  import { basicClone } from './helpers/basic_clone';
19
+ import { AppState } from "react-native";
19
20
 
20
21
  const {
21
22
  _listenCollection,
@@ -59,11 +60,10 @@ class RNMT {
59
60
 
60
61
  if (!Scoped.InitializedProject[projectUrl]) {
61
62
  Scoped.InitializedProject[projectUrl] = basicClone(this.config);
62
- Scoped.LastTokenRefreshRef[projectUrl] = 0;
63
63
  triggerAuthToken(projectUrl);
64
- initTokenRefresher({ ...this.config }, true);
64
+ initTokenRefresher({ config: this.config, forceRefresh: true });
65
65
 
66
- let isConnected, recentToken;
66
+ let isConnected, recentToken, isVirtualMachineFocused = true;
67
67
 
68
68
  const socket = io(`${this.config.wsPrefix}://${this.config.baseUrl}`, {
69
69
  transports: ['websocket', 'polling', 'flashsocket'],
@@ -86,9 +86,9 @@ class RNMT {
86
86
  };
87
87
  const onDisconnect = () => {
88
88
  ++connectionIte;
89
- isConnected = false;
90
- Scoped.IS_CONNECTED[projectUrl] = false;
91
- ServerReachableListener.dispatchPersist(projectUrl, false);
89
+ isConnected = isVirtualMachineFocused ? false : null;
90
+ Scoped.IS_CONNECTED[projectUrl] = isConnected;
91
+ ServerReachableListener.dispatchPersist(projectUrl, isConnected);
92
92
  }
93
93
 
94
94
  const manualCheckConnection = () => {
@@ -112,6 +112,11 @@ class RNMT {
112
112
  manualCheckConnection();
113
113
  });
114
114
 
115
+ AppState.addEventListener('change', (s) => {
116
+ isVirtualMachineFocused = s === 'active';
117
+ manualCheckConnection();
118
+ });
119
+
115
120
  const updateMountedToken = () => {
116
121
  socket.emit('_update_mounted_user', recentToken || null);
117
122
  };
@@ -308,7 +313,7 @@ class RNMT {
308
313
  tokenListener = listenTokenReady(status => {
309
314
  if (lastTokenStatus === (status || false)) return;
310
315
 
311
- if (status === 'ready') {
316
+ if (status) {
312
317
  init();
313
318
  } else {
314
319
  socket?.close?.();
@@ -448,7 +453,7 @@ const ConfigValidator = {
448
453
  if (v.endsWith('/')) throw '"projectUrl" must not end with a trailing slash "/"';
449
454
  },
450
455
  disableCache: (v) => {
451
- if (typeof v !== 'boolean')
456
+ if (typeof v !== 'boolean' && v !== undefined)
452
457
  throw `Invalid value supplied to disableCache, value must be a boolean`;
453
458
  },
454
459
  maxRetries: (v) => {
@@ -456,7 +461,7 @@ const ConfigValidator = {
456
461
  throw `Invalid value supplied to maxRetries, value must be positive integer greater than zero`;
457
462
  },
458
463
  enableE2E_Encryption: (v) => {
459
- if (typeof v !== 'boolean')
464
+ if (typeof v !== 'boolean' && v !== undefined)
460
465
  throw `Invalid value supplied to enableE2E_Encryption, value must be a boolean`;
461
466
  },
462
467
  castBSON: v => {
@@ -468,7 +473,7 @@ const ConfigValidator = {
468
473
  throw `Expected "borrowToken" to be valid https or http link but got "${v}"`;
469
474
  },
470
475
  serverE2E_PublicKey: (v) => {
471
- if (typeof v !== 'string' || !v.trim())
476
+ if (v !== undefined && (typeof v !== 'string' || !v.trim()))
472
477
  throw `Invalid value supplied to serverETE_PublicKey, value must be a non-empty string`;
473
478
  },
474
479
  extraHeaders: v => {
@@ -2,11 +2,12 @@ import { doSignOut, revokeAuthIntance } from "./index.js";
2
2
  import EngineApi from "../../helpers/engine_api";
3
3
  import { AuthTokenListener, TokenRefreshListener } from "../../helpers/listeners";
4
4
  import { decodeBinary, deserializeE2E, listenReachableServer } from "../../helpers/peripherals";
5
- import { awaitStore, buildFetchInterface, buildFetchResult, getPrefferTime, updateCacheStore } from "../../helpers/utils";
5
+ import { awaitStore, buildFetchInterface, buildFetchResult, updateCacheStore } from "../../helpers/utils";
6
6
  import { CacheStore, Scoped } from "../../helpers/variables";
7
7
  import { simplifyError } from "simplify-error";
8
8
  import { Validator } from "guard-object";
9
9
  import { basicClone } from "../../helpers/basic_clone";
10
+ import NativeMosquitoTransport from "../../NativeMosquitoTransport.js";
10
11
 
11
12
  export const listenToken = (callback, projectUrl) =>
12
13
  AuthTokenListener.listenToPersist(projectUrl, (t, n) => {
@@ -17,16 +18,16 @@ export const listenToken = (callback, projectUrl) =>
17
18
  export const injectFreshToken = async (config, { token, refreshToken }) => {
18
19
  const { projectUrl } = config;
19
20
 
20
- await awaitStore();
21
21
  CacheStore.AuthStore[projectUrl] = { token, refreshToken };
22
22
  Scoped.AuthJWTToken[projectUrl] = token;
23
23
  const isEmulated = projectUrl in CacheStore.EmulatedAuth;
24
24
  if (isEmulated) delete CacheStore.EmulatedAuth[projectUrl];
25
+ await updateTokenTimestamp(projectUrl, token);
25
26
 
26
27
  updateCacheStore(['AuthStore', isEmulated ? 'EmulatedAuth' : ''].filter(v => v));
27
28
 
28
29
  triggerAuthToken(projectUrl);
29
- initTokenRefresher(config);
30
+ initTokenRefresher({ config });
30
31
  };
31
32
 
32
33
  export const injectEmulatedAuth = async (config, emulatedURL) => {
@@ -49,7 +50,7 @@ export const injectEmulatedAuth = async (config, emulatedURL) => {
49
50
 
50
51
  updateCacheStore(['AuthStore', 'EmulatedAuth']);
51
52
  triggerAuthToken(projectUrl);
52
- initTokenRefresher(config);
53
+ initTokenRefresher({ config });
53
54
  };
54
55
 
55
56
  export const parseToken = (token) => JSON.parse(decodeBinary(token.split('.')[1]));
@@ -59,28 +60,35 @@ export const triggerAuthToken = async (projectUrl, isInit) => {
59
60
  AuthTokenListener.dispatchPersist(projectUrl, CacheStore.AuthStore[projectUrl]?.token || null, isInit);
60
61
  };
61
62
 
62
- export const awaitRefreshToken = (projectUrl) => new Promise(resolve => {
63
- const l = TokenRefreshListener.listenToPersist(projectUrl, v => {
64
- if (v === 'ready') {
65
- l();
66
- resolve();
63
+ export const awaitRefreshToken = (projectUrl) =>
64
+ new Promise(async resolve => {
65
+ try {
66
+ if (await initTokenRefresher({ justCheck: true, config: Scoped.InitializedProject[projectUrl] })) {
67
+ resolve();
68
+ } else throw null;
69
+ } catch (_) {
70
+ const l = TokenRefreshListener.listenToPersist(projectUrl, v => {
71
+ if (v) {
72
+ l();
73
+ resolve();
74
+ }
75
+ });
67
76
  }
68
77
  });
69
- });
70
78
 
71
79
  export const listenTokenReady = (callback, projectUrl) => TokenRefreshListener.listenToPersist(projectUrl, callback);
72
80
 
73
- export const initTokenRefresher = async (config, forceRefresh) => {
81
+ export const initTokenRefresher = async ({ config, forceRefresh, justCheck }) => {
74
82
  const { projectUrl, maxRetries } = config;
75
83
  if (!Scoped.IsStoreReady) await awaitStore();
76
84
  const { token } = CacheStore.AuthStore[projectUrl] || {};
77
85
  const emulatedURL = CacheStore.EmulatedAuth[projectUrl];
78
- const tokenInfo = token && parseToken(token);
79
86
 
80
- clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
87
+ if (!justCheck) clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
81
88
  if (emulatedURL) return;
82
89
 
83
90
  const notifyAuthReady = (value) => {
91
+ if (justCheck) return;
84
92
  TokenRefreshListener.dispatchPersist(projectUrl, value);
85
93
  getEmulatedLinks(projectUrl).forEach(v => {
86
94
  TokenRefreshListener.dispatchPersist(v, value);
@@ -88,65 +96,84 @@ export const initTokenRefresher = async (config, forceRefresh) => {
88
96
  }
89
97
 
90
98
  if (token) {
91
- const expireOn = (tokenInfo.exp * 1000) - 60000;
92
- const hasExpire = getPrefferTime() >= expireOn;
93
- const rizz = () => refreshToken(config, ++Scoped.LastTokenRefreshRef[projectUrl], maxRetries, forceRefresh);
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
+ });
109
+ }
94
110
 
95
- if (hasExpire || forceRefresh) {
111
+ if (await hasTokenExpire(projectUrl) || forceRefresh) {
96
112
  notifyAuthReady();
97
113
  return rizz();
98
114
  } else {
99
- notifyAuthReady('ready');
100
- Scoped.TokenRefreshTimer[projectUrl] = setInterval(() => {
101
- const countdown = expireOn - getPrefferTime();
102
- if (countdown > 3000) return;
115
+ notifyAuthReady(true);
116
+ if (justCheck) {
117
+ return true;
118
+ } else {
119
+ let lastIte = 0;
103
120
  clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
104
- notifyAuthReady();
105
- rizz();
106
- }, 3000);
121
+ Scoped.TokenRefreshTimer[projectUrl] = setInterval(async () => {
122
+ const iteRef = ++lastIte;
123
+ if (iteRef !== lastIte || !(await hasTokenExpire(projectUrl))) return;
124
+ clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
125
+ notifyAuthReady();
126
+ rizz();
127
+ }, 9000);
128
+ }
107
129
  }
108
130
  } else {
109
- notifyAuthReady('ready');
110
- if (forceRefresh) {
111
- return simplifyError('no_token_yet', 'No token is available to initiate a refresh').simpleError;
112
- }
131
+ notifyAuthReady(true);
132
+ if (justCheck) return true;
113
133
  }
114
134
  };
115
135
 
136
+ const hasTokenExpire = async (projectUrl) => {
137
+ const timestamp = Scoped.TokenTimestamping[projectUrl];
138
+ if (!timestamp) return true;
139
+ const uptime = await NativeMosquitoTransport.getSystemUptime();
140
+
141
+ return timestamp.ttl <= (uptime - timestamp.uptime);
142
+ }
143
+
144
+ const updateTokenTimestamp = async (projectUrl, token) => {
145
+ const { exp, iat } = parseToken(token);
146
+ Scoped.TokenTimestamping[projectUrl] = {
147
+ ttl: ((exp * 1000) - (iat * 1000)) - 60_000,
148
+ uptime: await NativeMosquitoTransport.getSystemUptime()
149
+ };
150
+ }
151
+
116
152
  export const getEmulatedLinks = (projectUrl) => Object.entries(CacheStore.EmulatedAuth)
117
153
  .filter(([_, v]) => v === projectUrl)
118
154
  .map(v => v[0]);
119
155
 
120
- const refreshToken = (builder, processRef, remainRetries = 1, isForceRefresh) => new Promise(async (resolve, reject) => {
121
- const { projectUrl, serverE2E_PublicKey, uglify, extraHeaders } = builder;
122
- const lostProcess = simplifyError('process_lost', 'The token refresh process has been lost and replaced with another one');
156
+ const refreshToken = (builder, remainRetries = 1, isForceRefresh) =>
157
+ new Promise(async (resolve, reject) => {
158
+ const { projectUrl, serverE2E_PublicKey, uglify, extraHeaders } = builder;
123
159
 
124
- try {
125
- const { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
126
-
127
- const [reqBuilder, [privateKey]] = await buildFetchInterface({
128
- body: { token, r_token },
129
- uglify,
130
- serverE2E_PublicKey,
131
- extraHeaders
132
- });
160
+ try {
161
+ const { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
133
162
 
134
- let data;
163
+ const [reqBuilder, [privateKey]] = await buildFetchInterface({
164
+ body: { token, r_token },
165
+ uglify,
166
+ serverE2E_PublicKey,
167
+ extraHeaders
168
+ });
135
169
 
136
- try {
137
- data = await buildFetchResult(await fetch(EngineApi._refreshAuthToken(projectUrl, uglify), reqBuilder), uglify);
138
- } finally {
139
- if (processRef !== Scoped.LastTokenRefreshRef[projectUrl]) {
140
- reject(lostProcess.simpleError);
141
- return;
142
- }
143
- }
170
+ const data = await buildFetchResult(await fetch(EngineApi._refreshAuthToken(projectUrl, uglify), reqBuilder), uglify);
144
171
 
145
- const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
172
+ const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
146
173
 
147
- if (CacheStore.AuthStore[projectUrl]) {
148
174
  CacheStore.AuthStore[projectUrl].token = f.result.token;
149
175
  Scoped.AuthJWTToken[projectUrl] = f.result.token;
176
+ await updateTokenTimestamp(projectUrl, f.result.token);
150
177
 
151
178
  resolve(f.result.token);
152
179
  const isInit = !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh;
@@ -162,30 +189,24 @@ const refreshToken = (builder, processRef, remainRetries = 1, isForceRefresh) =>
162
189
  if (isForceRefresh) Scoped.InitiatedForcedToken[v] = true;
163
190
  });
164
191
  updateCacheStore(['AuthStore']);
165
- initTokenRefresher(builder);
166
- } else reject(lostProcess.simpleError);
167
- } catch (e) {
168
- if (e.simpleError) {
169
- console.error(`refreshToken error: ${e.simpleError?.message}`);
170
- doSignOut({ ...builder });
171
- reject(e.simpleError);
172
- } else if (remainRetries <= 0) {
173
- reject(
174
- processRef === Scoped.LastTokenRefreshRef[projectUrl] ?
175
- lostProcess.simpleError :
192
+ initTokenRefresher({ config: builder });
193
+ } catch (e) {
194
+ if (e.simpleError) {
195
+ console.error(`refreshToken error: ${e.simpleError?.message}`);
196
+ doSignOut({ ...builder });
197
+ reject(e.simpleError);
198
+ } else if (remainRetries <= 0) {
199
+ reject(
176
200
  simplifyError('retry_limit_reached', 'The retry limit has been reach and execution prematurely stopped').simpleError
177
- );
178
- console.error(`refreshToken retry limit exceeded`);
179
- } else {
180
- const l = listenReachableServer(c => {
181
- if (processRef !== Scoped.LastTokenRefreshRef[projectUrl]) {
182
- reject(lostProcess.simpleError);
183
- l();
184
- } else if (c) {
185
- l();
186
- refreshToken(builder, processRef, remainRetries - 1, isForceRefresh).then(resolve, reject);
187
- }
188
- }, projectUrl);
201
+ );
202
+ console.error(`refreshToken retry limit exceeded err:`, e);
203
+ } else {
204
+ const l = listenReachableServer(c => {
205
+ if (c) {
206
+ l();
207
+ refreshToken(builder, remainRetries - 1, isForceRefresh).then(resolve, reject);
208
+ }
209
+ }, projectUrl);
210
+ }
189
211
  }
190
- }
191
- });
212
+ });
@@ -59,10 +59,11 @@ export default class MTAuth {
59
59
  onError?.(simplifyError('user_login_required', 'You must be signed-in to use this method').simpleError);
60
60
  return;
61
61
  }
62
- if (processID !== lastInitRef) return;
62
+ if (processID !== lastInitRef || hasCancelled) return;
63
63
  const mtoken = Scoped.AuthJWTToken[projectUrl],
64
64
  [reqBuilder, [privateKey]] = uglify ? await serializeE2E({ mtoken }, undefined, serverE2E_PublicKey) : [null, []];
65
65
 
66
+ if (processID !== lastInitRef || hasCancelled) return;
66
67
  socket = io(`${wsPrefix}://${baseUrl}`, {
67
68
  transports: ['websocket', 'polling', 'flashsocket'],
68
69
  auth: {
@@ -159,7 +160,7 @@ export default class MTAuth {
159
160
 
160
161
  signOut = () => doSignOut(this.builder);
161
162
 
162
- forceRefreshToken = () => initTokenRefresher(this.builder, true);
163
+ forceRefreshToken = () => initTokenRefresher({ config: this.builder, forceRefresh: true });
163
164
 
164
165
  emulate = async (projectUrl) => {
165
166
  await injectEmulatedAuth(this.builder, projectUrl);
@@ -189,8 +190,8 @@ const doCustomSignin = (builder, email, password) => new Promise(async (resolve,
189
190
  token: r.result.token,
190
191
  refreshToken: r.result.refreshToken
191
192
  });
192
- await injectFreshToken(builder, r.result);
193
193
  revokeAuthIntance(builder, thisAuthStore);
194
+ await injectFreshToken(builder, r.result);
194
195
  } catch (e) {
195
196
  reject(simplifyCaughtError(e).simpleError);
196
197
  }
@@ -223,8 +224,8 @@ const doCustomSignup = (builder, email, password, name, metadata) => new Promise
223
224
  refreshToken: r.result.refreshToken,
224
225
  isNewUser: !!r.result.isNewUser
225
226
  });
226
- await injectFreshToken(builder, r.result);
227
227
  revokeAuthIntance(builder, thisAuthStore);
228
+ await injectFreshToken(builder, r.result);
228
229
  } catch (e) {
229
230
  reject(simplifyCaughtError(e).simpleError);
230
231
  }
@@ -248,7 +249,7 @@ const clearCacheForSignout = (builder, disposeEmulated) => {
248
249
 
249
250
  purgeCache(projectUrl, true);
250
251
  if (disposeEmulated) getEmulatedLinks(projectUrl).forEach(e => purgeCache(e));
251
- initTokenRefresher(builder);
252
+ initTokenRefresher({ config: builder });
252
253
  };
253
254
 
254
255
  export const doSignOut = async (builder) => {
@@ -335,8 +336,8 @@ const doProviderSignin = (builder, token, metadata, endpointer) => new Promise(a
335
336
  refreshToken: f.result.refreshToken,
336
337
  isNewUser: f.result.isNewUser
337
338
  });
338
- await injectFreshToken(builder, f.result);
339
339
  revokeAuthIntance(builder, thisAuthStore);
340
+ await injectFreshToken(builder, f.result);
340
341
  } catch (e) {
341
342
  reject(simplifyCaughtError(e).simpleError);
342
343
  }
@@ -206,6 +206,7 @@ const listenDocument = (callback, onError, builder, config) => {
206
206
 
207
207
  const [encPlate, [privateKey]] = uglify ? await serializeE2E({ _body: authObj }, mtoken, serverE2E_PublicKey) : ['', []];
208
208
 
209
+ if (hasCancelled || processID !== lastInitRef) return;
209
210
  socket = io(`${wsPrefix}://${baseUrl}`, {
210
211
  transports: ['websocket', 'polling', 'flashsocket'],
211
212
  extraHeaders,