mosquito-transport-js 0.3.0 → 0.3.1
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/package.json +4 -4
- package/src/helpers/e2e_worker.js +109 -0
- package/src/helpers/peripherals.js +32 -14
- package/src/index.d.ts +1 -1
- package/src/index.js +20 -4
- package/src/products/database/accessor.js +12 -26
- package/src/products/database/index.js +15 -9
- package/src/products/database/validator.js +26 -4
- package/src/products/http_callable/index.js +23 -12
- package/src/products/storage/index.js +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mosquito-transport-js",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Javascript web sdk for mosquito-transport (https://github.com/deflexable/mosquito-transport)",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -32,17 +32,17 @@
|
|
|
32
32
|
"bson": "^6.8.0",
|
|
33
33
|
"buffer": "^6.0.3",
|
|
34
34
|
"crypto-js": "^4.2.0",
|
|
35
|
-
"guard-object": "^1.1.
|
|
35
|
+
"guard-object": "^1.1.3",
|
|
36
|
+
"limit-task": "1.0.0",
|
|
36
37
|
"json-buffer": "^3.0.1",
|
|
37
38
|
"lodash.clonedeep": "^4.5.0",
|
|
38
39
|
"lodash.get": "^4.4.2",
|
|
39
40
|
"lodash.set": "^4.3.2",
|
|
40
41
|
"lodash.unset": "^4.5.2",
|
|
41
|
-
"set-large-timeout": "^1.0.1",
|
|
42
42
|
"simplify-error": "^1.0.1",
|
|
43
43
|
"socket.io-client": "^4.6.2",
|
|
44
44
|
"subscription-listener": "^1.1.2",
|
|
45
|
-
"tweetnacl": "^1.0.
|
|
45
|
+
"tweetnacl-functional": "^1.0.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@babel/core": "^7.24.6",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import LimitTask from "limit-task";
|
|
2
|
+
import naclPkg from 'tweetnacl-functional';
|
|
3
|
+
|
|
4
|
+
function e2e_baseCode() {
|
|
5
|
+
const serializeE2E = (data, serverPublicKey) => {
|
|
6
|
+
const pair = box.keyPair(),
|
|
7
|
+
nonce = randomBytes(box.nonceLength);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
data: box(data, nonce, serverPublicKey, pair.secretKey),
|
|
11
|
+
pair,
|
|
12
|
+
nonce
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const deserializeE2E = (data, nonce, serverPublicKey, clientPrivateKey) => {
|
|
17
|
+
const result = box.open(
|
|
18
|
+
data,
|
|
19
|
+
nonce,
|
|
20
|
+
serverPublicKey,
|
|
21
|
+
clientPrivateKey
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (!result) throw 'Decrypting e2e message failed';
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
self.onmessage = function (event) {
|
|
29
|
+
const { data } = event;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
let response;
|
|
33
|
+
if (data.type === 'encrypt') {
|
|
34
|
+
response = serializeE2E(...data.params);
|
|
35
|
+
} else if (data.type === 'decrypt') {
|
|
36
|
+
response = deserializeE2E(...data.params);
|
|
37
|
+
}
|
|
38
|
+
self.postMessage([response, data.session]);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
self.postMessage([undefined, undefined, { error }]);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const workerCode = `
|
|
46
|
+
${naclPkg.NACL.toString()}
|
|
47
|
+
const naclPkg = {};
|
|
48
|
+
|
|
49
|
+
NACL(naclPkg);
|
|
50
|
+
|
|
51
|
+
const { box, randomBytes } = naclPkg;
|
|
52
|
+
|
|
53
|
+
${e2e_baseCode.toString()}
|
|
54
|
+
e2e_baseCode();
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const spawnWorker = () => {
|
|
58
|
+
const resolution = {};
|
|
59
|
+
let ite = 0;
|
|
60
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
61
|
+
const worker = new Worker(URL.createObjectURL(blob));
|
|
62
|
+
|
|
63
|
+
// Receive the result from the worker
|
|
64
|
+
worker.onmessage = function (event) {
|
|
65
|
+
const [response, session, error] = event.data;
|
|
66
|
+
if (error) {
|
|
67
|
+
resolution[session][1](error.error);
|
|
68
|
+
} else {
|
|
69
|
+
resolution[session][0](response);
|
|
70
|
+
}
|
|
71
|
+
delete resolution[session];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const queue = LimitTask(3);
|
|
75
|
+
|
|
76
|
+
return (type, params) => queue(() =>
|
|
77
|
+
new Promise((resolve, reject) => {
|
|
78
|
+
const session = `${++ite}`;
|
|
79
|
+
|
|
80
|
+
resolution[session] = [resolve, reject];
|
|
81
|
+
worker.postMessage({ type, params, session });
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let currentTask = -1;
|
|
87
|
+
const e2e_engines = [];
|
|
88
|
+
|
|
89
|
+
const MAX_WORKERS = 7;
|
|
90
|
+
|
|
91
|
+
const addTask = (type, params) => {
|
|
92
|
+
++currentTask;
|
|
93
|
+
if (currentTask >= e2e_engines.length) {
|
|
94
|
+
if (e2e_engines.length < MAX_WORKERS) {
|
|
95
|
+
// spawn new engine
|
|
96
|
+
e2e_engines.push(spawnWorker());
|
|
97
|
+
} else currentTask = 0;
|
|
98
|
+
}
|
|
99
|
+
return e2e_engines[currentTask](type, params);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default {
|
|
103
|
+
encrypt: (bufferData, serverPublicKey) => {
|
|
104
|
+
return addTask('encrypt', [bufferData, serverPublicKey]);
|
|
105
|
+
},
|
|
106
|
+
decrypt: (data, nonce, serverPublicKey, clientPrivateKey) => {
|
|
107
|
+
return addTask('decrypt', [data, nonce, serverPublicKey, clientPrivateKey]);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
@@ -2,8 +2,9 @@ import { Buffer } from "buffer";
|
|
|
2
2
|
import { ServerReachableListener } from "./listeners";
|
|
3
3
|
import aes_pkg from 'crypto-js/aes.js';
|
|
4
4
|
import Utf8Encoder from 'crypto-js/enc-utf8.js';
|
|
5
|
-
import naclPkg from 'tweetnacl';
|
|
5
|
+
import naclPkg from 'tweetnacl-functional';
|
|
6
6
|
import getLodash from "lodash.get";
|
|
7
|
+
import e2e_worker from "./e2e_worker";
|
|
7
8
|
|
|
8
9
|
const { encrypt, decrypt } = aes_pkg;
|
|
9
10
|
const { box, randomBytes } = naclPkg;
|
|
@@ -36,6 +37,10 @@ export const niceTry = (promise) => new Promise(async resolve => {
|
|
|
36
37
|
} catch (e) { resolve(); }
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
export const normalizeRoute = (route = '') => route.split('').map((v, i, a) =>
|
|
41
|
+
((!i && v === '/') || (i === a.length - 1 && v === '/') || (i && a[i - 1] === '/' && v === '/')) ? '' : v
|
|
42
|
+
).join('');
|
|
43
|
+
|
|
39
44
|
export const shuffleArray = (n) => {
|
|
40
45
|
const array = [...n];
|
|
41
46
|
let currentIndex = array.length, randomIndex;
|
|
@@ -99,6 +104,20 @@ export const decryptString = (txt, password, iv) => {
|
|
|
99
104
|
};
|
|
100
105
|
|
|
101
106
|
export const serializeE2E = (data, auth_token, serverPublicKey) => {
|
|
107
|
+
const inputData = new Uint8Array(Buffer.from(JSON.stringify([data, auth_token]), 'utf8'));
|
|
108
|
+
|
|
109
|
+
if (inputData.byteLength > 10240) {
|
|
110
|
+
// dispatch to background thread
|
|
111
|
+
const { data, pair, nonce } = e2e_worker.encrypt(inputData, serverPublicKey);
|
|
112
|
+
const pubBase64 = Buffer.from(pair.publicKey).toString('base64'),
|
|
113
|
+
nonceBase64 = Buffer.from(nonce).toString('base64');
|
|
114
|
+
|
|
115
|
+
return [
|
|
116
|
+
`${pubBase64}.${nonceBase64}.${Buffer.from(data).toString('base64')}`,
|
|
117
|
+
[pair.secretKey, pair.publicKey]
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
|
|
102
121
|
const pair = box.keyPair(),
|
|
103
122
|
nonce = randomBytes(box.nonceLength),
|
|
104
123
|
pubBase64 = Buffer.from(pair.publicKey).toString('base64'),
|
|
@@ -107,12 +126,9 @@ export const serializeE2E = (data, auth_token, serverPublicKey) => {
|
|
|
107
126
|
return [
|
|
108
127
|
`${pubBase64}.${nonceBase64}.${Buffer.from(
|
|
109
128
|
box(
|
|
110
|
-
|
|
111
|
-
data,
|
|
112
|
-
auth_token
|
|
113
|
-
]), 'utf8'),
|
|
129
|
+
inputData,
|
|
114
130
|
nonce,
|
|
115
|
-
|
|
131
|
+
serverPublicKey,
|
|
116
132
|
pair.secretKey
|
|
117
133
|
)
|
|
118
134
|
).toString('base64')}`,
|
|
@@ -120,14 +136,16 @@ export const serializeE2E = (data, auth_token, serverPublicKey) => {
|
|
|
120
136
|
];
|
|
121
137
|
};
|
|
122
138
|
|
|
123
|
-
export const deserializeE2E = (data, serverPublicKey, clientPrivateKey) => {
|
|
124
|
-
const [binaryNonce, binaryData] = data.split('.'),
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
export const deserializeE2E = (data = '', serverPublicKey, clientPrivateKey) => {
|
|
140
|
+
const [binaryNonce, binaryData] = data.split('.').map(v => new Uint8Array(Buffer.from(v, 'base64')));
|
|
141
|
+
let baseArray;
|
|
142
|
+
|
|
143
|
+
if (binaryData.byteLength > 10240) {
|
|
144
|
+
// dispatch to background thread
|
|
145
|
+
baseArray = e2e_worker.decrypt(binaryData, binaryNonce, serverPublicKey, clientPrivateKey);
|
|
146
|
+
} else {
|
|
147
|
+
baseArray = box.open(binaryData, binaryNonce, serverPublicKey, clientPrivateKey);
|
|
148
|
+
}
|
|
131
149
|
|
|
132
150
|
if (!baseArray) throw 'Decrypting e2e message failed';
|
|
133
151
|
return JSON.parse(Buffer.from(baseArray).toString('utf8'))[0];
|
package/src/index.d.ts
CHANGED
|
@@ -380,7 +380,7 @@ interface MTAuth {
|
|
|
380
380
|
getAuthToken: () => Promise<string>;
|
|
381
381
|
getRefreshToken: () => Promise<string>;
|
|
382
382
|
getRefreshTokenData: () => Promise<RefreshTokenData>;
|
|
383
|
-
parseToken: () =>
|
|
383
|
+
parseToken: (token: string) => AuthData | RefreshTokenData;
|
|
384
384
|
listenAuth: (callback: (auth: TokenEventData) => void) => () => void;
|
|
385
385
|
getAuth: () => Promise<TokenEventData>;
|
|
386
386
|
signOut: () => Promise<void>;
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { MTAuth } from "./products/auth";
|
|
|
5
5
|
import { MTCollection, batchWrite, trySendPendingWrite } from "./products/database";
|
|
6
6
|
import { MTStorage } from "./products/storage";
|
|
7
7
|
import { ServerReachableListener, TokenRefreshListener } from "./helpers/listeners";
|
|
8
|
-
import { initTokenRefresher, listenToken, listenTokenReady, triggerAuthToken } from "./products/auth/accessor";
|
|
8
|
+
import { initTokenRefresher, listenToken, listenTokenReady, parseToken, triggerAuthToken } from "./products/auth/accessor";
|
|
9
9
|
import { TIMESTAMP, DOCUMENT_EXTRACTION, FIND_GEO_JSON, GEO_JSON } from "./products/database/types";
|
|
10
10
|
import { mfetch } from "./products/http_callable";
|
|
11
11
|
import { io } from "socket.io-client";
|
|
@@ -24,11 +24,22 @@ const {
|
|
|
24
24
|
_listenUserVerification
|
|
25
25
|
} = EngineApi;
|
|
26
26
|
|
|
27
|
+
// https://socket.io/docs/v3/emit-cheatsheet/#reserved-events
|
|
28
|
+
const reservedEventName = [
|
|
29
|
+
'connect',
|
|
30
|
+
'connect_error',
|
|
31
|
+
'disconnect',
|
|
32
|
+
'disconnecting',
|
|
33
|
+
'newListener',
|
|
34
|
+
'removeListener'
|
|
35
|
+
];
|
|
36
|
+
|
|
27
37
|
export class MosquitoTransport {
|
|
28
38
|
constructor(config) {
|
|
29
39
|
validateMTConfig(config, this);
|
|
30
40
|
this.config = {
|
|
31
41
|
...config,
|
|
42
|
+
serverE2E_PublicKey: config.serverE2E_PublicKey && new Uint8Array(Buffer.from(config.serverE2E_PublicKey, 'base64')),
|
|
32
43
|
castBSON: config.castBSON === undefined || config.castBSON,
|
|
33
44
|
maxRetries: config.maxRetries || 3,
|
|
34
45
|
uglify: config.enableE2E_Encryption
|
|
@@ -166,7 +177,12 @@ export class MosquitoTransport {
|
|
|
166
177
|
tokenListener,
|
|
167
178
|
clientPrivateKey;
|
|
168
179
|
|
|
169
|
-
const listenerCallback = (callback) => function () {
|
|
180
|
+
const listenerCallback = (route, callback) => function () {
|
|
181
|
+
if (reservedEventName.includes(route)) {
|
|
182
|
+
callback?.(...[...arguments]);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
const [args, emitable] = [...arguments];
|
|
171
187
|
let res;
|
|
172
188
|
|
|
@@ -307,7 +323,7 @@ export class MosquitoTransport {
|
|
|
307
323
|
if (restrictedRoute.includes(route))
|
|
308
324
|
throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
|
|
309
325
|
const ref = ++socketListenerIte,
|
|
310
|
-
listener = listenerCallback(callback);
|
|
326
|
+
listener = listenerCallback(route, callback);
|
|
311
327
|
|
|
312
328
|
socketListenerList.push([ref, 'on', route, listener]);
|
|
313
329
|
if (socket) socket.on(route, listener);
|
|
@@ -321,7 +337,7 @@ export class MosquitoTransport {
|
|
|
321
337
|
if (restrictedRoute.includes(route))
|
|
322
338
|
throw `${route} is a restricted socket path, avoid using any of ${restrictedRoute}`;
|
|
323
339
|
const ref = ++socketListenerIte,
|
|
324
|
-
listener = listenerCallback(callback);
|
|
340
|
+
listener = listenerCallback(route, callback);
|
|
325
341
|
|
|
326
342
|
socketListenerList.push([ref, 'once', route, listener]);
|
|
327
343
|
if (socket) socket.once(route, listener);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { niceHash, shuffleArray, sortArrayByObjectKey } from "../../helpers/peripherals";
|
|
2
2
|
import { awaitStore, updateCacheStore } from "../../helpers/utils";
|
|
3
3
|
import { CacheStore, Scoped } from "../../helpers/variables";
|
|
4
|
-
import { CompareBson, confirmFilterDoc, defaultBSON, downcastBSON, validateCollectionName, validateFilter } from "./validator";
|
|
4
|
+
import { assignExtractionFind, CompareBson, confirmFilterDoc, defaultBSON, downcastBSON, validateCollectionName, validateFilter } from "./validator";
|
|
5
5
|
import getLodash from 'lodash.get';
|
|
6
6
|
import setLodash from 'lodash.set';
|
|
7
7
|
import unsetLodash from 'lodash.unset';
|
|
@@ -48,7 +48,7 @@ export const insertRecord = async (builder, config, accessId, value) => {
|
|
|
48
48
|
|
|
49
49
|
await awaitStore();
|
|
50
50
|
const { projectUrl, dbUrl, dbName, path, command } = builder;
|
|
51
|
-
const entityId = generateRecordID({}, config);
|
|
51
|
+
const entityId = await generateRecordID({}, config);
|
|
52
52
|
const colData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', entityId], { config, command, listing: [] });
|
|
53
53
|
const trackedData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId]);
|
|
54
54
|
|
|
@@ -106,7 +106,7 @@ export const getRecord = async (builder, config, accessId) => {
|
|
|
106
106
|
await awaitStore();
|
|
107
107
|
const { projectUrl, dbUrl, dbName, path, command } = builder;
|
|
108
108
|
const { find, findOne, sort, direction, limit, random } = command;
|
|
109
|
-
const entityId = generateRecordID({}, config);
|
|
109
|
+
const entityId = await generateRecordID({}, config);
|
|
110
110
|
const colData = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'data', entityId]);
|
|
111
111
|
const colRecord = getLodash(CacheStore.DatabaseStore, [projectUrl, dbUrl, dbName, path, 'record', accessId]);
|
|
112
112
|
|
|
@@ -193,16 +193,18 @@ const recurseNonAtomicWrite = (obj, i, type) => {
|
|
|
193
193
|
if (k === '_id') throw `avoid providing "_id" for ${type}() operation as _id only reference a single document`;
|
|
194
194
|
if (k === '_foreign_doc') throw '"_foreign_doc" is readonly';
|
|
195
195
|
}
|
|
196
|
-
if (k.includes('$') || k.includes('.'))
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
if (k.includes('$') || k.includes('.')) {
|
|
197
|
+
if (!(k === '$timestamp' && v === 'now'))
|
|
198
|
+
throw `invalid property "${k}", ${type}() operation fields must not contain .$`;
|
|
199
|
+
}
|
|
200
|
+
if (Validator.OBJECT(v)) recurseNonAtomicWrite(v, i + 1, type);
|
|
199
201
|
});
|
|
200
202
|
};
|
|
201
203
|
|
|
202
204
|
const recurseAtomicWrite = (obj, i, type) => {
|
|
203
205
|
if (!Validator.OBJECT(obj)) throw `expected a document but got ${obj}`;
|
|
204
|
-
Object.entries(obj).forEach(([k]) => {
|
|
205
|
-
if (!(k in AtomicWriter)) throw `Unknown update operator: ${k}`;
|
|
206
|
+
Object.entries(obj).forEach(([k, v]) => {
|
|
207
|
+
if (!i && !(k in AtomicWriter)) throw `Unknown update operator: ${k}`;
|
|
206
208
|
if (i === 1) {
|
|
207
209
|
if ((k === '_id' || k.startsWith('_id.')))
|
|
208
210
|
throw `avoid providing "_id" for ${type}() operation as _id only reference a single document`;
|
|
@@ -211,7 +213,7 @@ const recurseAtomicWrite = (obj, i, type) => {
|
|
|
211
213
|
throw '"_foreign_doc" is readonly';
|
|
212
214
|
}
|
|
213
215
|
if (k.includes('.$')) throw `unsupported operation at "${k}"`;
|
|
214
|
-
if (!i) recurseAtomicWrite(
|
|
216
|
+
if (!i || Validator.OBJECT(v)) recurseAtomicWrite(v, i + 1, type);
|
|
215
217
|
});
|
|
216
218
|
};
|
|
217
219
|
|
|
@@ -299,7 +301,7 @@ export const syncCache = (builder, result) => {
|
|
|
299
301
|
result.value.map(({ scope, value, find, path }) =>
|
|
300
302
|
({ type: scope, value, find, path })
|
|
301
303
|
)
|
|
302
|
-
: { ...result, path: builder.path }
|
|
304
|
+
: [{ ...result, path: builder.path }]
|
|
303
305
|
).forEach(({ value: writeObj, find, type, path }) => {
|
|
304
306
|
WriteValidator[type]({ find, value: writeObj });
|
|
305
307
|
validateCollectionName(path);
|
|
@@ -600,22 +602,6 @@ const getFindFields = (find) => {
|
|
|
600
602
|
return result.filter((v, i, a) => a.findIndex(b => b === v) === i);
|
|
601
603
|
};
|
|
602
604
|
|
|
603
|
-
const assignExtractionFind = (data, find) => {
|
|
604
|
-
if (!find) return find;
|
|
605
|
-
|
|
606
|
-
if (niceGuard({ $dynamicValue: GuardSignal.NON_EMPTY_STRING }, find)) {
|
|
607
|
-
return getLodash(data, find.$dynamicValue) || null;
|
|
608
|
-
} else if (Validator.OBJECT(find)) {
|
|
609
|
-
return Object.fromEntries(
|
|
610
|
-
Object.entries(find).map(([k, v]) =>
|
|
611
|
-
Validator.JSON(v) ? [k, assignExtractionFind(data, v)] : [k, v]
|
|
612
|
-
)
|
|
613
|
-
);
|
|
614
|
-
} else if (Array.isArray(find)) {
|
|
615
|
-
return find.map(v => assignExtractionFind(data, v));
|
|
616
|
-
} else return find;
|
|
617
|
-
};
|
|
618
|
-
|
|
619
605
|
const deserializeWriteValue = (value) => {
|
|
620
606
|
if (!value) return value;
|
|
621
607
|
|
|
@@ -133,9 +133,9 @@ const listenDocument = (callback, onError, builder, config) => {
|
|
|
133
133
|
const { projectUrl, wsPrefix, serverE2E_PublicKey, baseUrl, dbUrl, dbName, accessKey, path, disableCache, command, uglify, castBSON } = builder;
|
|
134
134
|
const { find, findOne, sort, direction, limit } = command;
|
|
135
135
|
const { disableAuth } = config || {};
|
|
136
|
-
const accessId = generateRecordID(builder, config);
|
|
137
136
|
const shouldCache = !disableCache;
|
|
138
137
|
const processId = `${++Scoped.AnyProcessIte}`;
|
|
138
|
+
let accessId;
|
|
139
139
|
|
|
140
140
|
validateListenFindConfig(config);
|
|
141
141
|
validateFilter(findOne || find);
|
|
@@ -159,9 +159,13 @@ const listenDocument = (callback, onError, builder, config) => {
|
|
|
159
159
|
};
|
|
160
160
|
|
|
161
161
|
if (shouldCache) {
|
|
162
|
-
|
|
163
|
-
if (
|
|
164
|
-
|
|
162
|
+
accessId = generateRecordID(builder, config).then(hash => {
|
|
163
|
+
if (hasCancelled) return hash;
|
|
164
|
+
cacheListener = listenQueryEntry(snapshot => {
|
|
165
|
+
if (!Scoped.IS_CONNECTED[projectUrl]) dispatchSnapshot(snapshot);
|
|
166
|
+
}, { accessId: hash, builder, config, processId });
|
|
167
|
+
return hash;
|
|
168
|
+
});
|
|
165
169
|
|
|
166
170
|
awaitStore().then(() => {
|
|
167
171
|
if (hasCancelled) return;
|
|
@@ -210,13 +214,15 @@ const listenDocument = (callback, onError, builder, config) => {
|
|
|
210
214
|
socket.on('mSnapshot', async ([err, snapshot]) => {
|
|
211
215
|
hasRespond = true;
|
|
212
216
|
if (err) {
|
|
213
|
-
onError
|
|
217
|
+
if (typeof onError === 'function') {
|
|
218
|
+
onError(simplifyCaughtError(err).simpleError);
|
|
219
|
+
} else console.error('unhandled listen for:', { path, find }, ' error:', err);
|
|
214
220
|
} else {
|
|
215
221
|
if (uglify) snapshot = deserializeE2E(snapshot, serverE2E_PublicKey, privateKey);
|
|
216
222
|
snapshot = deserializeBSON(snapshot)._;
|
|
217
223
|
dispatchSnapshot(snapshot);
|
|
218
224
|
|
|
219
|
-
if (shouldCache) insertRecord(builder, config, accessId, snapshot);
|
|
225
|
+
if (shouldCache) insertRecord(builder, config, await accessId, snapshot);
|
|
220
226
|
}
|
|
221
227
|
});
|
|
222
228
|
|
|
@@ -284,7 +290,7 @@ const initOnDisconnectionTask = (builder, value, type) => {
|
|
|
284
290
|
socket = io(`${wsPrefix}://${baseUrl}`, {
|
|
285
291
|
transports: ['websocket', 'polling', 'flashsocket'],
|
|
286
292
|
auth: uglify ? {
|
|
287
|
-
e2e: serializeE2E(authObj, mtoken, serverE2E_PublicKey)[0],
|
|
293
|
+
e2e: serializeE2E({ accessKey, _body: authObj }, mtoken, serverE2E_PublicKey)[0],
|
|
288
294
|
_m_internal: true
|
|
289
295
|
} : {
|
|
290
296
|
...mtoken ? { mtoken } : {},
|
|
@@ -334,7 +340,7 @@ const countCollection = async (builder, config) => {
|
|
|
334
340
|
const { projectUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, maxRetries = 7, uglify, path, disableCache, command = {} } = builder;
|
|
335
341
|
const { find } = command;
|
|
336
342
|
const { disableAuth } = config || {};
|
|
337
|
-
const accessId = generateRecordID({ ...builder, countDoc: true }, config);
|
|
343
|
+
const accessId = await generateRecordID({ ...builder, countDoc: true }, config);
|
|
338
344
|
|
|
339
345
|
await awaitStore();
|
|
340
346
|
if (config !== undefined)
|
|
@@ -424,7 +430,7 @@ const findObject = async (builder, config) => {
|
|
|
424
430
|
const { find, findOne, sort, direction, limit, random } = command;
|
|
425
431
|
const { retrieval = RETRIEVAL.DEFAULT, episode = 0, disableAuth, disableMinimizer } = config || {};
|
|
426
432
|
const enableMinimizer = !disableMinimizer;
|
|
427
|
-
const accessId = generateRecordID(builder, config);
|
|
433
|
+
const accessId = await generateRecordID(builder, config);
|
|
428
434
|
const processAccessId = `${accessId}${projectUrl}${dbUrl}${dbName}${retrieval}`;
|
|
429
435
|
const getRecordData = () => getRecord(builder, config, accessId);
|
|
430
436
|
const shouldCache = (retrieval !== RETRIEVAL.DEFAULT || !disableCache) &&
|
|
@@ -6,10 +6,15 @@ import { Binary, BSONRegExp, BSONSymbol, Code, DBRef, Decimal128, Double, Int32,
|
|
|
6
6
|
import { bboxPolygon, booleanIntersects, booleanWithin, circle, distance, polygon } from "@turf/turf";
|
|
7
7
|
|
|
8
8
|
const DirectionList = [1, -1, 'asc', 'desc', 'ascending', 'descending'];
|
|
9
|
-
const FilterFootPrint = t =>
|
|
9
|
+
const FilterFootPrint = t => {
|
|
10
|
+
validateFilter(t);
|
|
11
|
+
return true;
|
|
12
|
+
};
|
|
10
13
|
const ReturnAndExcludeFootprint = t => t === undefined ||
|
|
11
14
|
!(Array.isArray(t) ? t : [t]).filter(v => !Validator.TRIMMED_NON_EMPTY_STRING(v)).length;
|
|
12
15
|
|
|
16
|
+
const ConfigFind = t => t && FilterFootPrint(assignExtractionFind({}, t));
|
|
17
|
+
|
|
13
18
|
const FindConfig = {
|
|
14
19
|
extraction: t => t === undefined ||
|
|
15
20
|
(Array.isArray(t) ? t : [t]).filter(m =>
|
|
@@ -18,8 +23,8 @@ const FindConfig = {
|
|
|
18
23
|
sort: (t, p) => t === undefined || (Validator.TRIMMED_NON_EMPTY_STRING(t) && p.find),
|
|
19
24
|
direction: (t, p) => t === undefined || (p.sort && p.find && DirectionList.includes(t)),
|
|
20
25
|
limit: (t, p) => t === undefined || (Validator.POSITIVE_INTEGER(t) && p.find),
|
|
21
|
-
find: (t, p) => (t === undefined && p.findOne) || (!p.findOne &&
|
|
22
|
-
findOne: (t, p) => (t === undefined && p.find) || (!p.find &&
|
|
26
|
+
find: (t, p) => (t === undefined && p.findOne) || (!p.findOne && ConfigFind(t)),
|
|
27
|
+
findOne: (t, p) => (t === undefined && p.find) || (!p.find && ConfigFind(t)),
|
|
23
28
|
returnOnly: ReturnAndExcludeFootprint,
|
|
24
29
|
excludeFields: ReturnAndExcludeFootprint
|
|
25
30
|
}).validate(m)
|
|
@@ -41,7 +46,8 @@ export const validateListenFindConfig = (config) => config === undefined ||
|
|
|
41
46
|
extraction: FindConfig.extraction,
|
|
42
47
|
returnOnly: FindConfig.returnOnly,
|
|
43
48
|
excludeFields: FindConfig.excludeFields,
|
|
44
|
-
disableAuth: FindConfig.disableAuth
|
|
49
|
+
disableAuth: FindConfig.disableAuth,
|
|
50
|
+
episode: t => [undefined, 0, 1].includes(t)
|
|
45
51
|
}).validate(config);
|
|
46
52
|
|
|
47
53
|
export const validateFindObject = command =>
|
|
@@ -55,6 +61,22 @@ export const validateFindObject = command =>
|
|
|
55
61
|
random: (t, p) => t === undefined || (!p.sort && t === true),
|
|
56
62
|
}).validate({ ...command });
|
|
57
63
|
|
|
64
|
+
export const assignExtractionFind = (data, find) => {
|
|
65
|
+
if (!find) return find;
|
|
66
|
+
|
|
67
|
+
if (niceGuard({ $dynamicValue: GuardSignal.NON_EMPTY_STRING }, find)) {
|
|
68
|
+
return getLodash(data, find.$dynamicValue) || null;
|
|
69
|
+
} else if (Validator.OBJECT(find)) {
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
Object.entries(find).map(([k, v]) =>
|
|
72
|
+
Validator.JSON(v) ? [k, assignExtractionFind(data, v)] : [k, v]
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
} else if (Array.isArray(find)) {
|
|
76
|
+
return find.map(v => assignExtractionFind(data, v));
|
|
77
|
+
} else return find;
|
|
78
|
+
};
|
|
79
|
+
|
|
58
80
|
export const validateCollectionName = collectionName => {
|
|
59
81
|
// Check if the collection name is empty
|
|
60
82
|
if (!collectionName || typeof collectionName !== 'string')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
|
-
import { deserializeE2E, listenReachableServer, niceHash, serializeE2E } from "../../helpers/peripherals";
|
|
2
|
+
import { deserializeE2E, listenReachableServer, niceHash, normalizeRoute, serializeE2E } from "../../helpers/peripherals";
|
|
3
3
|
import { awaitStore, getReachableServer, updateCacheStore } from "../../helpers/utils";
|
|
4
4
|
import { RETRIEVAL } from "../../helpers/values";
|
|
5
5
|
import { CacheStore, Scoped } from "../../helpers/variables";
|
|
@@ -7,6 +7,7 @@ import { awaitRefreshToken } from "../auth/accessor";
|
|
|
7
7
|
import { simplifyCaughtError } from "simplify-error";
|
|
8
8
|
import { guardObject, Validator } from "guard-object";
|
|
9
9
|
import cloneDeep from "lodash.clonedeep";
|
|
10
|
+
import { stringify } from "json-buffer";
|
|
10
11
|
|
|
11
12
|
const buildFetchData = (data) => {
|
|
12
13
|
const { ok, type, status, statusText, redirected, url, headers, size, base64 } = data;
|
|
@@ -35,7 +36,7 @@ export const mfetch = async (input = '', init, config) => {
|
|
|
35
36
|
const { projectUrl, serverE2E_PublicKey, method, maxRetries = 7, disableCache, accessKey, uglify } = config;
|
|
36
37
|
const { headers, body } = init || {};
|
|
37
38
|
|
|
38
|
-
if (
|
|
39
|
+
if (method !== undefined)
|
|
39
40
|
guardObject({
|
|
40
41
|
enableMinimizer: t => t === undefined || Validator.BOOLEAN(t),
|
|
41
42
|
rawApproach: t => t === undefined || Validator.BOOLEAN(t),
|
|
@@ -48,6 +49,8 @@ export const mfetch = async (input = '', init, config) => {
|
|
|
48
49
|
const disableAuth = method?.disableAuth || isBaseUrl;
|
|
49
50
|
const shouldCache = (retrieval !== RETRIEVAL.DEFAULT || !disableCache) &&
|
|
50
51
|
retrieval !== RETRIEVAL.NO_CACHE_NO_AWAIT;
|
|
52
|
+
const uglified = !!(!isBaseUrl && uglify);
|
|
53
|
+
|
|
51
54
|
const rawHeader = Object.fromEntries(
|
|
52
55
|
[...new Headers(headers).entries()]
|
|
53
56
|
);
|
|
@@ -58,16 +61,25 @@ export const mfetch = async (input = '', init, config) => {
|
|
|
58
61
|
if ('uglified' in rawHeader)
|
|
59
62
|
throw '"uglified" in header is a reserved prop';
|
|
60
63
|
|
|
61
|
-
if (input.startsWith(projectUrl) && !rawApproach)
|
|
64
|
+
if (!input.startsWith(projectUrl) && !rawApproach)
|
|
62
65
|
throw `please set { rawApproach: true } if you're trying to access different endpoint at "${input}"`;
|
|
63
66
|
|
|
64
|
-
if (body !== undefined
|
|
65
|
-
|
|
67
|
+
if (body !== undefined) {
|
|
68
|
+
if (
|
|
69
|
+
typeof body !== 'string' &&
|
|
70
|
+
!Buffer.isBuffer(body) &&
|
|
71
|
+
!Validator.JSON(body) &&
|
|
72
|
+
!(body instanceof File) &&
|
|
73
|
+
!(body instanceof Blob)
|
|
74
|
+
) throw `"body" must be any of string, buffer, object, File, Blob`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const rawBody = stringify([(body instanceof File || body instanceof Blob) ? await body.arrayBuffer() : body]);
|
|
66
78
|
|
|
67
|
-
const reqId = niceHash(
|
|
79
|
+
const reqId = await niceHash(
|
|
68
80
|
JSON.stringify([
|
|
69
81
|
rawHeader,
|
|
70
|
-
|
|
82
|
+
rawBody,
|
|
71
83
|
!!disableAuth,
|
|
72
84
|
input
|
|
73
85
|
])
|
|
@@ -131,19 +143,18 @@ export const mfetch = async (input = '', init, config) => {
|
|
|
131
143
|
await awaitRefreshToken(projectUrl);
|
|
132
144
|
|
|
133
145
|
const mtoken = Scoped.AuthJWTToken[projectUrl];
|
|
134
|
-
const uglified = !!(!isBaseUrl && body && uglify);
|
|
135
146
|
const initType = rawHeader['content-type'];
|
|
136
147
|
|
|
137
|
-
const [reqBuilder, [privateKey]] = uglified ? serializeE2E(
|
|
148
|
+
const [reqBuilder, [privateKey]] = uglified ? serializeE2E(rawBody, mtoken, serverE2E_PublicKey) : [null, []];
|
|
138
149
|
|
|
139
|
-
const f = await fetch(isBaseUrl ? input : `${projectUrl}/${input}`, {
|
|
150
|
+
const f = await fetch(isBaseUrl ? input : `${projectUrl}/${normalizeRoute(input)}`, {
|
|
140
151
|
...isBaseUrl ? {} : { method: 'POST' },
|
|
141
152
|
...init,
|
|
142
153
|
...uglified ? { body: reqBuilder } : {},
|
|
143
154
|
cache: 'no-cache',
|
|
144
155
|
headers: {
|
|
145
|
-
...isBaseUrl ? {} : { '
|
|
146
|
-
...
|
|
156
|
+
...isBaseUrl ? {} : { 'content-type': 'application/json' },
|
|
157
|
+
...rawHeader,
|
|
147
158
|
...uglified ? {
|
|
148
159
|
uglified,
|
|
149
160
|
'content-type': 'text/plain',
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import EngineApi from "../../helpers/engine_api";
|
|
2
2
|
import { encodeBinary } from "../../helpers/peripherals";
|
|
3
3
|
import { Scoped } from "../../helpers/variables";
|
|
4
|
-
import { awaitReachableServer, buildFetchInterface
|
|
4
|
+
import { awaitReachableServer, buildFetchInterface } from "../../helpers/utils";
|
|
5
5
|
import { awaitRefreshToken } from "../auth/accessor";
|
|
6
6
|
import { Buffer } from "buffer";
|
|
7
|
+
import { simplifyError } from "simplify-error";
|
|
7
8
|
|
|
8
9
|
export class MTStorage {
|
|
9
10
|
constructor(config) {
|