@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
JavaScript
;
////////////////////////////////////////////////////////////////////////////
//
// 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