|
1 | 1 | /** |
2 | | -* Module for some common utility functions and references |
3 | | -* @module api/utils/common |
4 | | -*/ |
5 | | - |
| 2 | + * Module for some common utility functions and references |
| 3 | + * @module api/utils/common |
| 4 | + */ |
6 | 5 | /** |
7 | 6 | * @typedef {import('../../types/requestProcessor').Params} Params |
8 | 7 | * @typedef {import('../../types/common').TimeObject} TimeObject |
|
12 | 11 |
|
13 | 12 | /** @lends module:api/utils/common **/ |
14 | 13 | /** @type(import('../../types/common').Common) */ |
15 | | -var common = {}; |
| 14 | +const common = {}; |
16 | 15 |
|
17 | 16 | /** @type(import('moment-timezone')) */ |
18 | | -var moment = require('moment-timezone'); |
19 | | -var crypto = require('crypto'), |
20 | | - logger = require('./log.js'), |
21 | | - mcc_mnc_list = require('mcc-mnc-list'), |
22 | | - plugins = require('../../plugins/pluginManager.js'), |
23 | | - countlyConfig = require('./../config', 'dont-enclose'), |
24 | | - argon2 = require('argon2'), |
25 | | - mongodb = require('mongodb'), |
26 | | - getRandomValues = require('get-random-values'), |
27 | | - _ = require('lodash'); |
| 17 | +const moment = require('moment-timezone'); |
| 18 | +const crypto = require('crypto'); |
| 19 | +const logger = require('./log.js'); |
| 20 | +const mcc_mnc_list = require('mcc-mnc-list'); |
| 21 | +const plugins = require('../../plugins/pluginManager.js'); |
| 22 | +const countlyConfig = require('./../config', 'dont-enclose'); |
| 23 | +const argon2 = require('argon2'); |
| 24 | +const mongodb = require('mongodb'); |
| 25 | +const getRandomValues = require('get-random-values'); |
| 26 | +const semver = require('semver'); |
| 27 | +const _ = require('lodash'); |
28 | 28 |
|
29 | 29 | var matchHtmlRegExp = /"|'|&(?!amp;|quot;|#39;|lt;|gt;|#46;|#36;)|<|>/; |
30 | 30 | var matchLessHtmlRegExp = /[<>]/; |
@@ -210,6 +210,9 @@ common.dbUserMap = { |
210 | 210 | 'platform': 'p', |
211 | 211 | 'platform_version': 'pv', |
212 | 212 | 'app_version': 'av', |
| 213 | + 'app_version_major': 'av_major', |
| 214 | + 'app_version_minor': 'av_minor', |
| 215 | + 'app_version_patch': 'av_patch', |
213 | 216 | 'last_begin_session_timestamp': 'lbst', |
214 | 217 | 'last_end_session_timestamp': 'lest', |
215 | 218 | 'has_ongoing_session': 'hos', |
@@ -2055,6 +2058,134 @@ common.versionCompare = function(v1, v2, options) { |
2055 | 2058 | return compareParts; |
2056 | 2059 | }; |
2057 | 2060 |
|
| 2061 | +/** |
| 2062 | + * Parse app_version into major, minor, patch components |
| 2063 | + * @param {string|number} version - The version to parse |
| 2064 | + * @returns {object} Object containing major, minor, patch, original version, and success flag |
| 2065 | + */ |
| 2066 | +common.parseAppVersion = function(version) { |
| 2067 | + try { |
| 2068 | + if (typeof version !== 'string') { |
| 2069 | + version = String(version); |
| 2070 | + } |
| 2071 | + |
| 2072 | + // Ensure version has at least one decimal point |
| 2073 | + if (version.indexOf('.') === -1) { |
| 2074 | + version += '.0'; |
| 2075 | + } |
| 2076 | + |
| 2077 | + const parsedVersion = semver.valid(semver.coerce(version)); |
| 2078 | + if (parsedVersion) { |
| 2079 | + const versionObj = semver.parse(parsedVersion); |
| 2080 | + if (versionObj) { |
| 2081 | + return { |
| 2082 | + major: versionObj.major, |
| 2083 | + minor: versionObj.minor, |
| 2084 | + patch: versionObj.patch, |
| 2085 | + original: version, |
| 2086 | + success: true |
| 2087 | + }; |
| 2088 | + } |
| 2089 | + } |
| 2090 | + } |
| 2091 | + catch (error) { |
| 2092 | + // Silently catch any errors from semver library |
| 2093 | + // console.error('Error parsing app version:', error); |
| 2094 | + } |
| 2095 | + |
| 2096 | + // Return only original version with success=false if parsing fails or throws an exception |
| 2097 | + return { |
| 2098 | + original: version, |
| 2099 | + success: false |
| 2100 | + }; |
| 2101 | +}; |
| 2102 | + |
| 2103 | +/** |
| 2104 | + * Check if a version string follows some kind of scheme (there is only semantic versioning (semver) for now) |
| 2105 | + * @param {string} inpVersion - an app version string |
| 2106 | + * @return {array} [regex.exec result, version scheme name] |
| 2107 | + */ |
| 2108 | +common.checkAppVersion = function(inpVersion) { |
| 2109 | + // Regex is from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string |
| 2110 | + const semverRgx = /(^v?)(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; |
| 2111 | + // Half semver is similar to semver but with only one dot |
| 2112 | + const halfSemverRgx = /(^v?)(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; |
| 2113 | + |
| 2114 | + let execResult = semverRgx.exec(inpVersion); |
| 2115 | + |
| 2116 | + if (execResult) { |
| 2117 | + return [execResult, 'semver']; |
| 2118 | + } |
| 2119 | + |
| 2120 | + execResult = halfSemverRgx.exec(inpVersion); |
| 2121 | + |
| 2122 | + if (execResult) { |
| 2123 | + return [execResult, 'halfSemver']; |
| 2124 | + } |
| 2125 | + |
| 2126 | + return [null, null]; |
| 2127 | +}; |
| 2128 | + |
| 2129 | +/** |
| 2130 | + * Transform a version string so it will be numerically correct when sorted |
| 2131 | + * For example '1.10.2' will be transformed to '100001.100010.100002' |
| 2132 | + * So when sorted ascending it will come after '1.2.0' ('100001.100002.100000') |
| 2133 | + * @param {string} inpVersion - an app version string |
| 2134 | + * @return {string} the transformed app version |
| 2135 | + * @note Imported and moved from @module plugins/crashes/api/parts/version (which has now been deprecated) |
| 2136 | + */ |
| 2137 | +common.transformAppVersion = function(inpVersion) { |
| 2138 | + const [execResult, versionScheme] = common.checkAppVersion(inpVersion); |
| 2139 | + |
| 2140 | + if (execResult === null) { |
| 2141 | + // Version string does not follow any scheme, just return it |
| 2142 | + return inpVersion; |
| 2143 | + } |
| 2144 | + |
| 2145 | + // Mark version parts based on semver scheme |
| 2146 | + let prefixIdx = 1; |
| 2147 | + let majorIdx = 2; |
| 2148 | + let minorIdx = 3; |
| 2149 | + let patchIdx = 4; |
| 2150 | + let preReleaseIdx = 5; |
| 2151 | + let buildIdx = 6; |
| 2152 | + |
| 2153 | + if (versionScheme === 'halfSemver') { |
| 2154 | + patchIdx -= 1; |
| 2155 | + preReleaseIdx -= 1; |
| 2156 | + buildIdx -= 1; |
| 2157 | + } |
| 2158 | + |
| 2159 | + let transformed = ''; |
| 2160 | + // Rejoin version parts to a new string |
| 2161 | + for (let idx = prefixIdx; idx < buildIdx; idx += 1) { |
| 2162 | + let part = execResult[idx]; |
| 2163 | + |
| 2164 | + if (part) { |
| 2165 | + if (idx >= majorIdx && idx <= patchIdx) { |
| 2166 | + part = 100000 + parseInt(part, 10); |
| 2167 | + } |
| 2168 | + |
| 2169 | + if (idx >= minorIdx && idx <= patchIdx) { |
| 2170 | + part = '.' + part; |
| 2171 | + } |
| 2172 | + |
| 2173 | + if (idx === preReleaseIdx) { |
| 2174 | + part = '-' + part; |
| 2175 | + } |
| 2176 | + |
| 2177 | + if (idx === buildIdx) { |
| 2178 | + part = '+' + part; |
| 2179 | + } |
| 2180 | + |
| 2181 | + transformed += part; |
| 2182 | + } |
| 2183 | + } |
| 2184 | + |
| 2185 | + return transformed; |
| 2186 | +}; |
| 2187 | + |
| 2188 | + |
2058 | 2189 | common.adjustTimestampByTimezone = function(ts, tz) { |
2059 | 2190 | var d = moment(); |
2060 | 2191 | if (tz) { |
|
0 commit comments