react-native-mosquito-transport 0.0.14
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/CODE_OF_CONDUCT.md +133 -0
- package/CONTRIBUTING.md +114 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/android/build.gradle +77 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/mosquitodb/MosquitodbModule.java +32 -0
- package/android/src/main/java/com/mosquitodb/MosquitodbPackage.java +28 -0
- package/example/.bundle/config +2 -0
- package/example/.node-version +1 -0
- package/example/.watchmanconfig +1 -0
- package/example/Gemfile +6 -0
- package/example/android/app/build.gradle +170 -0
- package/example/android/app/debug.keystore +0 -0
- package/example/android/app/proguard-rules.pro +10 -0
- package/example/android/app/src/debug/AndroidManifest.xml +13 -0
- package/example/android/app/src/debug/java/com/mosquitodbexample/ReactNativeFlipper.java +75 -0
- package/example/android/app/src/main/AndroidManifest.xml +25 -0
- package/example/android/app/src/main/java/com/mosquitodbexample/MainActivity.java +35 -0
- package/example/android/app/src/main/java/com/mosquitodbexample/MainApplication.java +62 -0
- package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +36 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/values/strings.xml +3 -0
- package/example/android/app/src/main/res/values/styles.xml +9 -0
- package/example/android/app/src/release/java/com/mosquitodbexample/ReactNativeFlipper.java +20 -0
- package/example/android/build.gradle +21 -0
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/example/android/gradle.properties +44 -0
- package/example/android/gradlew +234 -0
- package/example/android/gradlew.bat +89 -0
- package/example/android/settings.gradle +4 -0
- package/example/app.json +4 -0
- package/example/babel.config.js +17 -0
- package/example/index.js +5 -0
- package/example/ios/.xcode.env +11 -0
- package/example/ios/File.swift +6 -0
- package/example/ios/MosquitodbExample/AppDelegate.h +6 -0
- package/example/ios/MosquitodbExample/AppDelegate.mm +36 -0
- package/example/ios/MosquitodbExample/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/example/ios/MosquitodbExample/Images.xcassets/Contents.json +6 -0
- package/example/ios/MosquitodbExample/Info.plist +55 -0
- package/example/ios/MosquitodbExample/LaunchScreen.storyboard +47 -0
- package/example/ios/MosquitodbExample/main.m +10 -0
- package/example/ios/MosquitodbExample-Bridging-Header.h +3 -0
- package/example/ios/MosquitodbExample.xcodeproj/project.pbxproj +702 -0
- package/example/ios/MosquitodbExample.xcodeproj/xcshareddata/xcschemes/MosquitodbExample.xcscheme +88 -0
- package/example/ios/MosquitodbExampleTests/Info.plist +24 -0
- package/example/ios/MosquitodbExampleTests/MosquitodbExampleTests.m +66 -0
- package/example/ios/Podfile +60 -0
- package/example/metro.config.js +40 -0
- package/example/package.json +22 -0
- package/example/react-native.config.js +10 -0
- package/example/src/App.tsx +31 -0
- package/ios/Mosquitodb-Bridging-Header.h +2 -0
- package/ios/Mosquitodb.m +22 -0
- package/ios/Mosquitodb.swift +305 -0
- package/ios/Mosquitodb.xcodeproj/project.pbxproj +283 -0
- package/package.json +45 -0
- package/react-native-mosquitodb.podspec +35 -0
- package/src/helpers/EngineApi.js +34 -0
- package/src/helpers/listeners.js +7 -0
- package/src/helpers/peripherals.js +195 -0
- package/src/helpers/utils.js +113 -0
- package/src/helpers/values.js +72 -0
- package/src/helpers/variables.js +34 -0
- package/src/index.d.ts +373 -0
- package/src/index.js +369 -0
- package/src/products/auth/accessor.js +151 -0
- package/src/products/auth/index.js +279 -0
- package/src/products/database/accessor.js +316 -0
- package/src/products/database/index.js +603 -0
- package/src/products/database/types.js +22 -0
- package/src/products/database/validator.js +282 -0
- package/src/products/http_callable/index.js +230 -0
- package/src/products/storage/index.js +217 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import 'react-native-get-random-values';
|
|
2
|
+
import { IS_WHOLE_NUMBER, deserializeE2E, listenReachableServer, serializeE2E } from "./helpers/peripherals";
|
|
3
|
+
import { releaseCacheStore } from "./helpers/utils";
|
|
4
|
+
import { Scoped } from "./helpers/variables";
|
|
5
|
+
import { MTAuth } from "./products/auth";
|
|
6
|
+
import { MTCollection, batchWrite } from "./products/database";
|
|
7
|
+
import { MTStorage } from "./products/storage";
|
|
8
|
+
import { ServerReachableListener, TokenRefreshListener } from "./helpers/listeners";
|
|
9
|
+
import { initTokenRefresher, listenTokenReady, triggerAuthToken } from "./products/auth/accessor";
|
|
10
|
+
import { TIMESTAMP, DOCUMENT_EXTRACTION, FIND_GEO_JSON, GEO_JSON } from "./products/database/types";
|
|
11
|
+
import { mfetch } from "./products/http_callable";
|
|
12
|
+
import { io } from "socket.io-client";
|
|
13
|
+
import { validateCollectionPath } from "./products/database/validator";
|
|
14
|
+
import { CACHE_PROTOCOL, Regexs } from "./helpers/values";
|
|
15
|
+
import { trySendPendingWrite } from "./products/database/accessor";
|
|
16
|
+
import EngineApi from './helpers/EngineApi';
|
|
17
|
+
import { parse, stringify } from 'json-buffer';
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
_listenCollection,
|
|
21
|
+
_listenDocument,
|
|
22
|
+
_startDisconnectWriteTask,
|
|
23
|
+
_cancelDisconnectWriteTask,
|
|
24
|
+
_listenUserVerification
|
|
25
|
+
} = EngineApi;
|
|
26
|
+
|
|
27
|
+
class RNMT {
|
|
28
|
+
constructor(config) {
|
|
29
|
+
validateMTConfig(config, this);
|
|
30
|
+
this.config = {
|
|
31
|
+
...config,
|
|
32
|
+
uglify: config.enableE2E_Encryption,
|
|
33
|
+
apiUrl: config.projectUrl,
|
|
34
|
+
projectUrl: config.projectUrl.split('/').slice(0, -1).join('/')
|
|
35
|
+
};
|
|
36
|
+
const { projectUrl } = this.config;
|
|
37
|
+
|
|
38
|
+
this.config.baseUrl = projectUrl.split('://')[1];
|
|
39
|
+
|
|
40
|
+
if (!Scoped.ReleaseCacheData)
|
|
41
|
+
throw `releaseCache must be called before creating any ${this.constructor.name} instance`;
|
|
42
|
+
|
|
43
|
+
if (!Scoped.InitializedProject[projectUrl]) {
|
|
44
|
+
Scoped.InitializedProject[projectUrl] = true;
|
|
45
|
+
Scoped.LastTokenRefreshRef[projectUrl] = 0;
|
|
46
|
+
triggerAuthToken(projectUrl);
|
|
47
|
+
initTokenRefresher({ ...this.config }, true);
|
|
48
|
+
|
|
49
|
+
const socket = io(`ws://${projectUrl.split('://')[1]}`, {
|
|
50
|
+
auth: { _m_internal: true }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
socket.on('connect', () => {
|
|
54
|
+
ServerReachableListener.dispatch(projectUrl, true);
|
|
55
|
+
});
|
|
56
|
+
socket.on('disconnect', () => {
|
|
57
|
+
ServerReachableListener.dispatch(projectUrl, false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
listenReachableServer(c => {
|
|
61
|
+
Scoped.IS_CONNECTED[projectUrl] = c;
|
|
62
|
+
if (c) trySendPendingWrite();
|
|
63
|
+
}, projectUrl);
|
|
64
|
+
|
|
65
|
+
TokenRefreshListener.listenTo(projectUrl, v => {
|
|
66
|
+
Scoped.IS_TOKEN_READY[projectUrl] = v;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static releaseCache(prop) {
|
|
72
|
+
if (Scoped.ReleaseCacheData) throw `calling ${this.name} multiple times is prohibited`;
|
|
73
|
+
validateReleaseCacheProp({ ...prop });
|
|
74
|
+
Scoped.ReleaseCacheData = { ...prop };
|
|
75
|
+
releaseCacheStore({ ...prop });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getDatabase = (dbName, dbUrl) => ({
|
|
79
|
+
collection: (path) => new MTCollection({
|
|
80
|
+
...this.config,
|
|
81
|
+
path,
|
|
82
|
+
...(dbName ? { dbName } : {}),
|
|
83
|
+
...(dbUrl ? { dbUrl } : {})
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
collection = (path) => {
|
|
87
|
+
validateCollectionPath(path);
|
|
88
|
+
return new MTCollection({ ...this.config, path });
|
|
89
|
+
}
|
|
90
|
+
batchWrite = (map, configx) => batchWrite({ ...this.config }, map, configx);
|
|
91
|
+
auth = () => new MTAuth({ ...this.config });
|
|
92
|
+
storage = () => new MTStorage({ ...this.config });
|
|
93
|
+
fetchHttp = (endpoint, init, config) => mfetch(endpoint, init, { ...this.config, method: config });
|
|
94
|
+
listenReachableServer = (callback) => listenReachableServer(callback, this.config.projectUrl);
|
|
95
|
+
|
|
96
|
+
getSocket = (configOpts) => {
|
|
97
|
+
const { disableAuth, authHandshake } = configOpts || {},
|
|
98
|
+
{ projectUrl, uglify, accessKey, serverE2E_PublicKey } = this.config;
|
|
99
|
+
|
|
100
|
+
const restrictedRoute = [
|
|
101
|
+
_listenCollection,
|
|
102
|
+
_listenDocument,
|
|
103
|
+
_startDisconnectWriteTask,
|
|
104
|
+
_cancelDisconnectWriteTask,
|
|
105
|
+
_listenUserVerification
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
let socketReadyCallback,
|
|
109
|
+
makeSocketCallback = () => new Promise(resolve => {
|
|
110
|
+
socketReadyCallback = resolve;
|
|
111
|
+
}),
|
|
112
|
+
socketReadyPromise = makeSocketCallback(),
|
|
113
|
+
socketListenerList = [],
|
|
114
|
+
socketListenerIte = 0;
|
|
115
|
+
|
|
116
|
+
let hasCancelled,
|
|
117
|
+
socket,
|
|
118
|
+
tokenListener,
|
|
119
|
+
clientPrivateKey;
|
|
120
|
+
|
|
121
|
+
const listenerCallback = (callback) => function () {
|
|
122
|
+
const [args, ...restArgs] = [...arguments];
|
|
123
|
+
let res;
|
|
124
|
+
|
|
125
|
+
if (uglify) {
|
|
126
|
+
res = parse(deserializeE2E(args, serverE2E_PublicKey, clientPrivateKey));
|
|
127
|
+
} else res = args;
|
|
128
|
+
|
|
129
|
+
callback?.(...res || [], ...typeof restArgs[0] === 'function' ? [function () {
|
|
130
|
+
const args = [...arguments];
|
|
131
|
+
let res;
|
|
132
|
+
|
|
133
|
+
if (uglify) {
|
|
134
|
+
res = serializeE2E(stringify(args), undefined, serverE2E_PublicKey)[0];
|
|
135
|
+
} else res = args;
|
|
136
|
+
|
|
137
|
+
restArgs[0](res);
|
|
138
|
+
}] : []);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const emit = ({ timeout, promise, emittion: emittionx }) => new Promise(async (resolve, reject) => {
|
|
142
|
+
const [route, ...emittion] = emittionx;
|
|
143
|
+
|
|
144
|
+
if (typeof route !== 'string')
|
|
145
|
+
throw `expected ${promise ? 'emitWithAck' : 'emit'} first argument to be a string type`;
|
|
146
|
+
|
|
147
|
+
if (restrictedRoute.includes(route))
|
|
148
|
+
throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
|
|
149
|
+
|
|
150
|
+
let hasResolved, stime = Date.now();
|
|
151
|
+
|
|
152
|
+
const timer = isNaN(timeout) ? undefined : setTimeout(() => {
|
|
153
|
+
hasResolved = true;
|
|
154
|
+
reject(new Error('emittion timeout'));
|
|
155
|
+
}, timeout);
|
|
156
|
+
|
|
157
|
+
await socketReadyPromise;
|
|
158
|
+
if (hasResolved) return;
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const h = isNaN(timeout) ? socket : socket.timeout(timeout - (Date.now() - stime));
|
|
163
|
+
|
|
164
|
+
const lastEmit = emittion.slice(-1)[0],
|
|
165
|
+
mit = typeof lastEmit === 'function' ? emittion.slice(0, -1) : emittion;
|
|
166
|
+
|
|
167
|
+
const [reqBuilder, [privateKey]] = uglify ? serializeE2E(stringify(mit), undefined, serverE2E_PublicKey) : [undefined, []];
|
|
168
|
+
|
|
169
|
+
if (typeof lastEmit === 'function' && promise)
|
|
170
|
+
throw 'emitWithAck cannot have function in it parameter';
|
|
171
|
+
|
|
172
|
+
const p = await h[promise ? 'emitWithAck' : 'emit'](route,
|
|
173
|
+
...uglify ? [reqBuilder] : [mit],
|
|
174
|
+
...typeof lastEmit === 'function' ? [function () {
|
|
175
|
+
const args = [...arguments][0];
|
|
176
|
+
let res;
|
|
177
|
+
|
|
178
|
+
if (uglify) {
|
|
179
|
+
res = parse(deserializeE2E(args, serverE2E_PublicKey, privateKey));
|
|
180
|
+
} else res = args;
|
|
181
|
+
|
|
182
|
+
lastEmit(...res || []);
|
|
183
|
+
}] : []
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
resolve((promise && p) ? uglify ? parse(deserializeE2E(p, serverE2E_PublicKey, privateKey))[0] : p[0] : undefined);
|
|
187
|
+
} catch (e) {
|
|
188
|
+
reject(e);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const init = async () => {
|
|
193
|
+
if (hasCancelled) return;
|
|
194
|
+
const mtoken = disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl];
|
|
195
|
+
const [reqBuilder, [privateKey]] = uglify ? serializeE2E({ accessKey, a_extras: authHandshake }, mtoken, serverE2E_PublicKey) : [null, []];
|
|
196
|
+
|
|
197
|
+
socket = io(`ws://${projectUrl.split('://')[1]}`, {
|
|
198
|
+
auth: uglify ? {
|
|
199
|
+
ugly: true,
|
|
200
|
+
e2e: reqBuilder
|
|
201
|
+
} : {
|
|
202
|
+
...mtoken ? { mtoken } : {},
|
|
203
|
+
a_extras: authHandshake,
|
|
204
|
+
accessKey
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
clientPrivateKey = privateKey;
|
|
208
|
+
|
|
209
|
+
socketReadyCallback();
|
|
210
|
+
socketListenerList.forEach(([_, method, route, callback]) => {
|
|
211
|
+
socket[method](route, callback);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (disableAuth) {
|
|
216
|
+
init();
|
|
217
|
+
} else {
|
|
218
|
+
let lastTokenStatus;
|
|
219
|
+
|
|
220
|
+
tokenListener = listenTokenReady(status => {
|
|
221
|
+
if (lastTokenStatus === (status || false)) return;
|
|
222
|
+
|
|
223
|
+
if (status === 'ready') {
|
|
224
|
+
init();
|
|
225
|
+
} else {
|
|
226
|
+
socket?.close?.();
|
|
227
|
+
socket = undefined;
|
|
228
|
+
socketReadyPromise = makeSocketCallback();
|
|
229
|
+
}
|
|
230
|
+
lastTokenStatus = status || false;
|
|
231
|
+
}, projectUrl);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
timeout: (timeout) => ({
|
|
236
|
+
emitWithAck: function () {
|
|
237
|
+
return emit({
|
|
238
|
+
timeout,
|
|
239
|
+
promise: true,
|
|
240
|
+
emittion: [...arguments]
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}),
|
|
244
|
+
emit: function () { emit({ emittion: [...arguments] }) },
|
|
245
|
+
emitWithAck: function () {
|
|
246
|
+
return emit({
|
|
247
|
+
emittion: [...arguments],
|
|
248
|
+
promise: true
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
on: async (route, callback) => {
|
|
252
|
+
if (restrictedRoute.includes(route))
|
|
253
|
+
throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
|
|
254
|
+
const ref = ++socketListenerIte,
|
|
255
|
+
listener = listenerCallback(callback);
|
|
256
|
+
|
|
257
|
+
socketListenerList.push([ref, 'on', route, listener]);
|
|
258
|
+
if (socket) socket.on(route, listener);
|
|
259
|
+
|
|
260
|
+
return () => {
|
|
261
|
+
if (socket) socket.off(route, listener);
|
|
262
|
+
socketListenerList = socketListenerList.filter(([id]) => id !== ref);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
once: async (route, callback) => {
|
|
266
|
+
if (restrictedRoute.includes(route))
|
|
267
|
+
throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
|
|
268
|
+
const ref = ++socketListenerIte,
|
|
269
|
+
listener = listenerCallback(callback);
|
|
270
|
+
|
|
271
|
+
socketListenerList.push([ref, 'once', route, listener]);
|
|
272
|
+
if (socket) socket.once(route, listener);
|
|
273
|
+
|
|
274
|
+
return () => {
|
|
275
|
+
if (socket) socket.off(route, listener);
|
|
276
|
+
socketListenerList = socketListenerList.filter(([id]) => id !== ref);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
destroy: () => {
|
|
280
|
+
hasCancelled = true;
|
|
281
|
+
tokenListener?.();
|
|
282
|
+
if (socket) socket.close();
|
|
283
|
+
socketListenerList = [];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
wipeDatabaseCache = () => {
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const validateReleaseCacheProp = (prop) => {
|
|
294
|
+
const cacheList = [...Object.values(CACHE_PROTOCOL)];
|
|
295
|
+
|
|
296
|
+
Object.entries(prop).forEach(([k, v]) => {
|
|
297
|
+
if (k === 'cachePassword') {
|
|
298
|
+
if (typeof v !== 'string' || v.trim().length <= 0)
|
|
299
|
+
throw `Invalid value supplied to cachePassword, value must be a string and greater than 0 characters`;
|
|
300
|
+
} else if (k === 'cacheProtocol') {
|
|
301
|
+
if (!cacheList.includes(`${v}`)) throw `unknown value supplied to ${k}, expected any of ${cacheList}`;
|
|
302
|
+
} else throw `Unexpected property named ${k}`;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const validator = {
|
|
307
|
+
dbName: (v) => {
|
|
308
|
+
if (typeof v !== 'string' || !v.trim())
|
|
309
|
+
throw `Invalid value supplied to dbName, value must be string and greater than one`;
|
|
310
|
+
},
|
|
311
|
+
dbUrl: (v) => {
|
|
312
|
+
if (typeof v !== 'string' || !v.trim())
|
|
313
|
+
throw `Invalid value supplied to dbUrl, value must be string and greater than one`;
|
|
314
|
+
},
|
|
315
|
+
heapMemory: (v) => {
|
|
316
|
+
if (typeof v !== 'number' || v <= 0)
|
|
317
|
+
throw `Invalid value supplied to heapMemory, value must be number and greater than zero`;
|
|
318
|
+
},
|
|
319
|
+
projectUrl: (v) => {
|
|
320
|
+
if (typeof v !== 'string' || !Regexs.LINK().test(v.trim()))
|
|
321
|
+
throw `Invalid value supplied to projectUrl, value must be a string and greater than one`;
|
|
322
|
+
},
|
|
323
|
+
disableCache: (v) => {
|
|
324
|
+
if (typeof v !== 'boolean')
|
|
325
|
+
throw `Invalid value supplied to disableCache, value must be a boolean`;
|
|
326
|
+
},
|
|
327
|
+
accessKey: (v) => {
|
|
328
|
+
if (typeof v !== 'string' || !v.trim())
|
|
329
|
+
throw `Invalid value supplied to accessKey, value must be a string and greater than one`;
|
|
330
|
+
},
|
|
331
|
+
maxRetries: (v) => {
|
|
332
|
+
if (typeof v !== 'number' || v <= 0 || !IS_WHOLE_NUMBER(v))
|
|
333
|
+
throw `Invalid value supplied to maxRetries, value must be whole number and greater than zero`;
|
|
334
|
+
},
|
|
335
|
+
enableE2E_Encryption: (v) => {
|
|
336
|
+
if (typeof v !== 'boolean')
|
|
337
|
+
throw `Invalid value supplied to enableE2E_Encryption, value must be a boolean`;
|
|
338
|
+
},
|
|
339
|
+
serverE2E_PublicKey: (v) => {
|
|
340
|
+
if (typeof v !== 'string' || !v.trim())
|
|
341
|
+
throw `Invalid value supplied to serverETE_PublicKey, value must be string and greater than one`;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const validateMTConfig = (config, that) => {
|
|
346
|
+
if (typeof config !== 'object') throw `${that.constructor.name} config is not an object`;
|
|
347
|
+
const h = Object.keys(config);
|
|
348
|
+
|
|
349
|
+
for (let i = 0; i < h.length; i++) {
|
|
350
|
+
const k = h[i];
|
|
351
|
+
|
|
352
|
+
if (!validator[k]) throw `Unexpected property named ${k}`;
|
|
353
|
+
validator[k](config[k]);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (config.enableE2E_Encryption && !config.serverE2E_PublicKey)
|
|
357
|
+
throw '"serverE2E_PublicKey" is missing, enabling end-to-end encryption requires a public encryption key from the server';
|
|
358
|
+
if (!config['projectUrl']) throw `projectUrl is a required property in ${that.constructor.name}() constructor`;
|
|
359
|
+
if (!config['accessKey']) throw `accessKey is a required property in ${that.constructor.name}() constructor`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export {
|
|
363
|
+
TIMESTAMP,
|
|
364
|
+
DOCUMENT_EXTRACTION,
|
|
365
|
+
FIND_GEO_JSON,
|
|
366
|
+
GEO_JSON
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
export default RNMT;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import setLargeTimeout from "set-large-timeout";
|
|
2
|
+
import { doSignOut } from ".";
|
|
3
|
+
import EngineApi from "../../helpers/EngineApi";
|
|
4
|
+
import { AuthTokenListener, TokenRefreshListener } from "../../helpers/listeners";
|
|
5
|
+
import { decodeBinary, deserializeE2E, listenReachableServer, simplifyCaughtError } from "../../helpers/peripherals";
|
|
6
|
+
import { awaitReachableServer, awaitStore, buildFetchInterface, simplifyError, updateCacheStore } from "../../helpers/utils";
|
|
7
|
+
import { CacheStore, Scoped } from "../../helpers/variables";
|
|
8
|
+
|
|
9
|
+
export const listenToken = (callback, projectUrl) =>
|
|
10
|
+
AuthTokenListener.listenTo(projectUrl, (t, n) => {
|
|
11
|
+
if (t === undefined) return;
|
|
12
|
+
callback?.(t || null, n);
|
|
13
|
+
}, true);
|
|
14
|
+
|
|
15
|
+
export const injectFreshToken = async (config, { token, refreshToken }) => {
|
|
16
|
+
const { projectUrl } = config;
|
|
17
|
+
|
|
18
|
+
await awaitStore();
|
|
19
|
+
CacheStore.AuthStore[projectUrl] = { token, refreshToken };
|
|
20
|
+
Scoped.AuthJWTToken[projectUrl] = token;
|
|
21
|
+
updateCacheStore();
|
|
22
|
+
|
|
23
|
+
triggerAuthToken(projectUrl);
|
|
24
|
+
initTokenRefresher(config);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const parseToken = (token) => JSON.parse(decodeBinary(token.split('.')[1]));
|
|
28
|
+
|
|
29
|
+
export const triggerAuthToken = async (projectUrl, isInit) => {
|
|
30
|
+
await awaitStore();
|
|
31
|
+
AuthTokenListener.dispatch(projectUrl, CacheStore.AuthStore[projectUrl]?.token || null, isInit);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const awaitRefreshToken = (projectUrl) => new Promise(resolve => {
|
|
35
|
+
const l = TokenRefreshListener.listenTo(projectUrl, v => {
|
|
36
|
+
if (v === 'ready') {
|
|
37
|
+
l();
|
|
38
|
+
resolve();
|
|
39
|
+
}
|
|
40
|
+
}, true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const listenTokenReady = (callback, projectUrl) => TokenRefreshListener.listenTo(projectUrl, callback, true);
|
|
44
|
+
|
|
45
|
+
export const initTokenRefresher = async (config, forceRefresh) => {
|
|
46
|
+
const { projectUrl, maxRetries } = config;
|
|
47
|
+
await awaitStore();
|
|
48
|
+
const { token } = CacheStore.AuthStore[projectUrl] || {},
|
|
49
|
+
tokenInfo = token ? parseToken(token) : undefined;
|
|
50
|
+
|
|
51
|
+
Scoped.TokenRefreshTimer[projectUrl]?.();
|
|
52
|
+
|
|
53
|
+
if (token) {
|
|
54
|
+
const hasExpire = Date.now() >= (tokenInfo.exp * 1000) - 60000,
|
|
55
|
+
rizz = () => refreshToken(config, ++Scoped.LastTokenRefreshRef[projectUrl], maxRetries, maxRetries, forceRefresh);
|
|
56
|
+
|
|
57
|
+
if (hasExpire || forceRefresh) {
|
|
58
|
+
TokenRefreshListener.dispatch(projectUrl);
|
|
59
|
+
return rizz();
|
|
60
|
+
} else {
|
|
61
|
+
TokenRefreshListener.dispatch(projectUrl, 'ready');
|
|
62
|
+
Scoped.TokenRefreshTimer[projectUrl] = setLargeTimeout(() => {
|
|
63
|
+
TokenRefreshListener.dispatch(projectUrl);
|
|
64
|
+
rizz();
|
|
65
|
+
}, ((tokenInfo.exp * 1000) - 60000) - Date.now());
|
|
66
|
+
}
|
|
67
|
+
} else if (forceRefresh) {
|
|
68
|
+
TokenRefreshListener.dispatch(projectUrl, 'ready');
|
|
69
|
+
return simplifyError('no_token_yet', 'No token is available to initiate a refresh').simpleError
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const refreshToken = (builder, processRef, remainRetries = 7, initialRetries = 7, isForceRefresh) => new Promise(async (resolve, reject) => {
|
|
74
|
+
const { projectUrl, serverE2E_PublicKey, accessKey, uglify } = builder;
|
|
75
|
+
const lostProcess = simplifyError('process_lost', 'The token refresh process has been lost and replace with another one');
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
|
|
79
|
+
|
|
80
|
+
const [reqBuilder, [privateKey]] = buildFetchInterface({
|
|
81
|
+
body: { token, r_token },
|
|
82
|
+
accessKey,
|
|
83
|
+
uglify,
|
|
84
|
+
serverE2E_PublicKey
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const r = await (await fetch(EngineApi._refreshAuthToken(projectUrl, uglify), reqBuilder)).json();
|
|
88
|
+
|
|
89
|
+
if (processRef !== Scoped.LastTokenRefreshRef[projectUrl]) {
|
|
90
|
+
reject(lostProcess.simpleError);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (r.simpleError) throw r;
|
|
94
|
+
|
|
95
|
+
const f = uglify ? deserializeE2E(r.e2e, serverE2E_PublicKey, privateKey) : r;
|
|
96
|
+
|
|
97
|
+
if (CacheStore.AuthStore[projectUrl]) {
|
|
98
|
+
CacheStore.AuthStore[projectUrl].token = f.result.token;
|
|
99
|
+
Scoped.AuthJWTToken[projectUrl] = f.result.token;
|
|
100
|
+
|
|
101
|
+
resolve(f.result.token);
|
|
102
|
+
triggerAuthToken(projectUrl, !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh);
|
|
103
|
+
if (isForceRefresh) Scoped.InitiatedForcedToken[projectUrl] = true;
|
|
104
|
+
updateCacheStore();
|
|
105
|
+
initTokenRefresher(builder);
|
|
106
|
+
invalidateToken(builder, token);
|
|
107
|
+
} else reject(lostProcess.simpleError);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
if (e.simpleError) {
|
|
110
|
+
console.error(`refreshToken error: ${e.simpleError?.message}`);
|
|
111
|
+
doSignOut({ ...builder });
|
|
112
|
+
reject(e.simpleError);
|
|
113
|
+
} else if (remainRetries <= 0) {
|
|
114
|
+
reject(
|
|
115
|
+
processRef === Scoped.LastTokenRefreshRef[projectUrl] ?
|
|
116
|
+
lostProcess.simpleError :
|
|
117
|
+
simplifyError('retry_limit_reached', 'The retry limit has been reach and execution prematurely stopped').simpleError
|
|
118
|
+
);
|
|
119
|
+
console.error(`refreshToken retry exceeded, waiting for 2min before starting another retry`);
|
|
120
|
+
} else {
|
|
121
|
+
const l = listenReachableServer(c => {
|
|
122
|
+
if (processRef !== Scoped.LastTokenRefreshRef[projectUrl]) {
|
|
123
|
+
reject(lostProcess.simpleError);
|
|
124
|
+
l();
|
|
125
|
+
} else if (c) {
|
|
126
|
+
l();
|
|
127
|
+
refreshToken(builder, processRef, remainRetries - 1, initialRetries).then(resolve, reject);
|
|
128
|
+
}
|
|
129
|
+
}, projectUrl);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export const invalidateToken = async (builder, token) => {
|
|
135
|
+
try {
|
|
136
|
+
const { projectUrl, accessKey, uglify, serverE2E_PublicKey } = builder;
|
|
137
|
+
await awaitReachableServer(projectUrl);
|
|
138
|
+
|
|
139
|
+
const [reqBuilder] = buildFetchInterface({
|
|
140
|
+
body: { token },
|
|
141
|
+
accessKey,
|
|
142
|
+
uglify,
|
|
143
|
+
serverE2E_PublicKey
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const r = await (await fetch(EngineApi._invalidateToken(projectUrl, uglify), reqBuilder)).json();
|
|
147
|
+
if (r.simpleError) throw r;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
throw simplifyCaughtError(e).simpleError;
|
|
150
|
+
}
|
|
151
|
+
}
|