mosquito-transport-js 0.4.1 → 0.4.2

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/TODO CHANGED
@@ -1,9 +1,23 @@
1
- - fix local cache query on sqlite and fs
1
+ - fix local cache query on sqlite
2
2
  - fix and add all mongodb query and update operator
3
3
  - reauthenticate
4
4
  - change `Object` in d.ts to [key: string]: any
5
5
  - add `getServerTimeOffset` method
6
- - change null to undefined in `value`
7
- - `provide functionality to add extra header to MT instance (all outgoing request)`
6
+ - change undefined to null in `value`
7
+ - `provide functionality to add extra header to MT instance (all outgoing request)`
8
8
  - borrowToken
9
- - `add sqlite`
9
+ - `add sqlite`
10
+ - minimize extraction data ✅
11
+ - change `collection().onDisconnect()` to `collection().socket().onDisconnect()` and `collection().socket().onConnect()` ✅
12
+ - add `_foreign_doc` to d.ts ✅
13
+ - tree shake dependencies
14
+ - dynamic import for fs ✅
15
+ - new URL() work around ✅
16
+ - fetchHttp, default retrieval if has body ✅
17
+ - native hashing
18
+ - TextEncoder
19
+ - native storage upload
20
+ - lodashes ✅
21
+ - switch to events package
22
+ - serverTimeOffset
23
+ <!-- - error: "refreshToken retry limit exceeded" <--- no need -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mosquito-transport-js",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Javascript web sdk for mosquito-transport (https://github.com/brainbehindx/mosquito-transport)",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -31,14 +31,10 @@
31
31
  "@turf/turf": "^7.1.0",
32
32
  "bson": "^6.8.0",
33
33
  "buffer": "^6.0.3",
34
- "crypto-js": "^4.2.0",
35
34
  "entity-serializer": "^1.0.2",
36
35
  "guard-object": "^1.1.3",
37
36
  "limit-task": "1.0.0",
38
- "lodash.clonedeep": "^4.5.0",
39
- "lodash.get": "^4.4.2",
40
- "lodash.set": "^4.3.2",
41
- "lodash.unset": "^4.5.2",
37
+ "lodash": "^4.17.21",
42
38
  "simplify-error": "^1.0.1",
43
39
  "socket.io-client": "^4.6.2",
44
40
  "subscription-listener": "^1.1.2",
@@ -57,4 +53,4 @@
57
53
  "engines": {
58
54
  "node": ">=14"
59
55
  }
60
- }
56
+ }
@@ -1,12 +1,7 @@
1
1
  import { encodeBinary } from './peripherals';
2
2
 
3
- const EngineApiBase = (baseApi, ugly, path) => {
4
- const url = new URL(baseApi);
5
- if (ugly) {
6
- url.pathname = `/e2e/${encodeBinary(path)}`;
7
- } else url.pathname = path;
8
- return url.href;
9
- };
3
+ const EngineApiBase = (baseApi, ugly, path) =>
4
+ ugly ? `${baseApi}/e2e/${encodeBinary(path)}` : `${baseApi}/${path}`;
10
5
 
