mosquito-transport-js 0.3.5 → 0.3.7

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.5",
4
- "description": "Javascript web sdk for mosquito-transport (https://github.com/deflexable/mosquito-transport)",
3
+ "version": "0.3.7",
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": {
@@ -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";
2
+ import { awaitStore, releaseCacheStore } from "./helpers/utils";
3
3
  import { CacheStore, Scoped } from "./helpers/variables";
4
- import { MTAuth } from "./products/auth";
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, { purgePendingToken } 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,11 +128,17 @@ 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 };
135
135
  releaseCacheStore({ ...prop });
136
+ // purge residue tokens
137
+ awaitStore().then(() => {
138
+ Object.keys(CacheStore.PendingAuthPurge).forEach(k => {
139
+ purgePendingToken(k);
140
+ });
141
+ });
136
142
  }
137
143
 
138
144
  getDatabase = (dbName, dbUrl) => ({
@@ -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,21 @@ 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";
9
10
 
10
11
  const {
11
12
  _listenUserVerification,
12
13
  _signOut,
13
14
  _customSignin,
14
15
  _customSignup,
15
- _googleSignin
16
+ _googleSignin,
17
+ _areYouOk
16
18
  } = EngineApi;
17
19
 
18
- export class MTAuth {
20
+ export default class MTAuth {
19
21
  constructor(config) {
20
22
  this.builder = { ...config };
21
23
  }
@@ -164,6 +166,10 @@ export class MTAuth {
164
166
  signOut = () => doSignOut(this.builder);
165
167
 
166
168
  forceRefreshToken = () => initTokenRefresher(this.builder, true);
169
+
170
+ emulate = async (projectUrl) => {
171
+ await injectEmulatedAuth(this.builder, projectUrl);
172
+ }
167
173
  };
168
174
 
169
175
  const doCustomSignin = (builder, email, password) => new Promise(async (resolve, reject) => {
@@ -171,6 +177,8 @@ const doCustomSignin = (builder, email, password) => new Promise(async (resolve,
171
177
 
172
178
  try {
173
179
  await awaitStore();
180
+ const thisAuthStore = cloneDeep(CacheStore.AuthStore[projectUrl]);
181
+
174
182
  const [reqBuilder, [privateKey]] = await buildFetchInterface({
175
183
  body: { data: `${encodeBinary(email)}.${encodeBinary(password)}` },
176
184
  accessKey,
@@ -189,6 +197,7 @@ const doCustomSignin = (builder, email, password) => new Promise(async (resolve,
189
197
  refreshToken: r.result.refreshToken
190
198
  });
191
199
  await injectFreshToken(builder, r.result);
200
+ revokeAuthIntance(builder, thisAuthStore);
192
201
  } catch (e) {
193
202
  reject(simplifyCaughtError(e).simpleError);
194
203
  }
@@ -199,6 +208,8 @@ const doCustomSignup = (builder, email, password, name, metadata) => new Promise
199
208
 
200
209
  try {
201
210
  await awaitStore();
211
+ const thisAuthStore = cloneDeep(CacheStore.AuthStore[projectUrl]);
212
+
202
213
  const [reqBuilder, [privateKey]] = await buildFetchInterface({
203
214
  body: {
204
215
  data: `${encodeBinary(email)}.${encodeBinary(password)}.${(encodeBinary((name || '').trim()))}`,
@@ -220,47 +231,88 @@ const doCustomSignup = (builder, email, password, name, metadata) => new Promise
220
231
  refreshToken: r.result.refreshToken
221
232
  });
222
233
  await injectFreshToken(builder, r.result);
234
+ revokeAuthIntance(builder, thisAuthStore);
223
235
  } catch (e) {
224
236
  reject(simplifyCaughtError(e).simpleError);
225
237
  }
226
238
  });
227
239
 
228
- export const clearCacheForSignout = (builder) => {
229
- const { projectUrl } = builder;
230
- if (Scoped.AuthJWTToken[projectUrl]) delete Scoped.AuthJWTToken[projectUrl];
240
+ const purgeCache = (url, isMain) => {
241
+ if (url in Scoped.AuthJWTToken) delete Scoped.AuthJWTToken[url];
231
242
  Object.keys(CacheStore).forEach(e => {
232
- if (CacheStore[e][projectUrl]) delete CacheStore[e][projectUrl];
243
+ if (
244
+ e !== 'PendingAuthPurge' &&
245
+ (!['EmulatedAuth'].includes(e) || isMain)
246
+ ) {
247
+ if (CacheStore[e][url]) delete CacheStore[e][url];
248
+ }
233
249
  });
234
- TokenRefreshListener.dispatch(projectUrl);
235
- triggerAuthToken(projectUrl);
250
+ TokenRefreshListener.dispatch(url);
251
+ triggerAuthToken(url);
252
+ };
253
+
254
+ const clearCacheForSignout = (builder, disposeEmulated) => {
255
+ const { projectUrl } = builder;
256
+
257
+ purgeCache(projectUrl, true);
258
+ if (disposeEmulated) getEmulatedLinks(projectUrl).forEach(e => purgeCache(e));
236
259
  initTokenRefresher(builder);
237
260
  };
238
261
 
239
262
  export const doSignOut = async (builder) => {
240
263
  await awaitStore();
264
+ const emulatedURL = CacheStore.EmulatedAuth[builder.projectUrl];
241
265
 
242
- const { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders } = builder,
243
- { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl];
244
-
245
- clearCacheForSignout(builder);
266
+ clearCacheForSignout(builder, !emulatedURL);
246
267
  updateCacheStore(0);
268
+ if (emulatedURL) return;
269
+ await revokeAuthIntance(builder);
270
+ };
271
+
272
+ export const revokeAuthIntance = async (builder, authStore) => {
273
+ const { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders } = builder;
274
+ const { token, refreshToken: r_token } = { ...authStore };
275
+
276
+ if (!r_token || CacheStore.EmulatedAuth[projectUrl]) return;
277
+ const nodeId = `${Math.random()}`;
247
278
 
248
- if (token) {
279
+ CacheStore.PendingAuthPurge[nodeId] = {
280
+ auth: { token, refreshToken: r_token },
281
+ data: { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders }
282
+ };
283
+ await purgePendingToken(nodeId);
284
+ };
285
+
286
+ export const purgePendingToken = async (nodeId) => {
287
+ const {
288
+ auth: { token, refreshToken: r_token },
289
+ data: { projectUrl, serverE2E_PublicKey, accessKey, uglify, extraHeaders }
290
+ } = CacheStore.PendingAuthPurge[nodeId];
291
+
292
+ if (!token) return;
293
+ try {
294
+ let isConnected;
249
295
  try {
296
+ isConnected = (await (await fetch(_areYouOk(projectUrl))).json(), { cache: 'no-cache' }).status === 'yes';
297
+ } catch (_) { }
298
+
299
+ if (!isConnected)
250
300
  await awaitReachableServer(projectUrl);
251
301
 
252
- const [reqBuilder] = await buildFetchInterface({
253
- body: { token, r_token },
254
- accessKey,
255
- uglify,
256
- serverE2E_PublicKey,
257
- extraHeaders
258
- });
302
+ const [reqBuilder] = await buildFetchInterface({
303
+ body: { token, r_token },
304
+ accessKey,
305
+ uglify,
306
+ serverE2E_PublicKey,
307
+ extraHeaders
308
+ });
259
309
 
260
- await buildFetchResult(await fetch(_signOut(projectUrl, uglify), reqBuilder), uglify);
261
- } catch (e) {
262
- throw simplifyCaughtError(e).simpleError;
263
- }
310
+ await buildFetchResult(await fetch(_signOut(projectUrl, uglify), reqBuilder), uglify);
311
+ } catch (e) {
312
+ throw simplifyCaughtError(e).simpleError;
313
+ } finally {
314
+ delete CacheStore.PendingAuthPurge[nodeId];
315
+ updateCacheStore(0);
264
316
  }
265
317
  };
266
318
 
@@ -269,6 +321,8 @@ const doGoogleSignin = (builder, token) => new Promise(async (resolve, reject) =
269
321
 
270
322
  try {
271
323
  await awaitStore();
324
+ const thisAuthStore = cloneDeep(CacheStore.AuthStore[projectUrl]);
325
+
272
326
  const [reqBuilder, [privateKey]] = await buildFetchInterface({
273
327
  body: { token },
274
328
  accessKey,
@@ -288,6 +342,7 @@ const doGoogleSignin = (builder, token) => new Promise(async (resolve, reject) =
288
342
  isNewUser: f.result.isNewUser
289
343
  });
290
344
  await injectFreshToken(builder, f.result);
345
+ revokeAuthIntance(builder, thisAuthStore);
291
346
  } catch (e) {
292
347
  reject(simplifyCaughtError(e).simpleError);
293
348
  }