22* Fetching and processing data for remote config
33* @module plugins/remote-config/api/parts/data/rc
44*/
5+ const semver = require ( 'semver' ) ;
56var prng = require ( '../../../../api/utils/random-sfc32.js' ) ;
67var globalSeed = "Countly_is_awesome" ;
78
89/** @lends module:plugins/remote-config/api/parts/data/rc */
910var remoteConfig = { } ;
1011
1112/**
12- * Function to check if the given query would match the given user
13- * @param {Object } user - user
14- * @param {Object } query - query
13+ * Function to check if the given query matches the given user
14+ * @param {Object } inpUser - user object
15+ * @param {Object } inpQuery - condition query
1516 * @returns {Boolean } true if the query matches the user
1617 */
17- remoteConfig . processFilter = function ( user , query ) {
18- var queryStatus = false , isCohort = false , hasValue = false ;
19-
20- if ( Object . keys ( query ) . length ) {
21- queryStatus = true ;
22-
23- for ( var prop in query ) {
24- var parts = prop . split ( "." ) ;
25- var value ;
18+ remoteConfig . processFilter = function ( inpUser , inpQuery ) {
19+ /**
20+ * Inner function of processFilter for recursion
21+ * @param {Object } user - user object
22+ * @param {Object } query - condition query
23+ * @returns {Boolean } true if the query matches the user
24+ */
25+ function matchesQuery ( user , query ) {
26+ for ( let key in query ) {
27+ if ( typeof query [ key ] === 'object' && query [ key ] !== null && ! Array . isArray ( query [ key ] ) ) {
28+ let qResult = true ;
29+
30+ for ( let prop in query ) {
31+ if ( prop === '$or' ) {
32+ return qResult && query [ prop ] . some ( ( subQuery ) => matchesQuery ( user , subQuery ) ) ;
33+ }
34+ else if ( prop === '$and' ) {
35+ return qResult && query [ prop ] . every ( ( subQuery ) => matchesQuery ( user , subQuery ) ) ;
36+ }
2637
27- if ( parts [ 0 ] === "up" || parts . length === 1 ) {
28- var p = parts [ 0 ] ;
29- if ( p === "up" ) {
30- p = parts [ 1 ] ;
31- }
32- if ( user [ p ] ) {
33- value = user [ p ] ;
34- }
35- }
36- else if ( user [ parts [ 0 ] ] && user [ parts [ 0 ] ] [ parts [ 1 ] ] ) {
37- value = user [ parts [ 0 ] ] [ parts [ 1 ] ] ;
38- }
38+ let parts = prop . split ( "." ) ;
39+ let value ;
40+
41+ if ( parts [ 0 ] === "up" || parts . length === 1 ) {
42+ var p = parts [ 0 ] ;
43+ if ( p === "up" ) {
44+ p = parts [ 1 ] ;
45+ }
46+ if ( user [ p ] ) {
47+ value = user [ p ] ;
48+ }
49+ }
50+ else if ( user [ parts [ 0 ] ] && user [ parts [ 0 ] ] [ parts [ 1 ] ] ) {
51+ value = user [ parts [ 0 ] ] [ parts [ 1 ] ] ;
52+ }
3953
40- if ( parts [ 0 ] !== "chr" ) {
41- if ( typeof ( value ) !== "undefined" ) {
42- hasValue = true ;
43- queryStatus = queryStatus && processPropertyValues ( value , query , prop ) ;
44- }
45- else {
46- //If the type of the user prop is undefined, set query status to false, since data is not available
47- //In such cases only process if $nin is present otherwise we show the default value to the user
48- if ( query [ prop ] . $nin ) {
49- hasValue = true ;
50- queryStatus = queryStatus && processPropertyValues ( value , query , prop ) ;
54+ if ( parts [ 0 ] !== 'chr' ) {
55+ if ( typeof ( value ) !== 'undefined' ) {
56+ const filterType = Object . keys ( query [ prop ] ) [ 0 ] ;
57+
58+ if ( prop === 'up.av' && / ^ \$ ( g t | l t ) / . test ( filterType ) ) {
59+ qResult = qResult && processAppVersionValues ( user . av , { [ prop ] : query [ prop ] } , prop ) ;
60+ }
61+ else {
62+ qResult = qResult && processPropertyValues ( value , { [ prop ] : query [ prop ] } , prop ) ;
63+ }
64+ }
65+ else {
66+ //If data is not available, check for $nin and $exists operator since they can be true
67+ if ( query [ prop ] && ( '$nin' in query [ prop ] || '$exists' in query [ prop ] ) ) {
68+ qResult = qResult && processPropertyValues ( value , { [ prop ] : query [ prop ] } , prop ) ;
69+ } // Otherwise return false
70+ else {
71+ qResult = qResult && false ;
72+ }
73+ }
5174 }
5275 else {
53- queryStatus = false ;
76+ qResult = qResult && processCohortValues ( user , { chr : query [ prop ] } ) ;
5477 }
5578 }
79+
80+ return qResult ;
81+ }
82+ else if ( key === '$or' ) {
83+ return query [ key ] . some ( ( subQuery ) => matchesQuery ( user , subQuery ) ) ;
84+ }
85+ else if ( key === '$and' ) {
86+ return query [ key ] . every ( ( subQuery ) => matchesQuery ( user , subQuery ) ) ;
5687 }
5788 else {
58- hasValue = true ;
59- isCohort = true ;
89+ return false ;
6090 }
6191 }
62-
63- if ( isCohort ) {
64- queryStatus = queryStatus && processCohortValues ( user , query ) ;
65- }
66-
67- if ( ! hasValue ) {
68- //If the user does not have any user prop value, set query status to false, since data is not available
69- queryStatus = false ;
70- }
7192 }
7293
73- return queryStatus ;
94+ return matchesQuery ( inpUser , inpQuery ) ;
7495} ;
7596
7697/**
@@ -105,12 +126,34 @@ function processPropertyValues(value, query, prop) {
105126 case "$lte" : status = status && value <= query [ prop ] . $lte ; break ;
106127 case "$regex" : status = status && query [ prop ] . $regex . test ( value ) ; break ;
107128 case "$not" : status = status && ! query [ prop ] . $not . test ( value ) ; break ;
129+ case '$exists' : status = status && ( query [ prop ] . $exists === ( value !== undefined ) ) ; break ;
108130 }
109131 }
110132
111133 return status ;
112134}
113135
136+ /**
137+ * Function to process query property value
138+ * @param {Object } inpUserAv - user app version
139+ * @param {Object } query - filter
140+ * @param {String } prop - query property
141+ * @returns {Boolean } property value status
142+ */
143+ function processAppVersionValues ( inpUserAv , query , prop ) {
144+ // app version is stored in mongo like 1:1:0 instead of 1.1.0
145+ // the colons have to be replaced with dots so that semver lib can compare the app version
146+ const userAv = inpUserAv . replace ( / : / g, '.' ) ;
147+ const filterType = Object . keys ( query [ prop ] ) [ 0 ] ;
148+ const targetAv = query [ prop ] && query [ prop ] [ filterType ] && query [ prop ] [ filterType ] . replace ( / : / g, '.' ) ;
149+
150+ if ( ! semver . valid ( userAv ) || ! semver . valid ( targetAv ) ) {
151+ return false ;
152+ }
153+
154+ return semver [ filterType . slice ( 1 ) ] ( userAv , targetAv ) ;
155+ }
156+
114157/**
115158 * Function to process query cohort value
116159 * @param {Object } user - user data
@@ -162,4 +205,4 @@ function processCohortValues(user, query) {
162205 return status ;
163206}
164207
165- module . exports = remoteConfig ;
208+ module . exports = remoteConfig ;
0 commit comments