mosquito-transport-js 0.3.4 → 0.3.6

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/.jshintrc CHANGED
@@ -10,6 +10,7 @@
10
10
  "clearInterval": true,
11
11
  "setInterval": true,
12
12
  "AbortController": true,
13
- "URLSearchParams": true
13
+ "URLSearchParams": true,
14
+ "URL": true
14
15
  }
15
16
  }
package/README.md CHANGED
@@ -1 +1 @@
1
- # mosquito-transport-js
1
+ # mosquito-transport-js
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mosquito-transport-js",
3
- "version": "0.3.4",
4
- "description": "Javascript web sdk for mosquito-transport (https://github.com/deflexable/mosquito-transport)",
3
+ "version": "0.3.6",
4
+ "description": "Javascript web sdk for mosquito-transport (https://github.com/brainbehindx/mosquito-transport)",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -32,7 +32,7 @@
32
32
  "bson": "^6.8.0",
33
33
  "buffer": "^6.0.3",
34
34
  "crypto-js": "^4.2.0",
35
- "entity-serializer": "^1.0.1",
35
+ "entity-serializer": "^1.0.2",
36
36
  "guard-object": "^1.1.3",
37
37
  "limit-task": "1.0.0",
38
38
  "lodash.clonedeep": "^4.5.0",
