@@ -79,10 +79,11 @@ tracker.enable = function() {
7979 Countly . track_errors ( ) ;
8080 }
8181 setTimeout ( function ( ) {
82- var custom = tracker . getAllData ( ) ;
83- if ( Object . keys ( custom ) . length ) {
84- Countly . user_details ( { "custom" : custom } ) ;
85- }
82+ tracker . getAllData ( ) . then ( ( custom ) => {
83+ if ( Object . keys ( custom ) . length ) {
84+ Countly . user_details ( { "custom" : custom } ) ;
85+ }
86+ } ) ;
8687 } , 20000 ) ;
8788 }
8889} ;
@@ -167,30 +168,33 @@ tracker.getSDK = function() {
167168
168169/**
169170* Get server stats
171+ * @returns {Promise<Object> } server stats
170172**/
171173tracker . collectServerStats = function ( ) {
172174 var props = { } ;
173- stats . getServer ( common . db , function ( data ) {
174- common . db . collection ( "apps" ) . aggregate ( [ { $project : { last_data : 1 } } , { $sort : { "last_data" : - 1 } } , { $limit : 1 } ] , { allowDiskUse : true } , function ( errApps , resApps ) {
175- common . db . collection ( "members" ) . aggregate ( [ { $project : { last_login : 1 } } , { $sort : { "last_login" : - 1 } } , { $limit : 1 } ] , { allowDiskUse : true } , function ( errLogin , resLogin ) {
176- if ( resApps && resApps [ 0 ] ) {
177- props . last_data = resApps [ 0 ] . last_data || 0 ;
178- }
179- if ( resLogin && resLogin [ 0 ] ) {
180- props . last_login = resLogin [ 0 ] . last_login || 0 ;
181- }
182- if ( data ) {
183- if ( data . app_users ) {
184- props . app_users = data . app_users ;
175+ return new Promise ( ( resolve ) => {
176+ stats . getServer ( common . db , function ( data ) {
177+ common . db . collection ( "apps" ) . aggregate ( [ { $project : { last_data : 1 } } , { $sort : { "last_data" : - 1 } } , { $limit : 1 } ] , { allowDiskUse : true } , function ( errApps , resApps ) {
178+ common . db . collection ( "members" ) . aggregate ( [ { $project : { last_login : 1 } } , { $sort : { "last_login" : - 1 } } , { $limit : 1 } ] , { allowDiskUse : true } , function ( errLogin , resLogin ) {
179+ if ( resApps && resApps [ 0 ] ) {
180+ props . last_data = resApps [ 0 ] . last_data || 0 ;
185181 }
186- if ( data . apps ) {
187- props . apps = data . apps ;
182+ if ( resLogin && resLogin [ 0 ] ) {
183+ props . last_login = resLogin [ 0 ] . last_login || 0 ;
188184 }
189- if ( data . users ) {
190- props . users = data . users ;
185+ if ( data ) {
186+ if ( data . app_users ) {
187+ props . app_users = data . app_users ;
188+ }
189+ if ( data . apps ) {
190+ props . apps = data . apps ;
191+ }
192+ if ( data . users ) {
193+ props . users = data . users ;
194+ }
191195 }
192- }
193- return props ;
196+ resolve ( props ) ;
197+ } ) ;
194198 } ) ;
195199 } ) ;
196200 } ) ;
@@ -200,12 +204,13 @@ tracker.collectServerStats = function() {
200204* Get server data
201205* @returns {Object } server data
202206**/
203- tracker . collectServerData = function ( ) {
207+ tracker . collectServerData = async function ( ) {
204208 var props = { } ;
205209 props . trial = versionInfo . trial ? true : false ;
206210 props . plugins = plugins . getPlugins ( ) ;
207211 props . nodejs = process . version ;
208212 props . countly = versionInfo . version ;
213+ props . docker = hasDockerEnv ( ) || hasDockerCGroup ( ) || hasDockerMountInfo ( ) ;
209214 var edition = "Lite" ;
210215 if ( IS_FLEX ) {
211216 edition = "Flex" ;
@@ -217,22 +222,230 @@ tracker.collectServerData = function() {
217222 if ( common . db . build && common . db . build . version ) {
218223 props . mongodb = common . db . build . version ;
219224 }
225+ const sdkData = await tracker . getSDKData ( ) ;
226+ if ( sdkData && sdkData . sdk_versions && Object . keys ( sdkData . sdk_versions ) . length ) {
227+ props . sdks = Object . keys ( sdkData . sdk_versions ) ;
228+ for ( const [ key , value ] of Object . entries ( sdkData . sdk_versions ) ) {
229+ props [ key ] = value ;
230+ }
231+ }
232+
220233 return props ;
221234} ;
222235
223236/**
224237 * Get all eligible data
225238 * @returns {Object } all eligible data
226239 */
227- tracker . getAllData = function ( ) {
240+ tracker . getAllData = async function ( ) {
228241 var props = { } ;
229242 if ( plugins . getConfig ( "tracking" ) . server_user_details ) {
230- Object . assign ( props , tracker . collectServerStats ( ) ) ;
243+ Object . assign ( props , await tracker . collectServerStats ( ) ) ;
231244 }
232- Object . assign ( props , tracker . collectServerData ( ) ) ;
245+ Object . assign ( props , await tracker . collectServerData ( ) ) ;
233246 return props ;
234247} ;
235248
249+ /**
250+ * Query sdks collection for current and previous year (month 0) and combine meta_v2 data
251+ * @returns {Promise<Object> } Combined meta_v2 data from all matching documents
252+ */
253+ tracker . getSDKData = async function ( ) {
254+ var currentYear = new Date ( ) . getFullYear ( ) ;
255+ var previousYear = currentYear - 1 ;
256+
257+ // Build regex pattern to match: appid_YYYY:0_shard
258+ // Matches any app ID, year (current or previous), month 0, and any shard number
259+ var yearPattern = `(${ currentYear } |${ previousYear } )` ;
260+ var pattern = new RegExp ( `^[a-f0-9]{24}_${ yearPattern } :0_\\d+$` ) ;
261+
262+ try {
263+ // Use aggregation pipeline to combine meta_v2 data on MongoDB side
264+ var pipeline = [
265+ // Match documents for current and previous year, month 0, any shard
266+ {
267+ $match : {
268+ _id : pattern
269+ }
270+ } ,
271+ // Project only meta_v2 field and convert to array of key-value pairs
272+ {
273+ $project : {
274+ meta_v2 : { $objectToArray : "$meta_v2" }
275+ }
276+ } ,
277+ // Unwind meta_v2 array to process each meta key separately
278+ {
279+ $unwind : "$meta_v2"
280+ } ,
281+ // Convert nested objects to arrays for merging
282+ {
283+ $project : {
284+ metaKey : "$meta_v2.k" ,
285+ metaValue : { $objectToArray : "$meta_v2.v" }
286+ }
287+ } ,
288+ // Unwind nested values
289+ {
290+ $unwind : "$metaValue"
291+ } ,
292+ // Group by meta key and inner key to collect all unique combinations
293+ {
294+ $group : {
295+ _id : {
296+ metaKey : "$metaKey" ,
297+ innerKey : "$metaValue.k"
298+ } ,
299+ value : { $first : "$metaValue.v" }
300+ }
301+ } ,
302+ // Group by meta key to rebuild nested structure
303+ {
304+ $group : {
305+ _id : "$_id.metaKey" ,
306+ values : {
307+ $push : {
308+ k : "$_id.innerKey" ,
309+ v : "$value"
310+ }
311+ }
312+ }
313+ } ,
314+ // Convert arrays back to objects
315+ {
316+ $project : {
317+ _id : 0 ,
318+ k : "$_id" ,
319+ v : { $arrayToObject : "$values" }
320+ }
321+ } ,
322+ // Group all into single document
323+ {
324+ $group : {
325+ _id : null ,
326+ meta_v2 : {
327+ $push : {
328+ k : "$k" ,
329+ v : "$v"
330+ }
331+ }
332+ }
333+ } ,
334+ // Convert final array to object
335+ {
336+ $project : {
337+ _id : 0 ,
338+ meta_v2 : { $arrayToObject : "$meta_v2" }
339+ }
340+ }
341+ ] ;
342+
343+ var result = await common . db . collection ( "sdks" ) . aggregate ( pipeline ) . toArray ( ) ;
344+
345+ // Extract combined meta_v2 or return empty object if no results
346+ var combinedMeta = ( result && result [ 0 ] && result [ 0 ] . meta_v2 ) ? result [ 0 ] . meta_v2 : { } ;
347+
348+ // Process sdk_version to extract highest version per SDK
349+ var sdkVersions = { } ;
350+ if ( combinedMeta . sdk_version ) {
351+ for ( var versionKey in combinedMeta . sdk_version ) {
352+ // Parse SDK version format: [sdk_name]_major:minor:patch
353+ var match = versionKey . match ( / ^ \[ ( [ ^ \] ] + ) \] _ ( \d + ) : ( \d + ) : ( \d + ) $ / ) ;
354+ if ( match ) {
355+ var sdkName = match [ 1 ] ;
356+ var major = parseInt ( match [ 2 ] , 10 ) ;
357+ var minor = parseInt ( match [ 3 ] , 10 ) ;
358+ var patch = parseInt ( match [ 4 ] , 10 ) ;
359+
360+ // Check if this SDK exists and compare versions
361+ if ( ! sdkVersions [ sdkName ] ) {
362+ sdkVersions [ sdkName ] = {
363+ version : `${ major } .${ minor } .${ patch } ` ,
364+ major : major ,
365+ minor : minor ,
366+ patch : patch
367+ } ;
368+ }
369+ else {
370+ var current = sdkVersions [ sdkName ] ;
371+ // Compare versions (major.minor.patch)
372+ if ( major > current . major ||
373+ ( major === current . major && minor > current . minor ) ||
374+ ( major === current . major && minor === current . minor && patch > current . patch ) ) {
375+ sdkVersions [ sdkName ] = {
376+ version : `${ major } .${ minor } .${ patch } ` ,
377+ major : major ,
378+ minor : minor ,
379+ patch : patch
380+ } ;
381+ }
382+ }
383+ }
384+ }
385+ }
386+
387+ // Convert to simple object with just SDK name -> version string
388+ var simpleSdkVersions = { } ;
389+ for ( var sdk in sdkVersions ) {
390+ simpleSdkVersions [ `sdk_${ sdk } ` ] = sdkVersions [ sdk ] . version ;
391+ }
392+
393+ return {
394+ meta_v2 : combinedMeta ,
395+ sdk_versions : simpleSdkVersions ,
396+ years : [ previousYear , currentYear ] ,
397+ month : 0
398+ } ;
399+ }
400+ catch ( error ) {
401+ logger ( "tracker:server" ) . error ( "Error querying SDK data:" , error ) ;
402+ return {
403+ meta_v2 : { } ,
404+ error : error . message
405+ } ;
406+ }
407+ } ;
408+
409+ /**
410+ * Check if running in Docker environment
411+ * @returns {boolean } if running in docker
412+ */
413+ function hasDockerEnv ( ) {
414+ try {
415+ fs . statSync ( '/.dockerenv' ) ;
416+ return true ;
417+ }
418+ catch {
419+ return false ;
420+ }
421+ }
422+
423+ /**
424+ * Check if running in Docker by inspecting cgroup info
425+ * @returns {boolean } if running in docker
426+ */
427+ function hasDockerCGroup ( ) {
428+ try {
429+ return fs . readFileSync ( '/proc/self/cgroup' , 'utf8' ) . includes ( 'docker' ) ;
430+ }
431+ catch {
432+ return false ;
433+ }
434+ }
435+
436+ /**
437+ * Check if running in Docker by inspecting mountinfo
438+ * @returns {boolean } if running in docker
439+ */
440+ function hasDockerMountInfo ( ) {
441+ try {
442+ return fs . readFileSync ( '/proc/self/mountinfo' , 'utf8' ) . includes ( '/docker/containers/' ) ;
443+ }
444+ catch {
445+ return false ;
446+ }
447+ }
448+
236449/**
237450* Strip traling slashes from url
238451* @param {string } str - url to strip
0 commit comments