UNPKG

@react-native-ohos/realm

Version:

Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores

328 lines 13.8 kB
"use strict"; //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.submitAnalytics = exports.collectPlatformData = exports.debug = void 0; // Submits install information to Realm. // // Why are we doing this? In short, because it helps us build a better product // for you. None of the data personally identifies you, your employer or your // app, but it *will* help us understand what language you use, what Node.js // versions you target, etc. Having this info will help prioritizing our time, // adding new features and deprecating old features. Collecting an anonymized // application path & anonymized machine identifier is the only way for us to // count actual usage of the other metrics accurately. If we don’t have a way to // deduplicate the info reported, it will be useless, as a single developer // `npm install`-ing the same app 10 times would report 10 times more than another // developer that only installs once, making the data all but useless. // No one likes sharing data unless it’s necessary, we get it, and we’ve // debated adding this for a long long time. If you truly, absolutely // feel compelled to not send this data back to Realm, then you can set an // environment variable named REALM_DISABLE_ANALYTICS. // // Currently the following information is reported: // - What version of Realm is being installed. // - The OS platform and version which is being used. // - Node.js version numbers. // - JavaScript framework (React Native and Electron) version numbers. // - An anonymous machine identifier and hashed application path to aggregate // the other information on. const node_fs_1 = __importDefault(require("node:fs")); const node_path_1 = __importDefault(require("node:path")); const node_process_1 = __importDefault(require("node:process")); const node_https_1 = __importDefault(require("node:https")); const node_os_1 = __importDefault(require("node:os")); const node_console_1 = __importDefault(require("node:console")); const node_crypto_1 = require("node:crypto"); const node_buffer_1 = require("node:buffer"); const node_machine_id_1 = __importDefault(require("node-machine-id")); const debug_1 = __importDefault(require("debug")); exports.debug = (0, debug_1.default)("realm:submit-analytics"); const realmPackagePath = node_path_1.default.resolve(__dirname, "../.."); const realmCorePackagePath = node_path_1.default.resolve(realmPackagePath, "bindgen", "vendor", "realm-core"); /** * Path and credentials required to submit analytics through the webhook. */ const ANALYTICS_BASE_URL = "https://data.mongodb-api.com/app/realmsdkmetrics-zmhtm/endpoint/metric_webhook/metric"; /** * Constructs the full URL that will submit analytics to the webhook. * @param payload Information that will be submitted through the webhook. * @returns Complete analytics submission URL */ const getAnalyticsRequestUrl = (payload) => ANALYTICS_BASE_URL + "?data=" + node_buffer_1.Buffer.from(JSON.stringify(payload), "utf8").toString("base64"); /** * Generate a hash value of data using salt. * @returns base64 encoded SHA256 of data */ function sha256(data) { const salt = "Realm is great"; return (0, node_crypto_1.createHmac)("sha256", node_buffer_1.Buffer.from(salt)).update(data).digest().toString("base64"); } /** * Finds the root directory of the project. * @returns the root of the project */ function getProjectRoot() { let wd = node_process_1.default.env.npm_config_local_prefix; if (!wd) { wd = node_process_1.default.cwd(); const index = wd.indexOf("node_modules"); wd = index === -1 ? wd : wd.slice(0, index); } return wd; } /** * Finds and read package.json * @returns package.json as a JavaScript object */ function readPackageJson(packagePath) { const packageJsonPath = node_path_1.default.resolve(packagePath, "package.json"); return JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf-8")); } /** * Finds and write package.json */ function writePackageJson(packagePath, content) { const packageJsonPath = node_path_1.default.resolve(packagePath, "package.json"); node_fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(content, null, 2), "utf-8"); } /** * Heuristics to decide if analytics should be disabled. * @returns true if analytics is disabled */ function isAnalyticsDisabled() { if ("REALM_DISABLE_ANALYTICS" in node_process_1.default.env || "CI" in node_process_1.default.env) { return true; } else if ( // NODE_ENV is commonly used by JavaScript framework node_process_1.default.env["NODE_ENV"] === "production" || node_process_1.default.env["NODE_ENV"] === "test") { return true; } else { return false; } } function getRealmVersion() { return readPackageJson(realmPackagePath)["version"]; } /** * Reads and parses `dependencies.yml`. * Each line has be form "KEY: VALUE", and we to find "REALM_CORE_VERSION" * @returns the Realm Core version as a string */ function getRealmCoreVersion() { const dependenciesListPath = node_path_1.default.resolve(realmCorePackagePath, "dependencies.yml"); const dependenciesList = node_fs_1.default .readFileSync(dependenciesListPath, "utf8") .split("\n") .map((s) => s.split(":")); const versionLine = dependenciesList.find((e) => e[0] === "VERSION"); if (versionLine) { return versionLine[1]; } } /** * Save the anonymized bundle ID for later usage at runtime. */ function saveBundleId(anonymizedBundleId) { const packageJson = readPackageJson(realmPackagePath); // Initialize an object if it's missing if (typeof packageJson.config !== "object") { packageJson.config = {}; } packageJson.config.anonymizedBundleId = anonymizedBundleId; writePackageJson(realmPackagePath, packageJson); } /** * Determines if `npm` or `yarn` is used. * @returns An array with two elements: method and version */ function getInstallationMethod() { const userAgent = node_process_1.default.env["npm_config_user_agent"]; if (userAgent) { return userAgent.split(" ")[0].split("/"); } else { return ["unknown", "?.?.?"]; } } /** * Collect analytics data from the runtime system * @returns Analytics payload */ async function collectPlatformData(packagePath = getProjectRoot()) { // node-machine-id returns the ID SHA-256 hashed, if we cannot get the ID we send hostname instead let identifier; try { identifier = await node_machine_id_1.default.machineId(); } catch (err) { (0, exports.debug)(`Cannot get machine id: ${err}`); identifier = node_os_1.default.hostname(); } const realmVersion = getRealmVersion(); const realmCoreVersion = getRealmCoreVersion(); let framework = "node.js"; let frameworkVersion = node_process_1.default.version.slice(1); // skip the leading 'v' let jsEngine = "v8"; let bundleId = "unknown"; const packageJson = readPackageJson(packagePath); if (packageJson.name) { bundleId = packageJson["name"]; } if (packageJson.dependencies && packageJson.dependencies["react-native"]) { framework = "react-native"; frameworkVersion = packageJson.dependencies["react-native"]; } if (packageJson.devDependencies && packageJson.devDependencies["react-native"]) { framework = "react-native"; frameworkVersion = packageJson.devDependencies["react-native"]; } if (framework === "react-native") { try { const podfilePath = node_path_1.default.resolve(packagePath, "ios", "Podfile"); const podfile = node_fs_1.default.readFileSync(podfilePath, "utf8"); if (/hermes_enabled.*true/.test(podfile)) { jsEngine = "hermes"; } else { jsEngine = "jsc"; } } catch (err) { (0, exports.debug)(`Cannot read ios/Podfile: ${err}`); jsEngine = "unknown"; } try { const rnPath = node_path_1.default.resolve(packagePath, "node_modules", "react-native", "package.json"); const rnPackageJson = JSON.parse(node_fs_1.default.readFileSync(rnPath, "utf-8")); frameworkVersion = rnPackageJson["version"]; } catch (err) { (0, exports.debug)(`Cannot read react-native package.json: ${err}`); } } if (packageJson.dependencies && packageJson.dependencies["electron"]) { framework = "electron"; frameworkVersion = packageJson.dependencies["electron"]; } if (packageJson.devDependencies && packageJson.devDependencies["electron"]) { framework = "electron"; frameworkVersion = packageJson.devDependencies["electron"]; } if (framework === "electron") { try { const electronPath = node_path_1.default.resolve(packagePath, "node_modules", "electron", "package.json"); const electronPackageJson = JSON.parse(node_fs_1.default.readFileSync(electronPath, "utf-8")); frameworkVersion = electronPackageJson["version"]; } catch (err) { (0, exports.debug)(`Cannot read electron package.json: ${err}`); } } // JavaScript or TypeScript - we don't consider Flow as a programming language let language = "javascript"; let languageVersion = "unknown"; if (packageJson.dependencies && packageJson.dependencies["typescript"]) { language = "typescript"; languageVersion = packageJson.dependencies["typescript"]; } if (packageJson.devDependencies && packageJson.devDependencies["typescript"]) { language = "typescript"; languageVersion = packageJson.devDependencies["typescript"]; } if (language === "typescript") { try { const typescriptPath = node_path_1.default.resolve(packagePath, "node_modules", "typescript", "package.json"); const typescriptPackageJson = JSON.parse(node_fs_1.default.readFileSync(typescriptPath, "utf-8")); languageVersion = typescriptPackageJson["version"]; } catch (err) { (0, exports.debug)(`Cannot read typescript package.json: ${err}`); } } const installationMethod = getInstallationMethod(); return { token: "ce0fac19508f6c8f20066d345d360fd0", "JS Analytics Version": 3, distinct_id: identifier, "Anonymized Builder Id": sha256(identifier), "Anonymized Bundle Id": sha256(bundleId), "Realm Version": realmVersion, Binding: "Javascript", Version: packageJson.version, Language: language, "Language Version": languageVersion, Framework: framework, "Framework Version": frameworkVersion, "Host OS Type": node_os_1.default.platform(), "Host OS Version": node_os_1.default.release(), "Host CPU Arch": node_os_1.default.arch(), "Node.js version": node_process_1.default.version.slice(1), "Core Version": realmCoreVersion, "Sync Enabled": true, "Installation Method": installationMethod[0], "Installation Method Version": installationMethod[1], "Runtime Engine": jsEngine, }; } exports.collectPlatformData = collectPlatformData; /** * Collect and send analytics data to MongoDB over HTTPS * If `REALM_DISABLE_ANALYTICS` is set, no data is submitted to MongoDB */ async function submitAnalytics() { const data = await collectPlatformData(); const payload = { event: "install", properties: data, }; (0, exports.debug)(`payload: ${JSON.stringify(payload)}`); if ("REALM_PRINT_ANALYTICS" in node_process_1.default.env) { node_console_1.default.log("REALM ANALYTICS", JSON.stringify(data, null, 2)); } if (isAnalyticsDisabled()) { (0, exports.debug)("Analytics is disabled"); return; } saveBundleId(data["Anonymized Bundle Id"]); return new Promise((resolve, reject) => { // node 19 turns on keep-alive by default and it will make the https.get() to hang // https://github.com/realm/realm-js/issues/5136 const requestUrl = getAnalyticsRequestUrl(payload); node_https_1.default .get(requestUrl, { agent: new node_https_1.default.Agent({ keepAlive: false }) }, (res) => { resolve({ statusCode: res.statusCode, statusMessage: res.statusMessage, }); }) .on("error", (error) => { const message = error && error.message ? error.message : error; const err = new Error(`Failed to dispatch analytics: ${message}`); reject(err); }); }); } exports.submitAnalytics = submitAnalytics; //# sourceMappingURL=submit-analytics.js.map