@@ -17,11 +17,13 @@ export const updateCacheStore = (timer = 300) => {
17
17
 
18
18
  clearTimeout(Scoped.cacheStorageReducer);
19
19
  Scoped.cacheStorageReducer = setTimeout(() => {
20
- const { AuthStore, DatabaseStore, PendingWrites, ...restStore } = CacheStore;
20
+ const { AuthStore, EmulatedAuth, PendingAuthPurge, DatabaseStore, PendingWrites, ...restStore } = CacheStore;
21
21
 
22
22
  const txt = encryptString(
23
23
  JSON.stringify({
24
24
  AuthStore,
25
+ EmulatedAuth,
26
+ PendingAuthPurge,
25
27
  ...promoteCache ? {
26
28
  DatabaseStore: serializeToBase64(DatabaseStore),
27
29
  PendingWrites: serializeToBase64(PendingWrites)
@@ -27,10 +27,10 @@ export const DELIVERY = {
27
27
  };
28
28
 
29
29
  export const AUTH_PROVIDER_ID = {
30
- GOOGLE: 'google.com',
31
- FACEBOOK: 'facebook.com',
30
+ GOOGLE: 'google',
31
+ FACEBOOK: 'facebook',
32
32
  PASSWORD: 'password',
33
- TWITTER: 'x.com',
34
- GITHUB: 'github.com',
35
- APPLE: 'apple.com'
33
+ TWITTER: 'x',
34
+ GITHUB: 'github',
35
+ APPLE: 'apple'
36
36
  };
@@ -36,6 +36,8 @@ export const CacheStore = {
36
36
  DatabaseCountResult: {},
37
37
  DatabaseStats: {},
38
38
  AuthStore: {},
39
+ PendingAuthPurge: {},
40
+ EmulatedAuth: {},
39
41
  PendingWrites: {},
40
42
  FetchedStore: {}
41
43
  };
package/src/index.d.ts CHANGED
@@ -68,12 +68,12 @@ export class DoNotEncrypt {
68
68
  }
69
69
 
70
70
  interface auth_provider_id {
71
- GOOGLE: 'google.com';
72
- FACEBOOK: 'facebook.com';
71
+ GOOGLE: 'google';
72
+ FACEBOOK: 'facebook';
73
73
  PASSWORD: 'password';
74
- TWITTER: 'x.com';
75
- GITHUB: 'github.com';
76
- APPLE: 'apple.com';
74
+ TWITTER: 'x';
75
+ GITHUB: 'github';
76
+ APPLE: 'apple';
77
77
  }
78
78
 
79
79
  type auth_provider_id_values = auth_provider_id['GOOGLE'] |
@@ -147,7 +147,7 @@ interface BatchWriteConfig extends WriteConfig {
147
147
 
148
148
  export class MosquitoTransport {
149
149
  constructor(config: MTConfig);
150
- static releaseCache(option?: ReleaseCacheOption | ReleaseCacheOption_IO): void;
150
+ static initializeCache(option?: ReleaseCacheOption | ReleaseCacheOption_IO): void;
151
151
  getDatabase(dbName?: string, dbUrl?: string): GetDatabase;
152
152
  collection(path: string): MTCollection;
153
153
  auth(): MTAuth;
@@ -398,6 +398,7 @@ interface MTAuth {
398
398
  getAuth: () => Promise<TokenEventData>;
399
399
  signOut: () => Promise<void>;
400
400
  forceRefreshToken: () => Promise<string>;
401
+ emulate: (projectUrl?: string) => Promise<void>;
401
402
  }
402
403
 
403
404
  interface SigninResult {
package/src/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { deserializeE2E, listenReachableServer, serializeE2E } from "./helpers/peripherals";
2
- import { releaseCacheStore, awaitStore } from "./helpers/utils";
3
- import { CacheStore, Scoped } from "./helpers/variables";
4
- import { MTAuth } from "./products/auth";
2
+ import { awaitStore, releaseCacheStore } from "./helpers/utils";
3
+ import { Scoped, CacheStore } from "./helpers/variables";
5
4
  import { MTCollection, batchWrite, trySendPendingWrite } from "./products/database";
6
5
  import { MTStorage } from "./products/storage";
7
6
  import { ServerReachableListener, TokenRefreshListener } from "./helpers/listeners";
@@ -13,8 +12,9 @@ import { AUTH_PROVIDER_ID, CACHE_PROTOCOL } from "./helpers/values";
13
12
  import EngineApi from './helpers/engine_api';
14
13
  import { Validator } from 'guard-object';
15
14
  import sendMessage from "./helpers/broadcaster";
16
- import cloneDeep from "lodash.clonedeep";
17
- import { Buffer } from "buffer";
15
+ import cloneDeep from 'lodash.clonedeep';
16
+ import { Buffer } from 'buffer';
17
+ import MTAuth from './products/auth';
18
18
 
19
19
  const {
20
20
  _listenCollection,
@@ -51,7 +51,7 @@ export class MosquitoTransport {
51
51
  this.config.wsPrefix = this.config.secureUrl ? 'wss' : 'ws';
52
52
 
53
53
  if (!Scoped.ReleaseCacheData)
54
- throw `releaseCache must be called before creating any ${this.constructor.name} instance`;
54
+ throw `initializeCache must be called before creating any ${this.constructor.name} instance`;
55
55
 
56
56
  if (!Scoped.InitializedProject[projectUrl]) {
57
57
  Scoped.InitializedProject[projectUrl] = cloneDeep(this.config);
@@ -128,7 +128,7 @@ export class MosquitoTransport {
128
128
  }
129
129
  }
130
130
 
131
- static releaseCache(prop) {
131
+ static initializeCache(prop) {
132
132
  if (Scoped.ReleaseCacheData) throw `calling ${this.name}() multiple times is prohibited`;
133
133
  validateReleaseCacheProp({ ...prop });
134
134
  Scoped.ReleaseCacheData = { ...prop };
@@ -1,10 +1,12 @@
1
- import { doSignOut } from ".";
1
+ import cloneDeep from "lodash.clonedeep";
2
+ import { doSignOut, revokeAuthIntance } from ".";
2
3
  import EngineApi from "../../helpers/engine_api";
3
4
  import { AuthTokenListener, TokenRefreshListener } from "../../helpers/listeners";
4
5
  import { decodeBinary, deserializeE2E, listenReachableServer } from "../../helpers/peripherals";
5
6
  import { awaitStore, buildFetchInterface, buildFetchResult, getPrefferTime, updateCacheStore } from "../../helpers/utils";
6
7
  import { CacheStore, Scoped } from "../../helpers/variables";
7
8
  import { simplifyError } from "simplify-error";
9
+ import { Validator } from "guard-object";
8
10
 
9
11
  export const listenToken = (callback, projectUrl) =>
10
12
  AuthTokenListener.listenTo(projectUrl, (t, n) => {
@@ -18,12 +20,39 @@ export const injectFreshToken = async (config, { token, refreshToken }) => {
18
20
  await awaitStore();
19
21
  CacheStore.AuthStore[projectUrl] = { token, refreshToken };
20
22
  Scoped.AuthJWTToken[projectUrl] = token;
23
+ if (projectUrl in CacheStore.EmulatedAuth)
24
+ delete CacheStore.EmulatedAuth[projectUrl];
25
+
21
26
  updateCacheStore(0);
22
27
 
23
28
  triggerAuthToken(projectUrl);
24
29
  initTokenRefresher(config);
25
30
  };
26
31
 
32
+ export const injectEmulatedAuth = async (config, emulatedURL) => {
33
+ await awaitStore();
34
+ if (typeof emulatedURL !== 'string' || (!Validator.HTTPS(emulatedURL) && !Validator.HTTP(emulatedURL)))
35
+ throw `Expected "projectUrl" to be valid https or http link but got "${emulatedURL}"`;
36
+
37
+ const { projectUrl } = config;
38
+ const { token } = CacheStore.AuthStore[emulatedURL] || {};
39
+ const depended = Object.entries(CacheStore.EmulatedAuth).find(([_, v]) => projectUrl === v);
40
+
41
+ if (emulatedURL === projectUrl) throw `auth instance for ${emulatedURL} cannot emulate itself`;
42
+ if (depended) throw `Chain Emulation Error: this auth instance (${projectUrl}) cannot be emulated as other auth instance (${depended[0]}) is already emulating it`;
43
+
44
+ const thisAuthStore = cloneDeep(CacheStore.AuthStore[projectUrl]);
45
+
46
+ CacheStore.AuthStore[projectUrl] = cloneDeep(CacheStore.AuthStore[emulatedURL]);
47
+ Scoped.AuthJWTToken[projectUrl] = token;
48
+ CacheStore.EmulatedAuth[projectUrl] = emulatedURL;
49
+
50
+ revokeAuthIntance(config, thisAuthStore);
51
+ updateCacheStore(0);
52
+ triggerAuthToken(projectUrl);
53
+ initTokenRefresher(config);
54
+ };
55
+
27
56
  export const parseToken = (token) => JSON.parse(decodeBinary(token.split('.')[1]));
28
57
 
29
58
  export const triggerAuthToken = async (projectUrl, isInit) => {
@@ -46,9 +75,18 @@ export const initTokenRefresher = async (config, forceRefresh) => {
46
75
  const { projectUrl, maxRetries } = config;
47
76
  await awaitStore();
48
77
  const { token } = CacheStore.AuthStore[projectUrl] || {};
78
+ const emulatedURL = CacheStore.EmulatedAuth[projectUrl];
49
79
  const tokenInfo = token && parseToken(token);
50
80
 
51
81
  clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
82
+ if (emulatedURL) return;
83
+
84
+ const notifyAuthReady = (value) => {
85
+ TokenRefreshListener.dispatch(projectUrl, value);
86
+ getEmulatedLinks(projectUrl).forEach(v => {
87
+ TokenRefreshListener.dispatch(v, value);
88
+ });
89
+ }
52
90
 
53
91
  if (token) {
54
92
  const expireOn = (tokenInfo.exp * 1000) - 60000;
@@ -56,24 +94,28 @@ export const initTokenRefresher = async (config, forceRefresh) => {
56
94
  const rizz = () => refreshToken(config, ++Scoped.LastTokenRefreshRef[projectUrl], maxRetries, maxRetries, forceRefresh);
57
95
 
58
96
  if (hasExpire || forceRefresh) {
59
- TokenRefreshListener.dispatch(projectUrl);
97
+ notifyAuthReady();
60
98
  return rizz();
61
99
  } else {
62
- TokenRefreshListener.dispatch(projectUrl, 'ready');
100
+ notifyAuthReady('ready');
63
101
  Scoped.TokenRefreshTimer[projectUrl] = setInterval(() => {
64
102
  const countdown = expireOn - getPrefferTime();
65
103
  if (countdown > 3000) return;
66
104
  clearInterval(Scoped.TokenRefreshTimer[projectUrl]);
67
- TokenRefreshListener.dispatch(projectUrl);
105
+ notifyAuthReady();
68
106
  rizz();
69
107
  }, 3000);
70
108
  }
71
109
  } else if (forceRefresh) {
72
- TokenRefreshListener.dispatch(projectUrl, 'ready');
73
- return simplifyError('no_token_yet', 'No token is available to initiate a refresh').simpleError
110
+ notifyAuthReady('ready');
111
+ return simplifyError('no_token_yet', 'No token is available to initiate a refresh').simpleError;
74
112
  }
75
113
  };
76
114
 
115
+ export const getEmulatedLinks = (projectUrl) => Object.entries(CacheStore.EmulatedAuth)
116
+ .filter(([_, v]) => v === projectUrl)
117
+ .map(v => v[0]);
118
+
77
119
  const refreshToken = (builder, processRef, remainRetries = 7, initialRetries = 7, isForceRefresh) => new Promise(async (resolve, reject) => {
78
120
  const { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders } = builder;
79
121
  const lostProcess = simplifyError('process_lost', 'The token refresh process has been lost and replaced with another one');
@@ -107,8 +149,18 @@ const refreshToken = (builder, processRef, remainRetries = 7, initialRetries = 7
107
149
  Scoped.AuthJWTToken[projectUrl] = f.result.token;
108
150
 
109
151
  resolve(f.result.token);
110
- triggerAuthToken(projectUrl, !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh);
152
+ const isInit = !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh;
153
+
154
+ triggerAuthToken(projectUrl, isInit);
111
155
  if (isForceRefresh) Scoped.InitiatedForcedToken[projectUrl] = true;
156
+
157
+ getEmulatedLinks(projectUrl).forEach(v => {
158
+ CacheStore.AuthStore[v] = cloneDeep(CacheStore.AuthStore[projectUrl]);
159
+ Scoped.AuthJWTToken[v] = f.result.token;
160
+
161
+ triggerAuthToken(v, isInit);
162
+ if (isForceRefresh) Scoped.InitiatedForcedToken[v] = true;
163
+ });
112
164
  updateCacheStore();
113
165
  initTokenRefresher(builder);
114
166
  } else reject(lostProcess.simpleError);
@@ -3,19 +3,28 @@ import EngineApi from "../../helpers/engine_api";
3
3
  import { TokenRefreshListener } from "../../helpers/listeners";
4
4
  import { awaitReachableServer, awaitStore, buildFetchInterface, buildFetchResult, updateCacheStore } from "../../helpers/utils";
5
5
  import { CacheStore, Scoped } from "../../helpers/variables";
6
- import { awaitRefreshToken, initTokenRefresher, injectFreshToken, listenToken, parseToken, triggerAuthToken } from "./accessor";
6
+ import { awaitRefreshToken, getEmulatedLinks, initTokenRefresher, injectEmulatedAuth, injectFreshToken, listenToken, parseToken, triggerAuthToken } from "./accessor";
7
7
  import { deserializeE2E, encodeBinary, serializeE2E } from "../../helpers/peripherals";
8
8
  import { simplifyCaughtError, simplifyError } from "simplify-error";
9
+ import cloneDeep from "lodash.clonedeep";
10
+
11
+ // purge residue tokens
12
+ awaitStore().then(() => {
13
+ Object.keys(CacheStore.PendingAuthPurge).forEach(k => {
14
+ purgePendingToken(k);
15
+ });
16
+ });
9
17
 
10
18
  const {
11
19
  _listenUserVerification,
12
20
  _signOut,
13
21
  _customSignin,
14
22
  _customSignup,
15
- _googleSignin
23
+ _googleSignin,
24
+ _areYouOk
16
25
  } = EngineApi;
17
26
 
18
- export class MTAuth {
27
+ export default class MTAuth {
19
28
  constructor(config) {
20
29
  this.builder = { ...config };
21
30
  }
@@ -164,6 +173,10 @@ export class MTAuth {
164
173
  signOut = () => doSignOut(this.builder);
165
174
 
166
175
  forceRefreshToken = () => initTokenRefresher(this.builder, true);
176
+
177
+ emulate = async (projectUrl) => {
178
+ await injectEmulatedAuth(this.builder, projectUrl);
179
+ }
167
180
  };
168
181
 
169
182
  const doCustomSignin = (builder, email, password) => new Promise(async (resolve, reject) => {
@@ -171,6 +184,8 @@ const doCustomSignin = (builder, email, password) => new Promise(async (resolve,
171
184
 
172
185
  try {
173
186
  await awaitStore();
187
+ const thisAuthStore = cloneDeep(CacheStore.AuthStore[projectUrl]);
188
+
174
189
  const [reqBuilder, [privateKey]] = await buildFetchInterface({
175
190
  body: { data: `${encodeBinary(email)}.${encodeBinary(password)}` },
176
191
  accessKey,
@@ -189,6 +204,7 @@ const doCustomSignin = (builder, email, password) => new Promise(async (resolve,
189
204
  refreshToken: r.result.refreshToken
190
205
  });
191
206
  await injectFreshToken(builder, r.result);
207
+ revokeAuthIntance(builder, thisAuthStore);
192
208
  } catch (e) {
193
209
  reject(simplifyCaughtError(e).simpleError);
194
210
  }
@@ -199,6 +215,8 @@ const doCustomSignup = (builder, email, password, name, metadata) => new Promise
199
215
 
200
216
  try {
201
217
  await awaitStore();
218
+ const thisAuthStore = cloneDeep(CacheStore.AuthStore[projectUrl]);
219
+
202
220
  const [reqBuilder, [privateKey]] = await buildFetchInterface({
203
221
  body: {
204
222
  data: `${encodeBinary(email)}.${encodeBinary(password)}.${(encodeBinary((name || '').trim()))}`,
@@ -220,47 +238,88 @@ const doCustomSignup = (builder, email, password, name, metadata) => new Promise
220
238
  refreshToken: r.result.refreshToken
221
239
  });
222
240
  await injectFreshToken(builder, r.result);
241
+ revokeAuthIntance(builder, thisAuthStore);
223
242
  } catch (e) {
224
243
  reject(simplifyCaughtError(e).simpleError);
225
244
  }
226
245
  });
227
246
 
228
- export const clearCacheForSignout = (builder) => {
229
- const { projectUrl } = builder;
230
- if (Scoped.AuthJWTToken[projectUrl]) delete Scoped.AuthJWTToken[projectUrl];
247
+ const purgeCache = (url, isMain) => {
248
+ if (url in Scoped.AuthJWTToken) delete Scoped.AuthJWTToken[url];
231
249
  Object.keys(CacheStore).forEach(e => {
232
- if (CacheStore[e][projectUrl]) delete CacheStore[e][projectUrl];
250
+ if (
251
+ e !== 'PendingAuthPurge' &&
252
+ (!['EmulatedAuth'].includes(e) || isMain)
253
+ ) {
254
+ if (CacheStore[e][url]) delete CacheStore[e][url];
255
+ }
233
256
  });
234
- TokenRefreshListener.dispatch(projectUrl);
235
- triggerAuthToken(projectUrl);
257
+ TokenRefreshListener.dispatch(url);
258
+ triggerAuthToken(url);
259
+ };
260
+
261
+ const clearCacheForSignout = (builder, disposeEmulated) => {
262
+ const { projectUrl } = builder;
263
+
264
+ purgeCache(projectUrl, true);
265
+ if (disposeEmulated) getEmulatedLinks(projectUrl).forEach(e => purgeCache(e));
236
266
  initTokenRefresher(builder);
237
267
  };
238
268
 
239
269
  export const doSignOut = async (builder) => {
240
270
  await awaitStore();
271
+ const emulatedURL = CacheStore.EmulatedAuth[builder.projectUrl];
241
272
 
242
- const { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders } = builder,
243
- { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
244
-
245
- clearCacheForSignout(builder);
273
+ clearCacheForSignout(builder, !emulatedURL);
246
274
  updateCacheStore(0);
275
+ if (emulatedURL) return;
276
+ await revokeAuthIntance(builder);
277
+ };
278
+
279
+ export const revokeAuthIntance = async (builder, authStore) => {
280
+ const { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders } = builder;
281
+ const { token, refreshToken: r_token } = { ...authStore };
282
+
283
+ if (!r_token || CacheStore.EmulatedAuth[projectUrl]) return;
284
+ const nodeId = `${Math.random()}`;
285
+
286
+ CacheStore.PendingAuthPurge[nodeId] = {
287
+ auth: { token, refreshToken: r_token },
288
+ data: { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders }
289
+ };
290
+ await purgePendingToken(nodeId);
291
+ };
247
292
 
248
- if (token) {
293
+ const purgePendingToken = async (nodeId) => {
294
+ const {
295
+ auth: { token, refreshToken: r_token },
296
+ data: { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders }
297
+ } = CacheStore.PendingAuthPurge[nodeId];
298
+
299
+ if (!token) return;
300
+ try {
301
+ let isConnected;
249
302
  try {
303
+ isConnected = (await (await fetch(_areYouOk(projectUrl))).json()).status === 'yes';
304
+ } catch (_) { }
305
+
306
+ if (!isConnected)
250
307
  await awaitReachableServer(projectUrl);
251
308
 
252
- const [reqBuilder] = await buildFetchInterface({
253
- body: { token, r_token },
254
- accessKey,
255
- uglify,
256
- serverE2E_PublicKey,
257
- extraHeaders
258
- });
309
+ const [reqBuilder] = await buildFetchInterface({
310
+ body: { token, r_token },
311
+ accessKey,
312
+ uglify,
313
+ serverE2E_PublicKey,
314
+ extraHeaders
315
+ });
259
316
 
260
- await buildFetchResult(await fetch(_signOut(projectUrl, uglify), reqBuilder), uglify);
261
- } catch (e) {
262
- throw simplifyCaughtError(e).simpleError;
263
- }
317
+ await buildFetchResult(await fetch(_signOut(projectUrl, uglify), reqBuilder), uglify);
318
+ } catch (e) {
319
+ throw simplifyCaughtError(e).simpleError;
320
+ } finally {
321
+ delete CacheStore.PendingAuthPurge[nodeId];
322
+ updateCacheStore(0);
264
323
  }
265
324
  };
266
325
 
@@ -269,6 +328,8 @@ const doGoogleSignin = (builder, token) => new Promise(async (resolve, reject) =
269
328
 
270
329
  try {
271
330
  await awaitStore();
331
+ const thisAuthStore = cloneDeep(CacheStore.AuthStore[projectUrl]);
332
+
272
333
  const [reqBuilder, [privateKey]] = await buildFetchInterface({
273
334
  body: { token },
274
335
  accessKey,
@@ -288,6 +349,7 @@ const doGoogleSignin = (builder, token) => new Promise(async (resolve, reject) =
288
349
  isNewUser: f.result.isNewUser
289
350
  });
290
351
  await injectFreshToken(builder, f.result);
352
+ revokeAuthIntance(builder, thisAuthStore);
291
353
  } catch (e) {
292
354
  reject(simplifyCaughtError(e).simpleError);
293
355
  }
@@ -353,7 +353,7 @@ export const syncCache = (builder, result) => {
353
353
  });
354
354
  });
355
355
  if (!scrapDocs.length) {
356
- const scrapYard = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data']);
356
+ const scrapYard = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data'], {});
357
357
  Object.values(scrapYard).forEach(v => {
358
358
  v.listing.forEach(doc => {
359
359
  if (confirmFilterDoc(doc, find || findOne)) {