11
6
  const apis = {
12
7
  _readDocument: (baseApi, ugly) => EngineApiBase(baseApi, ugly, '_readDocument'),
@@ -1,13 +1,10 @@
1
1
  import { Buffer } from "buffer";
2
2
  import { ServerReachableListener } from "./listeners";
3
- import aes_pkg from 'crypto-js/aes.js';
4
- import Utf8Encoder from 'crypto-js/enc-utf8.js';
5
3
  import naclPkg from 'tweetnacl-functional';
6
- import getLodash from "lodash.get";
4
+ import getLodash from "lodash/get";
7
5
  import e2e_worker from "./e2e_worker";
8
6
  import { deserialize, serialize } from "entity-serializer";
9
7
 
10
- const { encrypt, decrypt } = aes_pkg;
11
8
  const { box, randomBytes } = naclPkg;
12
9
 
13
10
  export const listenReachableServer = (callback, projectUrl) => {
@@ -43,7 +40,7 @@ export const normalizeRoute = (route = '') => route.split('').map((v, i, a) =>
43
40
  ).join('');
44
41
 
45
42
  export const shuffleArray = (n) => {
46
- const array = [...n];
43
+ const array = n.slice(0);
47
44
  let currentIndex = array.length, randomIndex;
48
45
 
49
46
  while (currentIndex != 0) {
@@ -67,7 +64,7 @@ export function sortArrayByObjectKey(arr = [], key) {
67
64
  });
68
65
  };
69
66
 
70
- export async function niceHash(str) {
67
+ export async function niceHash(str = '') {
71
68
  try {
72
69
  // Convert the string to a Uint8Array
73
70
  const encoder = new TextEncoder();
@@ -96,14 +93,6 @@ export const sameInstance = (var1, var2) => {
96
93
  }
97
94
  };
98
95
 
99
- export const encryptString = (txt, password, iv) => {
100
- return encrypt(txt, `${password || ''}${iv || ''}`).toString();
101
- };
102
-
103
- export const decryptString = (txt, password, iv) => {
104
- return decrypt(txt, `${password || ''}${iv || ''}`).toString(Utf8Encoder);
105
- };
106
-
107
96
  export const serializeE2E = async (data, auth_token, serverPublicKey) => {
108
97
  const inputData = serialize([data, auth_token]);
109
98
 
@@ -0,0 +1,136 @@
1
+ import { Validator } from "guard-object";
2
+ import { incrementDatabaseSizeCore } from "../products/database/counter";
3
+ import { incrementFetcherSizeCore } from "../products/http_callable/counter";
4
+ import unsetLodash from 'lodash/unset';
5
+
6
+ export const purgeRedundantRecords = async (data, builder) => {
7
+ const { maxLocalDatabaseSize = 10485760, maxLocalFetchHttpSize = 10485760 } = builder;
8
+
9
+ /**
10
+ * @type {import('./variables')['CacheStore']['DatabaseStats']}
11
+ */
12
+ const { _db_size, _fetcher_size } = data.DatabaseStats || {};
13
+
14
+ const purgeDatabase = () => {
15
+ if (!Validator.POSITIVE_NUMBER(_db_size) || !maxLocalDatabaseSize || _db_size < maxLocalDatabaseSize) return;
16
+ const DbListing = [];
17
+
18
+ breakDbMap(data.DatabaseStore, (projectUrl, dbUrl, dbName, path, value) => {
19
+ Object.entries(value.instance).forEach(([access_id, obj]) => {
20
+ DbListing.push({
21
+ builder: { projectUrl, dbUrl, dbName },
22
+ path,
23
+ access_id,
24
+ value: obj
25
+ });
26
+ });
27
+ Object.entries(value.episode).forEach(([access_id, limitObj]) => {
28
+ Object.entries(limitObj).forEach(([limit, obj]) => {
29
+ DbListing.push({
30
+ builder: { projectUrl, dbUrl, dbName },
31
+ path,
32
+ access_id,
33
+ limit,
34
+ value: obj,
35
+ isEpisode: true
36
+ });
37
+ });
38
+ });
39
+ });
40
+
41
+ breakDbMap(data.DatabaseCountResult, (projectUrl, dbUrl, dbName, path, value) => {
42
+ Object.entries(value).forEach(([access_id, obj]) => {
43
+ DbListing.push({
44
+ builder: { projectUrl, dbUrl, dbName },
45
+ path,
46
+ access_id,
47
+ value: obj,
48
+ isCount: true
49
+ });
50
+ });
51
+ });
52
+
53
+ const redundantDbRanking = DbListing.sort((a, b) =>
54
+ a.value.touched - b.value.touched
55
+ );
56
+
57
+ const newSize = maxLocalDatabaseSize / 2;
58
+ let sizer = _db_size;
59
+ let cuts = 0;
60
+
61
+ for (let i = 0; i < redundantDbRanking.length; i++) {
62
+ sizer -= redundantDbRanking[i].value.size || 0;
63
+ ++cuts;
64
+ if (sizer < newSize) break;
65
+ }
66
+
67
+ console.warn(`purging ${cuts} of ${redundantDbRanking.length} db entities`);
68
+ redundantDbRanking.slice(0, cuts).forEach(({
69
+ builder,
70
+ path,
71
+ access_id,
72
+ isCount,
73
+ isEpisode,
74
+ limit,
75
+ value: { size }
76
+ }) => {
77
+ const { projectUrl, dbUrl, dbName } = builder;
78
+ if (isCount) {
79
+ unsetLodash(data.DatabaseCountResult, [projectUrl, dbUrl, dbName, path, access_id]);
80
+ } else {
81
+ incrementDatabaseSizeCore(data.DatabaseStats, builder, path, -size);
82
+ if (isEpisode) {
83
+ unsetLodash(data.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'episode', access_id, limit]);
84
+ } else {
85
+ unsetLodash(data.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'instance', access_id]);
86
+ }
87
+ }
88
+ });
89
+ }
90
+ const purgeFetcher = () => {
91
+ if (!Validator.POSITIVE_NUMBER(_fetcher_size) || !maxLocalFetchHttpSize || _fetcher_size < maxLocalFetchHttpSize) return;
92
+ const redundantFetchRanking = Object.entries(data.FetchedStore).map(([projectUrl, access_id_Obj]) =>
93
+ Object.entries(access_id_Obj).map(([access_id, data]) => ({
94
+ access_id,
95
+ projectUrl,
96
+ data
97
+ }))
98
+ ).flat().sort(([a], [b]) =>
99
+ a.data.touched - b.data.touched
100
+ );
101
+
102
+ const newSize = maxLocalFetchHttpSize / 2;
103
+ let sizer = _fetcher_size;
104
+ let cuts = 0;
105
+
106
+ for (let i = 0; i < redundantFetchRanking.length; i++) {
107
+ sizer -= redundantFetchRanking[i].data.size || 0;
108
+ ++cuts;
109
+ if (sizer < newSize) break;
110
+ }
111
+
112
+ console.warn(`purging ${cuts} of ${redundantFetchRanking.length} fetcher entities`);
113
+ redundantFetchRanking.slice(0, cuts).forEach(({ access_id, data: { size }, projectUrl }) => {
114
+ incrementFetcherSizeCore(data.DatabaseStats, projectUrl, -size);
115
+ unsetLodash(data.FetchedStore, [projectUrl, access_id]);
116
+ });
117
+ console.log('fetcher purging complete');
118
+ }
119
+ purgeDatabase();
120
+ purgeFetcher();
121
+ }
122
+
123
+ const breakDbMap = (obj, callback) =>
124
+ Object.entries(obj).forEach(([projectUrl, dbUrlObj]) => {
125
+ Object.entries(dbUrlObj).forEach(([dbUrl, dbNameObj]) => {
126
+ Object.entries(dbNameObj).forEach(([dbName, pathObj]) => {
127
+ Object.entries(pathObj).forEach(([path, value]) => {
128
+ callback(projectUrl, dbUrl, dbName, path, value);
129
+ });
130
+ });
131
+ });
132
+ });
133
+
134
+ export const purgeInstance = (projectUrl) => {
135
+ // TODO: purge when signed-out
136
+ }
@@ -1,76 +1,74 @@
1
1
  import { ServerReachableListener, StoreReadyListener } from "./listeners";
2
- import { CACHE_PROTOCOL, CACHE_STORAGE_PATH, DEFAULT_CACHE_PASSWORD } from "./values";
2
+ import { CACHE_STORAGE_PATH } from "./values";
3
3
  import { CacheStore, Scoped } from "./variables";
4
- import { decryptString, encryptString, serializeE2E } from "./peripherals";
4
+ import { serializeE2E } from "./peripherals";
5
5
  import { deserializeBSON, serializeToBase64 } from "../products/database/bson";
6
6
  import { trySendPendingWrite } from "../products/database";
7
7
  import { deserialize } from "entity-serializer";
8
+ import { purgeRedundantRecords } from "./purger";
8
9
 
9
- export const updateCacheStore = (timer = 300) => {
10
+ export const updateCacheStore = (timer = 300, node) => {
10
11
  try { window } catch (_) { return; }
11
- const {
12
- cachePassword = DEFAULT_CACHE_PASSWORD,
13
- cacheProtocol = CACHE_PROTOCOL.LOCAL_STORAGE,
14
- io,
15
- promoteCache
16
- } = Scoped.ReleaseCacheData;
17
12
 
18
- clearTimeout(Scoped.cacheStorageReducer);
19
- Scoped.cacheStorageReducer = setTimeout(() => {
20
- const { AuthStore, EmulatedAuth, PendingAuthPurge, DatabaseStore, PendingWrites, ...restStore } = CacheStore;
21
-
22
- const txt = encryptString(
23
- JSON.stringify({
24
- AuthStore,
25
- EmulatedAuth,
26
- PendingAuthPurge,
27
- ...promoteCache ? {
28
- DatabaseStore: serializeToBase64(DatabaseStore),
29
- PendingWrites: serializeToBase64(PendingWrites)
30
- } : {},
31
- ...promoteCache ? restStore : {}
32
- }),
33
- cachePassword,
34
- cachePassword
35
- );
13
+ const { io, promoteCache } = Scoped.ReleaseCacheData;
14
+
15
+ const doUpdate = async () => {
16
+ const {
17
+ AuthStore,
18
+ EmulatedAuth,
19
+ PendingAuthPurge,
20
+ DatabaseStore,
21
+ PendingWrites,
22
+ ...restStore
23
+ } = CacheStore;
24
+
25
+ const txt = JSON.stringify({
26
+ AuthStore,
27
+ EmulatedAuth,
28
+ PendingAuthPurge,
29
+ ...promoteCache ? {
30
+ DatabaseStore: serializeToBase64(DatabaseStore),
31
+ PendingWrites: serializeToBase64(PendingWrites)
32
+ } : {},
33
+ ...promoteCache ? restStore : {}
34
+ });
36
35
 
37
36
  if (io) {
38
- io.output(txt);
39
- } else if (cacheProtocol === CACHE_PROTOCOL.LOCAL_STORAGE) {
40
- window.localStorage.setItem(CACHE_STORAGE_PATH, txt);
41
- }
42
- }, timer);
37
+ io.output(txt, node);
38
+ } else window.localStorage.setItem(CACHE_STORAGE_PATH, txt);
39
+ };
40
+
41
+ clearTimeout(Scoped.cacheStorageReducer);
42
+ if (timer) {
43
+ Scoped.cacheStorageReducer = setTimeout(doUpdate, timer);
44
+ } else doUpdate();
43
45
  };
44
46
 
45
47
  export const releaseCacheStore = async (builder) => {
46
48
  try { window } catch (_) { return; }
47
- const {
48
- cachePassword = DEFAULT_CACHE_PASSWORD,
49
- cacheProtocol = CACHE_PROTOCOL.LOCAL_STORAGE,
50
- io
51
- } = builder;
52
-
53
- let txt;
54
-
55
- if (io) {
56
- txt = await io.input();
57
- } else if (cacheProtocol === CACHE_PROTOCOL.LOCAL_STORAGE) {
58
- txt = window.localStorage.getItem(CACHE_STORAGE_PATH);
59
- }
49
+ const { io } = builder;
50
+
51
+ let data = {};
60
52
 
61
- const j = JSON.parse(decryptString(txt || '', cachePassword, cachePassword) || '{}');
62
- const projectList = new Set([]);
53
+ try {
54
+ if (io) {
55
+ data = await io.input();
56
+ } else data = window.localStorage.getItem(CACHE_STORAGE_PATH);
57
+ data = JSON.parse(data || '{}');
58
+ await purgeRedundantRecords(data, builder);
59
+ } catch (error) {
60
+ console.error('releaseCacheStore err:', error);
61
+ }
63
62
 
64
- Object.entries(j).forEach(([k, v]) => {
63
+ Object.entries(data).forEach(([k, v]) => {
65
64
  if (['DatabaseStore', 'PendingWrites'].includes(k)) {
66
65
  CacheStore[k] = deserializeBSON(v);
67
66
  } else CacheStore[k] = v;
68
- projectList.add(k);
69
67
  });
70
68
  Object.entries(CacheStore.AuthStore).forEach(([key, value]) => {
71
69
  Scoped.AuthJWTToken[key] = value?.token;
72
70
  });
73
- projectList.forEach(projectUrl => {
71
+ Object.keys(CacheStore.PendingWrites).forEach(projectUrl => {
74
72
  if (Scoped.IS_CONNECTED[projectUrl])
75
73
  trySendPendingWrite(projectUrl);
76
74
  });
@@ -119,18 +117,17 @@ export const getReachableServer = (projectUrl) => new Promise(resolve => {
119
117
  }, true);
120
118
  });
121
119
 
122
- export const buildFetchInterface = async ({ body, accessKey, authToken, method, uglify, serverE2E_PublicKey, extraHeaders }) => {
120
+ export const buildFetchInterface = async ({ body, authToken, method, uglify, serverE2E_PublicKey, extraHeaders }) => {
123
121
  if (!uglify) body = JSON.stringify({ ...body });
124
122
  const [plate, keyPair] = uglify ? await serializeE2E(body, authToken, serverE2E_PublicKey) : [undefined, []];
125
123
 
126
124
  return [{
127
125
  body: uglify ? plate : body,
128
- cache: 'no-cache',
126
+ // cache: 'no-cache',
129
127
  headers: {
128
+ ...extraHeaders,
130
129
  'Content-type': uglify ? 'request/buffer' : 'application/json',
131
- 'Authorization': accessKey,
132
- ...(authToken && !uglify) ? { 'Mosquito-Token': authToken } : {},
133
- ...extraHeaders
130
+ ...(authToken && !uglify) ? { 'Mosquito-Token': authToken } : {}
134
131
  },
135
132
  method: method || 'POST'
136
133
  }, keyPair];
@@ -1,17 +1,13 @@
1
1
  import { encodeBinary } from './peripherals';
2
2
 
3
- export const CACHE_STORAGE_PATH = encodeBinary('MOSQUITO_TRANSPORT_FREEZER'),
4
- DEFAULT_CACHE_PASSWORD = encodeBinary('MOSQUITO_TRANSPORT_CACHE_PASSWORD');
5
-
6
- export const CACHE_PROTOCOL = {
7
- LOCAL_STORAGE: 'local-storage'
8
- };
3
+ export const CACHE_STORAGE_PATH = encodeBinary('MOSQUITO_TRANSPORT');
9
4
 
10
5
  export const RETRIEVAL = {
11
6
  STICKY: 'sticky',
12
7
  STICKY_NO_AWAIT: 'sticky-no-await',
13
8
  STICKY_RELOAD: 'sticky-reload',
14
9
  DEFAULT: 'default',
10
+ CACHE_AWAIT: 'cache-await',
15
11
  CACHE_NO_AWAIT: 'cache-no-await',
16
12
  NO_CACHE_NO_AWAIT: 'no-cache-no-await',
17
13
  NO_CACHE_AWAIT: 'no-cache-await'
@@ -19,11 +15,10 @@ export const RETRIEVAL = {
19
15
 
20
16
  export const DELIVERY = {
21
17
  DEFAULT: 'default',
22
- NO_CACHE: 'no-cache',
23
- NO_AWAIT: 'no-await',
24
- NO_AWAIT_NO_CACHE: 'no-await-no-cache',
25
- AWAIT_NO_CACHE: 'await-no-cache',
26
- CACHE_NO_AWAIT: 'cache-no-await'
18
+ CACHE_AWAIT: 'cache-await',
19
+ CACHE_NO_AWAIT: 'cache-no-await',
20
+ NO_CACHE_NO_AWAIT: 'no-cache-no-await',
21
+ NO_CACHE_AWAIT: 'no-cache-await'
27
22
  };
28
23
 
29
24
  export const AUTH_PROVIDER_ID = {
@@ -14,14 +14,8 @@ export const Scoped = {
14
14
  LastTokenRefreshRef: {},
15
15
  StorageProcessID: 0,
16
16
  InitiatedForcedToken: {},
17
- PendingFetchCollective: {
18
- pendingProcess: {},
19
- pendingResolution: {}
20
- },
21
- PendingDbReadCollective: {
22
- pendingProcess: {},
23
- pendingResolution: {}
24
- },
17
+ PendingFetchCollective: {},
18
+ PendingDbReadCollective: {},
25
19
  ActiveDatabaseListeners: {},
26
20
  OutgoingWrites: {},
27
21
  /**
@@ -34,7 +28,22 @@ export const Scoped = {
34
28
  export const CacheStore = {
35
29
  DatabaseStore: {},
36
30
  DatabaseCountResult: {},
37
- DatabaseStats: {},
31
+ DatabaseStats: {
32
+ /**
33
+ * @type {{[projectUrl: string]: {[dbUrl: string]: {[dbName: string]: {[path: string]: number}}}}}
34
+ */
35
+ database: {},
36
+ /**
37
+ * @type {{[projectUrl: string]: {[dbUrl: string]: {[dbName: string]: {[path: string]: boolean}}}}}
38
+ */
39
+ counters: {},
40
+ /**
41
+ * @type {{[projectUrl: string]: number}}
42
+ */
43
+ fetchers: {},
44
+ _db_size: 0,
45
+ _fetcher_size: 0
46
+ },
38
47
  AuthStore: {},
39
48
  PendingAuthPurge: {},
40
49
  EmulatedAuth: {},