react-native-mosquito-transport 0.0.48 → 0.0.49

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 +118 -94
  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.49",
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,104 +96,120 @@ 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
+ }, 7000);
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
 
116
- export const getEmulatedLinks = (projectUrl) => Object.entries(CacheStore.EmulatedAuth)
117
- .filter(([_, v]) => v === projectUrl)
118
- .map(v => v[0]);
136
+ const hasTokenExpire = async (projectUrl) => {
137
+ const timestamp = Scoped.TokenTimestamping[projectUrl];
138
+ if (!timestamp) return true;
139
+ const uptime = await NativeMosquitoTransport.getSystemUptime();
119
140
 
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');
141
+ return timestamp.ttl <= (uptime - timestamp.uptime);
142
+ }
123
143
 
124
- try {
125
- const { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
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
+ }
126
151
 
127
- const [reqBuilder, [privateKey]] = await buildFetchInterface({
128
- body: { token, r_token },
129
- uglify,
130
- serverE2E_PublicKey,
131
- extraHeaders
132
- });
152
+ export const getEmulatedLinks = (projectUrl) => Object.entries(CacheStore.EmulatedAuth)
153
+ .filter(([_, v]) => v === projectUrl)
154
+ .map(v => v[0]);
133
155
 
134
- let data;
156
+ const refreshToken = (builder, remainRetries = 1, isForceRefresh) =>
157
+ new Promise(async (resolve, reject) => {
158
+ const { projectUrl, serverE2E_PublicKey, uglify, extraHeaders } = builder;
159
+ const lostProcess = simplifyError('process_lost', 'The token refresh process has been lost and replaced with another one');
135
160
 
136
161
  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
- }
144
-
145
- const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
146
-
147
- if (CacheStore.AuthStore[projectUrl]) {
148
- CacheStore.AuthStore[projectUrl].token = f.result.token;
149
- Scoped.AuthJWTToken[projectUrl] = f.result.token;
150
-
151
- resolve(f.result.token);
152
- const isInit = !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh;
153
-
154
- triggerAuthToken(projectUrl, isInit);
155
- if (isForceRefresh) Scoped.InitiatedForcedToken[projectUrl] = true;
162
+ const { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
156
163
 
157
- getEmulatedLinks(projectUrl).forEach(v => {
158
- CacheStore.AuthStore[v] = basicClone(CacheStore.AuthStore[projectUrl]);
159
- Scoped.AuthJWTToken[v] = f.result.token;
160
-
161
- triggerAuthToken(v, isInit);
162
- if (isForceRefresh) Scoped.InitiatedForcedToken[v] = true;
164
+ const [reqBuilder, [privateKey]] = await buildFetchInterface({
165
+ body: { token, r_token },
166
+ uglify,
167
+ serverE2E_PublicKey,
168
+ extraHeaders
163
169
  });
164
- 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 :
170
+
171
+ const data = await buildFetchResult(await fetch(EngineApi._refreshAuthToken(projectUrl, uglify), reqBuilder), uglify);
172
+
173
+ const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data;
174
+
175
+ if (CacheStore.AuthStore[projectUrl]) {
176
+ CacheStore.AuthStore[projectUrl].token = f.result.token;
177
+ Scoped.AuthJWTToken[projectUrl] = f.result.token;
178
+ await updateTokenTimestamp(projectUrl, f.result.token);
179
+
180
+ resolve(f.result.token);
181
+ const isInit = !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh;
182
+
183
+ triggerAuthToken(projectUrl, isInit);
184
+ if (isForceRefresh) Scoped.InitiatedForcedToken[projectUrl] = true;
185
+
186
+ getEmulatedLinks(projectUrl).forEach(v => {
187
+ CacheStore.AuthStore[v] = basicClone(CacheStore.AuthStore[projectUrl]);
188
+ Scoped.AuthJWTToken[v] = f.result.token;
189
+
190
+ triggerAuthToken(v, isInit);
191
+ if (isForceRefresh) Scoped.InitiatedForcedToken[v] = true;
192
+ });
193
+ updateCacheStore(['AuthStore']);
194
+ initTokenRefresher({ config: builder });
195
+ } else reject(lostProcess.simpleError);
196
+ } catch (e) {
197
+ if (e.simpleError) {
198
+ console.error(`refreshToken error: ${e.simpleError?.message}`);
199
+ doSignOut({ ...builder });
200
+ reject(e.simpleError);
201
+ } else if (remainRetries <= 0) {
202
+ reject(
176
203
  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);
204
+ );
205
+ console.error(`refreshToken retry limit exceeded`);
206
+ } else {
207
+ const l = listenReachableServer(c => {
208
+ if (c) {
209
+ l();
210
+ refreshToken(builder, remainRetries - 1, isForceRefresh).then(resolve, reject);
211
+ }
212
+ }, projectUrl);
213
+ }
189
214
  }
190
- }
191
- });
215
+ });