react-native-mosquito-transport 0.0.14 → 0.0.15
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 +3 -0
- package/ios/Mosquitodb.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/package.json +2 -2
- package/src/helpers/EngineApi.js +0 -1
- package/src/helpers/utils.js +9 -5
- package/src/helpers/values.js +4 -3
- package/src/index.d.ts +48 -6
- package/src/index.js +20 -5
- package/src/products/auth/accessor.js +3 -23
- package/src/products/auth/index.js +9 -2
- package/src/products/database/index.js +4 -4
- package/src/products/database/validator.js +9 -2
package/TODO
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-mosquito-transport",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "React native javascript sdk for mosquito-transport (https://github.com/deflexable/mosquito-transport)",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"react-native-get-random-values": "^1.9.0",
|
|
37
37
|
"set-large-timeout": "^1.0.1",
|
|
38
38
|
"socket.io-client": "^4.6.2",
|
|
39
|
-
"subscription-listener": "^1.
|
|
39
|
+
"subscription-listener": "^1.1.2",
|
|
40
40
|
"tweetnacl": "^1.0.3"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
package/src/helpers/EngineApi.js
CHANGED
|
@@ -14,7 +14,6 @@ const apis = {
|
|
|
14
14
|
_twitterSignin: (baseApi, ugly) => `${baseApi}/${ugly ? encodeBinary(apis._twitterSignin(baseApi)) : '_twitterSignin'}`,
|
|
15
15
|
_githubSignin: (baseApi, ugly) => `${baseApi}/${ugly ? encodeBinary(apis._githubSignin(baseApi)) : '_githubSignin'}`,
|
|
16
16
|
_signOut: (baseApi, ugly) => `${baseApi}/${ugly ? encodeBinary(apis._signOut(baseApi)) : '_signOut'}`,
|
|
17
|
-
_invalidateToken: (baseApi, ugly) => `${baseApi}/${ugly ? encodeBinary(apis._invalidateToken(baseApi)) : '_invalidateToken'}`,
|
|
18
17
|
_refreshAuthToken: (baseApi, ugly) => `${baseApi}/${ugly ? encodeBinary(apis._refreshAuthToken(baseApi)) : '_refreshAuthToken'}`,
|
|
19
18
|
_downloadFile: (baseApi, ugly) => `${baseApi}/${ugly ? encodeBinary(apis._downloadFile(baseApi)) : '_downloadFile'}`,
|
|
20
19
|
_uploadFile: (baseApi, ugly) => `${baseApi}/${ugly ? encodeBinary(apis._uploadFile(baseApi)) : '_uploadFile'}`,
|
package/src/helpers/utils.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CacheStore, Scoped } from "./variables";
|
|
|
5
5
|
import { decryptString, encryptString, niceTry, serializeE2E } from "./peripherals";
|
|
6
6
|
|
|
7
7
|
export const updateCacheStore = () => {
|
|
8
|
-
const { cachePassword = DEFAULT_CACHE_PASSWORD, cacheProtocol = CACHE_PROTOCOL.ASYNC_STORAGE } = Scoped.ReleaseCacheData;
|
|
8
|
+
const { cachePassword = DEFAULT_CACHE_PASSWORD, cacheProtocol = CACHE_PROTOCOL.ASYNC_STORAGE, io } = Scoped.ReleaseCacheData;
|
|
9
9
|
|
|
10
10
|
clearTimeout(Scoped.cacheStorageReducer);
|
|
11
11
|
Scoped.cacheStorageReducer = setTimeout(() => {
|
|
@@ -15,7 +15,9 @@ export const updateCacheStore = () => {
|
|
|
15
15
|
cachePassword
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
-
if (
|
|
18
|
+
if (io) {
|
|
19
|
+
io.output(txt);
|
|
20
|
+
} else if (cacheProtocol === CACHE_PROTOCOL.ASYNC_STORAGE) {
|
|
19
21
|
AsyncStorage.setItem(CACHE_STORAGE_PATH, txt);
|
|
20
22
|
} else {
|
|
21
23
|
const fs = require('react-native-fs');
|
|
@@ -25,11 +27,13 @@ export const updateCacheStore = () => {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export const releaseCacheStore = async (builder) => {
|
|
28
|
-
const { cachePassword = DEFAULT_CACHE_PASSWORD, cacheProtocol = CACHE_PROTOCOL.ASYNC_STORAGE } = builder;
|
|
30
|
+
const { cachePassword = DEFAULT_CACHE_PASSWORD, cacheProtocol = CACHE_PROTOCOL.ASYNC_STORAGE, io } = builder;
|
|
29
31
|
|
|
30
32
|
let txt;
|
|
31
33
|
|
|
32
|
-
if (
|
|
34
|
+
if (io) {
|
|
35
|
+
txt = await io.input();
|
|
36
|
+
} else if (cacheProtocol === CACHE_PROTOCOL.ASYNC_STORAGE) {
|
|
33
37
|
txt = await niceTry(() => AsyncStorage.getItem(CACHE_STORAGE_PATH));
|
|
34
38
|
} else {
|
|
35
39
|
const fs = require('react-native-fs');
|
|
@@ -37,7 +41,7 @@ export const releaseCacheStore = async (builder) => {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
const j = JSON.parse(decryptString(txt || '', cachePassword, cachePassword) || '{}');
|
|
40
|
-
|
|
44
|
+
|
|
41
45
|
Object.entries(j).forEach(([k, v]) => {
|
|
42
46
|
CacheStore[k] = v;
|
|
43
47
|
});
|
package/src/helpers/values.js
CHANGED
|
@@ -13,7 +13,8 @@ export const CACHE_STORAGE_PATH = encodeBinary('MOSQUITO_TRANSPORT_FREEZER'),
|
|
|
13
13
|
|
|
14
14
|
export const CACHE_PROTOCOL = {
|
|
15
15
|
ASYNC_STORAGE: 'async-storage',
|
|
16
|
-
REACT_NATIVE_FS: 'reat-native-fs'
|
|
16
|
+
REACT_NATIVE_FS: 'reat-native-fs',
|
|
17
|
+
SQLITE: 'sqlite' // TODO:
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
export const RETRIEVAL = {
|
|
@@ -43,8 +44,8 @@ export const WRITE_OPS = {
|
|
|
43
44
|
$MAX: '$max',
|
|
44
45
|
$MIN: '$min',
|
|
45
46
|
$MUL: '$mul',
|
|
46
|
-
$RENAME: '$rename'
|
|
47
|
-
|
|
47
|
+
$RENAME: '$rename',
|
|
48
|
+
$SET_ON_INSERT: '$setOnInsert'
|
|
48
49
|
};
|
|
49
50
|
export const WRITE_OPS_LIST = Object.values(WRITE_OPS);
|
|
50
51
|
|
package/src/index.d.ts
CHANGED
|
@@ -25,9 +25,26 @@ interface mtimestamp { $timestamp: 'now' }
|
|
|
25
25
|
export const TIMESTAMP: mtimestamp;
|
|
26
26
|
export function DOCUMENT_EXTRACTION(path: string): { $dynamicValue: number };
|
|
27
27
|
|
|
28
|
+
interface ReleaseCacheOption_IO {
|
|
29
|
+
/**
|
|
30
|
+
* This password will be used to encrypt data stored locally
|
|
31
|
+
*/
|
|
32
|
+
cachePassword?: string;
|
|
33
|
+
io: {
|
|
34
|
+
/**
|
|
35
|
+
* feeds mosquito-transport data
|
|
36
|
+
*/
|
|
37
|
+
input: () => string;
|
|
38
|
+
/**
|
|
39
|
+
* emits mosquito-transport internal data
|
|
40
|
+
*/
|
|
41
|
+
output: (data: string) => void;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
interface ReleaseCacheOption {
|
|
29
46
|
/**
|
|
30
|
-
* This password will be used to
|
|
47
|
+
* This password will be used to encrypt data stored locally
|
|
31
48
|
*/
|
|
32
49
|
cachePassword?: string;
|
|
33
50
|
/**
|
|
@@ -62,7 +79,7 @@ interface BatchWriteConfig extends WriteConfig {
|
|
|
62
79
|
|
|
63
80
|
export default class RNMT {
|
|
64
81
|
constructor(config: RNMTConfig);
|
|
65
|
-
static releaseCache(option?: ReleaseCacheOption): void;
|
|
82
|
+
static releaseCache(option?: ReleaseCacheOption | ReleaseCacheOption_IO): void;
|
|
66
83
|
getDatabase(dbName?: string, dbUrl?: string): GetDatabase;
|
|
67
84
|
collection(path: string): RNMTCollection;
|
|
68
85
|
auth(): RNMTAuth;
|
|
@@ -274,6 +291,7 @@ interface DocumentFind {
|
|
|
274
291
|
$or?: any[];
|
|
275
292
|
$text?: {
|
|
276
293
|
$search: string;
|
|
294
|
+
$field: string;
|
|
277
295
|
$language?: string;
|
|
278
296
|
$caseSensitive?: boolean;
|
|
279
297
|
$diacriticSensitive?: boolean;
|
|
@@ -303,8 +321,11 @@ interface RNMTAuth {
|
|
|
303
321
|
listenVerifiedStatus: (callback?: (verified?: boolean) => void, onError?: (error?: ErrorResponse) => void) => () => void;
|
|
304
322
|
listenAuthToken: (callback: (token: string) => void) => () => void;
|
|
305
323
|
getAuthToken: () => Promise<string>;
|
|
306
|
-
|
|
307
|
-
|
|
324
|
+
getRefreshToken: () => Promise<string>;
|
|
325
|
+
getRefreshTokenData: () => Promise<RefreshTokenData>;
|
|
326
|
+
parseToken: () => string;
|
|
327
|
+
listenAuth: (callback: (auth: TokenEventData) => void) => () => void;
|
|
328
|
+
getAuth: () => Promise<TokenEventData>;
|
|
308
329
|
signOut: () => Promise<void>;
|
|
309
330
|
forceRefreshToken: () => Promise<string>;
|
|
310
331
|
}
|
|
@@ -325,14 +346,35 @@ interface AuthData {
|
|
|
325
346
|
signupMethod: 'google' | 'apple' | 'custom' | 'github' | 'twitter' | 'facebook' | string;
|
|
326
347
|
currentAuthMethod: 'google' | 'apple' | 'custom' | 'github' | 'twitter' | 'facebook' | string;
|
|
327
348
|
joinedOn: number;
|
|
328
|
-
encryptionKey: string;
|
|
329
349
|
uid: string;
|
|
330
350
|
claims: Object;
|
|
331
351
|
emailVerified: boolean;
|
|
352
|
+
tokenID: string;
|
|
353
|
+
disabled: boolean;
|
|
354
|
+
entityOf: string;
|
|
332
355
|
profile: {
|
|
333
356
|
photo: string;
|
|
334
357
|
name: string;
|
|
335
|
-
}
|
|
358
|
+
},
|
|
359
|
+
exp: number;
|
|
360
|
+
aud: string;
|
|
361
|
+
iss: string;
|
|
362
|
+
sub: string;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
interface RefreshTokenData {
|
|
366
|
+
uid: string;
|
|
367
|
+
tokenID: string;
|
|
368
|
+
isRefreshToken: true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
interface TokenEventData extends AuthData {
|
|
372
|
+
tokenManager: TokenManager | null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
interface TokenManager {
|
|
376
|
+
refreshToken: string;
|
|
377
|
+
accessToken: string;
|
|
336
378
|
}
|
|
337
379
|
|
|
338
380
|
interface RNMTStorage {
|
package/src/index.js
CHANGED
|
@@ -35,7 +35,9 @@ class RNMT {
|
|
|
35
35
|
};
|
|
36
36
|
const { projectUrl } = this.config;
|
|
37
37
|
|
|
38
|
+
this.config.secureUrl = projectUrl.startsWith('https');
|
|
38
39
|
this.config.baseUrl = projectUrl.split('://')[1];
|
|
40
|
+
this.config.wsPrefix = this.config.secureUrl ? 'wss' : 'ws';
|
|
39
41
|
|
|
40
42
|
if (!Scoped.ReleaseCacheData)
|
|
41
43
|
throw `releaseCache must be called before creating any ${this.constructor.name} instance`;
|
|
@@ -46,8 +48,12 @@ class RNMT {
|
|
|
46
48
|
triggerAuthToken(projectUrl);
|
|
47
49
|
initTokenRefresher({ ...this.config }, true);
|
|
48
50
|
|
|
49
|
-
const socket = io(
|
|
50
|
-
auth: { _m_internal: true }
|
|
51
|
+
const socket = io(`${this.config.wsPrefix}://${projectUrl.split('://')[1]}`, {
|
|
52
|
+
auth: { _m_internal: true, _from_base: true }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
socket.on('_signal_signout', () => {
|
|
56
|
+
this.auth().signOut();
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
socket.on('connect', () => {
|
|
@@ -69,7 +75,7 @@ class RNMT {
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
static releaseCache(prop) {
|
|
72
|
-
if (Scoped.ReleaseCacheData) throw `calling ${this.name} multiple times is prohibited`;
|
|
78
|
+
if (Scoped.ReleaseCacheData) throw `calling ${this.name}() multiple times is prohibited`;
|
|
73
79
|
validateReleaseCacheProp({ ...prop });
|
|
74
80
|
Scoped.ReleaseCacheData = { ...prop };
|
|
75
81
|
releaseCacheStore({ ...prop });
|
|
@@ -95,7 +101,7 @@ class RNMT {
|
|
|
95
101
|
|
|
96
102
|
getSocket = (configOpts) => {
|
|
97
103
|
const { disableAuth, authHandshake } = configOpts || {},
|
|
98
|
-
{ projectUrl, uglify, accessKey, serverE2E_PublicKey } = this.config;
|
|
104
|
+
{ projectUrl, uglify, accessKey, serverE2E_PublicKey, wsPrefix } = this.config;
|
|
99
105
|
|
|
100
106
|
const restrictedRoute = [
|
|
101
107
|
_listenCollection,
|
|
@@ -194,7 +200,7 @@ class RNMT {
|
|
|
194
200
|
const mtoken = disableAuth ? undefined : Scoped.AuthJWTToken[projectUrl];
|
|
195
201
|
const [reqBuilder, [privateKey]] = uglify ? serializeE2E({ accessKey, a_extras: authHandshake }, mtoken, serverE2E_PublicKey) : [null, []];
|
|
196
202
|
|
|
197
|
-
socket = io(
|
|
203
|
+
socket = io(`${wsPrefix}://${projectUrl.split('://')[1]}`, {
|
|
198
204
|
auth: uglify ? {
|
|
199
205
|
ugly: true,
|
|
200
206
|
e2e: reqBuilder
|
|
@@ -299,8 +305,17 @@ const validateReleaseCacheProp = (prop) => {
|
|
|
299
305
|
throw `Invalid value supplied to cachePassword, value must be a string and greater than 0 characters`;
|
|
300
306
|
} else if (k === 'cacheProtocol') {
|
|
301
307
|
if (!cacheList.includes(`${v}`)) throw `unknown value supplied to ${k}, expected any of ${cacheList}`;
|
|
308
|
+
} else if (k === 'io') {
|
|
309
|
+
Object.entries(v).forEach(([k, v]) => {
|
|
310
|
+
if (k === 'input' || k === 'output') {
|
|
311
|
+
if (typeof v !== 'function')
|
|
312
|
+
throw `Invalid value supplied to "io.${k}", expected a function but got "${v}"`;
|
|
313
|
+
} else throw `Unexpected property named "io.${k}"`;
|
|
314
|
+
});
|
|
302
315
|
} else throw `Unexpected property named ${k}`;
|
|
303
316
|
});
|
|
317
|
+
|
|
318
|
+
if (!prop?.io && !prop?.cacheProtocol) throw 'You need to provide either "io" or "cacheProtocol"';
|
|
304
319
|
}
|
|
305
320
|
|
|
306
321
|
const validator = {
|
|
@@ -2,8 +2,8 @@ import setLargeTimeout from "set-large-timeout";
|
|
|
2
2
|
import { doSignOut } from ".";
|
|
3
3
|
import EngineApi from "../../helpers/EngineApi";
|
|
4
4
|
import { AuthTokenListener, TokenRefreshListener } from "../../helpers/listeners";
|
|
5
|
-
import { decodeBinary, deserializeE2E, listenReachableServer
|
|
6
|
-
import {
|
|
5
|
+
import { decodeBinary, deserializeE2E, listenReachableServer } from "../../helpers/peripherals";
|
|
6
|
+
import { awaitStore, buildFetchInterface, simplifyError, updateCacheStore } from "../../helpers/utils";
|
|
7
7
|
import { CacheStore, Scoped } from "../../helpers/variables";
|
|
8
8
|
|
|
9
9
|
export const listenToken = (callback, projectUrl) =>
|
|
@@ -103,7 +103,6 @@ const refreshToken = (builder, processRef, remainRetries = 7, initialRetries = 7
|
|
|
103
103
|
if (isForceRefresh) Scoped.InitiatedForcedToken[projectUrl] = true;
|
|
104
104
|
updateCacheStore();
|
|
105
105
|
initTokenRefresher(builder);
|
|
106
|
-
invalidateToken(builder, token);
|
|
107
106
|
} else reject(lostProcess.simpleError);
|
|
108
107
|
} catch (e) {
|
|
109
108
|
if (e.simpleError) {
|
|
@@ -129,23 +128,4 @@ const refreshToken = (builder, processRef, remainRetries = 7, initialRetries = 7
|
|
|
129
128
|
}, projectUrl);
|
|
130
129
|
}
|
|
131
130
|
}
|
|
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
|
-
}
|
|
131
|
+
});
|
|
@@ -42,7 +42,7 @@ export class MTAuth {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
listenVerifiedStatus(callback, onError) {
|
|
45
|
-
const { projectUrl, serverE2E_PublicKey, uglify, baseUrl } = this.builder;
|
|
45
|
+
const { projectUrl, serverE2E_PublicKey, uglify, baseUrl, wsPrefix } = this.builder;
|
|
46
46
|
|
|
47
47
|
let socket, wasDisconnected, lastToken = Scoped.AuthJWTToken[projectUrl] || null, lastInitRef = 0;
|
|
48
48
|
|
|
@@ -58,7 +58,7 @@ export class MTAuth {
|
|
|
58
58
|
const mtoken = Scoped.AuthJWTToken[projectUrl],
|
|
59
59
|
[reqBuilder, [privateKey]] = uglify ? serializeE2E({ mtoken }, undefined, serverE2E_PublicKey) : [null, []];
|
|
60
60
|
|
|
61
|
-
socket = io(
|
|
61
|
+
socket = io(`${wsPrefix}://${baseUrl}`, {
|
|
62
62
|
auth: uglify ? {
|
|
63
63
|
e2e: reqBuilder,
|
|
64
64
|
_m_internal: true
|
|
@@ -104,6 +104,13 @@ export class MTAuth {
|
|
|
104
104
|
|
|
105
105
|
listenAuthToken = (callback) => listenToken(callback, this.builder.projectUrl);
|
|
106
106
|
|
|
107
|
+
getRefreshToken = async () => {
|
|
108
|
+
await awaitStore();
|
|
109
|
+
return CacheStore.AuthStore[this.builder.projectUrl]?.refreshToken;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
parseToken = (token) => parseToken(token);
|
|
113
|
+
|
|
107
114
|
getAuthToken = () => new Promise(resolve => {
|
|
108
115
|
const l = listenToken(t => {
|
|
109
116
|
l();
|
|
@@ -111,7 +111,7 @@ const {
|
|
|
111
111
|
} = EngineApi;
|
|
112
112
|
|
|
113
113
|
const listenDocument = (callback, onError, builder, config) => {
|
|
114
|
-
const { projectUrl, serverE2E_PublicKey, baseUrl, dbUrl, dbName, accessKey, path, disableCache, command, uglify } = builder,
|
|
114
|
+
const { projectUrl, wsPrefix, serverE2E_PublicKey, baseUrl, dbUrl, dbName, accessKey, path, disableCache, command, uglify } = builder,
|
|
115
115
|
{ find, findOne, sort, direction, limit } = command,
|
|
116
116
|
{ disableAuth } = config || {},
|
|
117
117
|
accessId = generateRecordID(builder, config),
|
|
@@ -163,7 +163,7 @@ const listenDocument = (callback, onError, builder, config) => {
|
|
|
163
163
|
|
|
164
164
|
const [encPlate, [privateKey]] = uglify ? serializeE2E({ accessKey, _body: authObj }, mtoken, serverE2E_PublicKey) : ['', []];
|
|
165
165
|
|
|
166
|
-
socket = io(
|
|
166
|
+
socket = io(`${wsPrefix}://${baseUrl}`, {
|
|
167
167
|
auth: uglify ? { e2e: encPlate, _m_internal: true } : {
|
|
168
168
|
accessKey,
|
|
169
169
|
_body: authObj,
|
|
@@ -217,7 +217,7 @@ const listenDocument = (callback, onError, builder, config) => {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
const initOnDisconnectionTask = (builder, value, type) => {
|
|
220
|
-
const { projectUrl, baseUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, path, command, uglify } = builder,
|
|
220
|
+
const { projectUrl, wsPrefix, baseUrl, serverE2E_PublicKey, dbUrl, dbName, accessKey, path, command, uglify } = builder,
|
|
221
221
|
{ find } = command || {},
|
|
222
222
|
disableAuth = false;
|
|
223
223
|
|
|
@@ -240,7 +240,7 @@ const initOnDisconnectionTask = (builder, value, type) => {
|
|
|
240
240
|
dbUrl
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
-
socket = io(
|
|
243
|
+
socket = io(`${wsPrefix}://${baseUrl}`, {
|
|
244
244
|
auth: uglify ? {
|
|
245
245
|
e2e: serializeE2E(authObj, mtoken, serverE2E_PublicKey)[0],
|
|
246
246
|
_m_internal: true
|
|
@@ -202,12 +202,19 @@ const evaluateFilter = (data = {}, filter = {}) => {
|
|
|
202
202
|
} else logics.push(false);
|
|
203
203
|
} else if ($ === $TEXT) {
|
|
204
204
|
if (commandSplit.slice(-1)[0].$ === '$search') {
|
|
205
|
-
const { $caseSensitive, $
|
|
205
|
+
const { $caseSensitive, $search, $field } = filter.$text;
|
|
206
206
|
|
|
207
207
|
if (typeof value !== 'string' || typeof $search !== 'string')
|
|
208
208
|
throw `$search must have a string value`;
|
|
209
|
+
if (!$field) throw '"$field" is required inside "$text" operator when "disableCache=false"';
|
|
210
|
+
const fieldArr = Array.isArray($field) ? $field : [$field];
|
|
209
211
|
|
|
210
|
-
|
|
212
|
+
fieldArr.forEach(v => {
|
|
213
|
+
if (typeof v !== 'string' || !v.trim())
|
|
214
|
+
throw `invalid item inside "$field", expected a non-empty string but got "${v}"`;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const searchTxt = fieldArr.map(v => getLodash(dataObj, v || '')).map(v =>
|
|
211
218
|
`${typeof v === 'string' ? v :
|
|
212
219
|
Array.isArray(v) ? v.map(v => typeof v === 'string' ? v : '').join(' ').trim() : ''}`.trim()
|
|
213
220
|
).join(' ').trim();
|