@mui/x-telemetry
Advanced tools
| export interface TelemetryContextType { | ||
| config: { | ||
| isInitialized: boolean; | ||
| }; | ||
| traits: Record<string, any> & { | ||
| machineId?: string | null; | ||
| fingerprint?: { | ||
| fullHash?: string | null; | ||
| coreHash?: string | null; | ||
| components?: Record<string, any> | null; | ||
| } | null; | ||
| projectId?: string | null; | ||
| anonymousId?: string | null; | ||
| sessionId?: string | null; | ||
| isDocker?: boolean; | ||
| isCI?: boolean; | ||
| }; | ||
| } | ||
| declare const defaultValue: TelemetryContextType; | ||
| export default defaultValue; |
+10
| // This file will be modified by the `postinstall` script. | ||
| // See postinstall/index.ts for more information. | ||
| const defaultValue = { | ||
| config: { | ||
| isInitialized: false | ||
| }, | ||
| traits: {} | ||
| }; | ||
| export default defaultValue; |
| import muiXTelemetryEvents from "./runtime/events.mjs"; | ||
| import sendMuiXTelemetryEventOriginal from "./runtime/sender.mjs"; | ||
| declare const sendMuiXTelemetryEvent: typeof sendMuiXTelemetryEventOriginal | (() => void); | ||
| declare const muiXTelemetrySettings: { | ||
| enableDebug: () => void; | ||
| enableTelemetry: () => void; | ||
| disableTelemetry: () => void; | ||
| }; | ||
| export { muiXTelemetryEvents, sendMuiXTelemetryEvent, muiXTelemetrySettings }; |
+22
| /** | ||
| * @mui/x-telemetry v9.0.0-alpha.2 | ||
| * | ||
| * @license SEE LICENSE IN LICENSE | ||
| * This source code is licensed under the SEE LICENSE IN LICENSE license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
| import muiXTelemetryEvents from "./runtime/events.mjs"; | ||
| import sendMuiXTelemetryEventOriginal from "./runtime/sender.mjs"; | ||
| import muiXTelemetrySettingsOriginal from "./runtime/settings.mjs"; | ||
| const noop = () => {}; | ||
| // To cut unused imports in production as early as possible | ||
| const sendMuiXTelemetryEvent = process.env.NODE_ENV === 'production' ? noop : sendMuiXTelemetryEventOriginal; | ||
| // To cut unused imports in production as early as possible | ||
| const muiXTelemetrySettings = process.env.NODE_ENV === 'production' ? { | ||
| enableDebug: noop, | ||
| enableTelemetry: noop, | ||
| disableTelemetry: noop | ||
| } : muiXTelemetrySettingsOriginal; | ||
| export { muiXTelemetryEvents, sendMuiXTelemetryEvent, muiXTelemetrySettings }; |
| import isDockerFunction from 'is-docker'; | ||
| import ciEnvironment from 'ci-info'; | ||
| let traits; | ||
| export default function getEnvironmentInfo() { | ||
| if (!traits) { | ||
| traits = { | ||
| isDocker: isDockerFunction(), | ||
| isCI: ciEnvironment.isCI | ||
| }; | ||
| } | ||
| return traits; | ||
| } |
| import { createHash } from 'crypto'; | ||
| export default async function getAnonymousMachineId() { | ||
| try { | ||
| const nodeMachineId = await import('node-machine-id'); | ||
| const rawMachineId = await nodeMachineId.machineId(true); | ||
| if (!rawMachineId) { | ||
| return null; | ||
| } | ||
| return createHash('sha256').update(rawMachineId).digest('hex'); | ||
| } catch (_) { | ||
| // Ignore any errors | ||
| return null; | ||
| } | ||
| } |
| import { exec } from 'child_process'; | ||
| import { createHash } from 'crypto'; | ||
| import util from 'util'; | ||
| const asyncExec = util.promisify(exec); | ||
| async function execCLI(command) { | ||
| try { | ||
| const response = await asyncExec(command, { | ||
| timeout: 1000, | ||
| windowsHide: true | ||
| }); | ||
| return String(response).trim(); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| // Q: Why does MUI need a project ID? Why is it looking at my git remote? | ||
| // A: | ||
| // MUI's telemetry always anonymizes these values. We need a way to | ||
| // differentiate different projects to track feature usage accurately. | ||
| // For example, to prevent a feature from appearing to be constantly `used` | ||
| // and then `unused` when switching between local projects. | ||
| async function getRawProjectId() { | ||
| return (await execCLI(`git config --local --get remote.origin.url`)) || process.env.REPOSITORY_URL || (await execCLI(`git rev-parse --show-toplevel`)) || process.cwd(); | ||
| } | ||
| export default async function getAnonymousProjectId() { | ||
| const rawProjectId = await getRawProjectId(); | ||
| return createHash('sha256').update(rawProjectId).digest('hex'); | ||
| } |
| import _extends from "@babel/runtime/helpers/esm/extends"; | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import { randomBytes } from 'crypto'; | ||
| import { fileURLToPath } from 'url'; | ||
| import getEnvironmentInfo from "./get-environment-info.mjs"; | ||
| import getAnonymousProjectId from "./get-project-id.mjs"; | ||
| import getAnonymousMachineId from "./get-machine-id.mjs"; | ||
| import { TelemetryStorage } from "./storage.mjs"; | ||
| const dirname = typeof __dirname === 'string' ? __dirname // cjs build in root dir | ||
| : (() => { | ||
| const filename = fileURLToPath(import.meta.url); | ||
| // esm build in `esm` directory, so we need to go up two levels | ||
| return path.dirname(path.dirname(filename)); | ||
| })(); | ||
| (async () => { | ||
| // If Node.js support permissions, we need to check if the current user has | ||
| // the necessary permissions to write to the file system. | ||
| if (typeof process.permission !== 'undefined' && !(process.permission.has('fs.read') && process.permission.has('fs.write'))) { | ||
| return; | ||
| } | ||
| const storage = await TelemetryStorage.init({ | ||
| distDir: process.cwd() | ||
| }); | ||
| const [environmentInfo, projectId, machineId] = await Promise.all([getEnvironmentInfo(), getAnonymousProjectId(), getAnonymousMachineId()]); | ||
| const contextData = { | ||
| config: { | ||
| isInitialized: true | ||
| }, | ||
| traits: _extends({}, environmentInfo, { | ||
| machineId, | ||
| projectId, | ||
| sessionId: randomBytes(32).toString('hex'), | ||
| anonymousId: storage.anonymousId | ||
| }) | ||
| }; | ||
| const writeContextData = (filePath, format) => { | ||
| const targetPath = path.resolve(dirname, '..', filePath, 'context.js'); | ||
| fs.writeFileSync(targetPath, format(JSON.stringify(contextData, null, 2))); | ||
| }; | ||
| writeContextData('esm', content => `export default ${content};`); | ||
| writeContextData('', content => [`"use strict";`, `Object.defineProperty(exports, "__esModule", { value: true });`, `exports.default = void 0;`, `var _default = exports.default = ${content};`].join('\n')); | ||
| })().catch(error => { | ||
| console.error('[telemetry] Failed to make initialization. Please, report error to MUI X team:\n' + 'https://mui.com/r/x-telemetry-postinstall-troubleshoot\n', error); | ||
| }); |
| export default function notifyAboutMuiXTelemetry() { | ||
| console.log(`[Attention]: MUI X now collects completely anonymous telemetry regarding usage.`); | ||
| console.log(`This information is used to shape MUI's roadmap and prioritize features.`); | ||
| console.log(`You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:`); | ||
| console.log('https://mui.com/x/guides/telemetry'); | ||
| console.log(); | ||
| } |
| import { randomBytes } from 'crypto'; | ||
| import fs from 'fs'; | ||
| import os from 'os'; | ||
| import path from 'path'; | ||
| import notifyAboutMuiXTelemetry from "./notify.mjs"; | ||
| import getEnvironmentInfo from "./get-environment-info.mjs"; | ||
| // This is the key that specifies when the user was informed about telemetry collection. | ||
| const TELEMETRY_KEY_NOTIFY_DATE = 'telemetry.notifiedAt'; | ||
| // This is a quasi-persistent identifier used to dedupe recurring events. It's | ||
| // generated from random data and completely anonymous. | ||
| const TELEMETRY_KEY_ID = `telemetry.anonymousId`; | ||
| const CONFIG_FILE_NAME = 'config.json'; | ||
| const PROJECT_NAME = 'mui-x'; | ||
| function getConfigDirectory(distDir) { | ||
| const env = getEnvironmentInfo(); | ||
| const isLikelyEphemeral = env.isCI || env.isDocker; | ||
| if (isLikelyEphemeral) { | ||
| return path.join(distDir, 'cache', PROJECT_NAME); | ||
| } | ||
| const { | ||
| platform | ||
| } = process; | ||
| const homedir = os.homedir(); | ||
| if (platform === 'darwin') { | ||
| return path.join(homedir, 'Library', 'Preferences', PROJECT_NAME); | ||
| } | ||
| if (platform === 'win32') { | ||
| const appData = process.env.APPDATA || path.join(homedir, 'AppData', 'Roaming'); | ||
| return path.join(appData, PROJECT_NAME, 'Config'); | ||
| } | ||
| // Linux / others: follow XDG Base Directory specification | ||
| const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(homedir, '.config'); | ||
| return path.join(xdgConfig, PROJECT_NAME); | ||
| } | ||
| function readConfigFile(configPath) { | ||
| try { | ||
| return JSON.parse(fs.readFileSync(configPath, 'utf-8')); | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
| function writeConfigFile(configPath, data) { | ||
| const dir = path.dirname(configPath); | ||
| fs.mkdirSync(dir, { | ||
| recursive: true | ||
| }); | ||
| fs.writeFileSync(configPath, JSON.stringify(data, null, '\t')); | ||
| } | ||
| export class TelemetryStorage { | ||
| static async init({ | ||
| distDir | ||
| }) { | ||
| const configDirectory = getConfigDirectory(distDir); | ||
| let configFilePath = null; | ||
| try { | ||
| configFilePath = path.join(configDirectory, CONFIG_FILE_NAME); | ||
| // Verify write access by ensuring the directory exists | ||
| fs.mkdirSync(configDirectory, { | ||
| recursive: true | ||
| }); | ||
| } catch { | ||
| configFilePath = null; | ||
| } | ||
| return new TelemetryStorage(configFilePath); | ||
| } | ||
| constructor(filePath) { | ||
| this.configPath = filePath; | ||
| this.notify(); | ||
| } | ||
| notify = () => { | ||
| if (!this.configPath) { | ||
| return; | ||
| } | ||
| // The end-user has already been notified about our telemetry integration. We | ||
| // don't need to constantly annoy them about it. | ||
| // We will re-inform users about the telemetry if significant changes are | ||
| // ever made. | ||
| const data = readConfigFile(this.configPath); | ||
| if (data[TELEMETRY_KEY_NOTIFY_DATE]) { | ||
| return; | ||
| } | ||
| data[TELEMETRY_KEY_NOTIFY_DATE] = Date.now().toString(); | ||
| writeConfigFile(this.configPath, data); | ||
| notifyAboutMuiXTelemetry(); | ||
| }; | ||
| get anonymousId() { | ||
| if (this.configPath) { | ||
| const data = readConfigFile(this.configPath); | ||
| const existing = data[TELEMETRY_KEY_ID]; | ||
| if (typeof existing === 'string') { | ||
| return existing; | ||
| } | ||
| const generated = randomBytes(32).toString('hex'); | ||
| data[TELEMETRY_KEY_ID] = generated; | ||
| writeConfigFile(this.configPath, data); | ||
| return generated; | ||
| } | ||
| return randomBytes(32).toString('hex'); | ||
| } | ||
| } |
| interface TelemetryEnvConfig { | ||
| NODE_ENV: string | '<unknown>'; | ||
| IS_COLLECTING: boolean | undefined; | ||
| DEBUG: boolean; | ||
| } | ||
| declare global { | ||
| var __MUI_X_TELEMETRY_DISABLED__: boolean | undefined; | ||
| } | ||
| export declare function getTelemetryEnvConfig(skipCache?: boolean): TelemetryEnvConfig; | ||
| export declare function getTelemetryEnvConfigValue<K extends keyof TelemetryEnvConfig>(key: K): TelemetryEnvConfig[K]; | ||
| export declare function setTelemetryEnvConfigValue<K extends keyof TelemetryEnvConfig>(key: K, value: NonNullable<TelemetryEnvConfig[K]>): void; | ||
| export {}; |
| const envEnabledValues = ['1', 'true', 'yes', 'y']; | ||
| const envDisabledValues = ['0', 'false', 'no', 'n']; | ||
| function getBooleanEnv(value) { | ||
| if (!value) { | ||
| return undefined; | ||
| } | ||
| if (envEnabledValues.includes(value)) { | ||
| return true; | ||
| } | ||
| if (envDisabledValues.includes(value)) { | ||
| return false; | ||
| } | ||
| return undefined; | ||
| } | ||
| function getBooleanEnvFromEnvObject(envKey, envObj) { | ||
| const keys = Object.keys(envObj); | ||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i]; | ||
| if (!key.endsWith(envKey)) { | ||
| continue; | ||
| } | ||
| const value = getBooleanEnv(envObj[key]?.toLowerCase()); | ||
| if (typeof value === 'boolean') { | ||
| return value; | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
| function getIsTelemetryCollecting() { | ||
| // Check global variable | ||
| // eslint-disable-next-line no-underscore-dangle | ||
| const globalValue = globalThis.__MUI_X_TELEMETRY_DISABLED__; | ||
| if (typeof globalValue === 'boolean') { | ||
| // If disabled=true, telemetry is disabled | ||
| // If disabled=false, telemetry is enabled | ||
| return !globalValue; | ||
| } | ||
| try { | ||
| if (typeof process !== 'undefined' && process.env && typeof process.env === 'object') { | ||
| const result = getBooleanEnvFromEnvObject('MUI_X_TELEMETRY_DISABLED', process.env); | ||
| if (typeof result === 'boolean') { | ||
| // If disabled=true, telemetry is disabled | ||
| // If disabled=false, telemetry is enabled | ||
| return !result; | ||
| } | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| try { | ||
| // Some build tools replace env variables on compilation | ||
| // e.g. Next.js, webpack EnvironmentPlugin | ||
| const envValue = process.env.MUI_X_TELEMETRY_DISABLED || process.env.NEXT_PUBLIC_MUI_X_TELEMETRY_DISABLED || process.env.GATSBY_MUI_X_TELEMETRY_DISABLED || process.env.REACT_APP_MUI_X_TELEMETRY_DISABLED || process.env.PUBLIC_MUI_X_TELEMETRY_DISABLED; | ||
| const result = getBooleanEnv(envValue); | ||
| if (typeof result === 'boolean') { | ||
| // If disabled=true, telemetry is disabled | ||
| // If disabled=false, telemetry is enabled | ||
| return !result; | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| return undefined; | ||
| } | ||
| function getIsDebugModeEnabled() { | ||
| try { | ||
| // Check global variable | ||
| // eslint-disable-next-line no-underscore-dangle | ||
| const globalValue = globalThis.__MUI_X_TELEMETRY_DEBUG__; | ||
| if (typeof globalValue === 'boolean') { | ||
| return globalValue; | ||
| } | ||
| if (typeof process !== 'undefined' && process.env && typeof process.env === 'object') { | ||
| const result = getBooleanEnvFromEnvObject('MUI_X_TELEMETRY_DEBUG', process.env); | ||
| if (typeof result === 'boolean') { | ||
| return result; | ||
| } | ||
| } | ||
| // e.g. Webpack EnvironmentPlugin | ||
| if (process.env.MUI_X_TELEMETRY_DEBUG) { | ||
| const result = getBooleanEnv(process.env.MUI_X_TELEMETRY_DEBUG); | ||
| if (typeof result === 'boolean') { | ||
| return result; | ||
| } | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| try { | ||
| // e.g. Next.js, webpack EnvironmentPlugin | ||
| const envValue = process.env.MUI_X_TELEMETRY_DEBUG || process.env.NEXT_PUBLIC_MUI_X_TELEMETRY_DEBUG || process.env.GATSBY_MUI_X_TELEMETRY_DEBUG || process.env.REACT_APP_MUI_X_TELEMETRY_DEBUG || process.env.PUBLIC_MUI_X_TELEMETRY_DEBUG; | ||
| const result = getBooleanEnv(envValue); | ||
| if (typeof result === 'boolean') { | ||
| return result; | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| return false; | ||
| } | ||
| function getNodeEnv() { | ||
| try { | ||
| return process.env.NODE_ENV ?? '<unknown>'; | ||
| } catch (_) { | ||
| return '<unknown>'; | ||
| } | ||
| } | ||
| let cachedEnv = null; | ||
| export function getTelemetryEnvConfig(skipCache = false) { | ||
| if (skipCache || !cachedEnv) { | ||
| cachedEnv = { | ||
| NODE_ENV: getNodeEnv(), | ||
| IS_COLLECTING: getIsTelemetryCollecting(), | ||
| DEBUG: getIsDebugModeEnabled() | ||
| }; | ||
| } | ||
| return cachedEnv; | ||
| } | ||
| export function getTelemetryEnvConfigValue(key) { | ||
| return getTelemetryEnvConfig()[key]; | ||
| } | ||
| export function setTelemetryEnvConfigValue(key, value) { | ||
| getTelemetryEnvConfig()[key] = value; | ||
| } |
| import { TelemetryEventContext } from "../types.mjs"; | ||
| declare const muiXTelemetryEvents: { | ||
| licenseVerification: (() => null) | ((context: TelemetryEventContext, payload: { | ||
| packageReleaseInfo: string; | ||
| packageName: string; | ||
| licenseStatus?: string; | ||
| }) => { | ||
| eventName: string; | ||
| payload: { | ||
| packageReleaseInfo: string; | ||
| packageName: string; | ||
| licenseStatus?: string; | ||
| }; | ||
| context: TelemetryEventContext; | ||
| }); | ||
| }; | ||
| export default muiXTelemetryEvents; |
| const noop = () => null; | ||
| const muiXTelemetryEvents = { | ||
| licenseVerification: process.env.NODE_ENV === 'production' ? noop : (context, payload) => ({ | ||
| eventName: 'licenseVerification', | ||
| payload, | ||
| context | ||
| }) | ||
| }; | ||
| export default muiXTelemetryEvents; |
| declare function fetchWithRetry(url: string, options: RequestInit, retries?: number): Promise<Response>; | ||
| export { fetchWithRetry }; |
| async function fetchWithRetry(url, options, retries = 3) { | ||
| try { | ||
| const response = await fetch(url, options); | ||
| if (response.ok) { | ||
| return response; | ||
| } | ||
| throw /* FIXME (minify-errors-in-prod): Unminified error message in production build! */new Error(`MUI X: Request failed with status ${response.status}`); | ||
| } catch (error) { | ||
| if (retries === 0) { | ||
| throw error; | ||
| } | ||
| return new Promise(resolve => { | ||
| setTimeout(() => { | ||
| resolve(fetchWithRetry(url, options, retries - 1)); | ||
| }, Math.random() * 3_000); | ||
| }); | ||
| } | ||
| } | ||
| export { fetchWithRetry }; |
| import type { TelemetryContextType } from "../context.mjs"; | ||
| declare function getTelemetryContext(): Promise<TelemetryContextType>; | ||
| export { TelemetryContextType }; | ||
| export default getTelemetryContext; |
| import _extends from "@babel/runtime/helpers/esm/extends"; | ||
| import telemetryContext from "../context.mjs"; | ||
| import { getWindowStorageItem, setWindowStorageItem } from "./window-storage.mjs"; | ||
| function generateId(length) { | ||
| let result = ''; | ||
| const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
| const charactersLength = characters.length; | ||
| let counter = 0; | ||
| while (counter < length) { | ||
| result += characters.charAt(Math.floor(Math.random() * charactersLength)); | ||
| counter += 1; | ||
| } | ||
| return result; | ||
| } | ||
| function pick(obj, keys) { | ||
| return keys.reduce((acc, key) => { | ||
| acc[key] = obj[key]; | ||
| return acc; | ||
| }, {}); | ||
| } | ||
| const getBrowserFingerprint = typeof window === 'undefined' || process.env.NODE_ENV === 'test' ? () => undefined : async () => { | ||
| const fingerprintLCKey = 'fingerprint'; | ||
| try { | ||
| const existingFingerprint = getWindowStorageItem('localStorage', fingerprintLCKey); | ||
| if (existingFingerprint) { | ||
| return JSON.parse(existingFingerprint); | ||
| } | ||
| const FingerprintJS = await import('@fingerprintjs/fingerprintjs'); | ||
| const fp = await FingerprintJS.load({ | ||
| monitoring: false | ||
| }); | ||
| const fpResult = await fp.get(); | ||
| const components = _extends({}, fpResult.components); | ||
| delete components.cookiesEnabled; | ||
| const fullHash = FingerprintJS.hashComponents(components); | ||
| const coreHash = FingerprintJS.hashComponents(_extends({}, pick(components, ['fonts', 'audio', 'languages', 'deviceMemory', 'timezone', 'sessionStorage', 'localStorage', 'indexedDB', 'openDatabase', 'platform', 'canvas', 'vendor', 'vendorFlavors', 'colorGamut', 'forcedColors', 'monochrome', 'contrast', 'reducedMotion', 'math', 'videoCard', 'architecture']))); | ||
| const result = { | ||
| fullHash, | ||
| coreHash | ||
| }; | ||
| setWindowStorageItem('localStorage', fingerprintLCKey, JSON.stringify(result)); | ||
| return result; | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| }; | ||
| function getAnonymousId() { | ||
| const localStorageKey = 'anonymous_id'; | ||
| const existingAnonymousId = getWindowStorageItem('localStorage', localStorageKey); | ||
| if (existingAnonymousId) { | ||
| return existingAnonymousId; | ||
| } | ||
| const generated = `anid_${generateId(32)}`; | ||
| if (setWindowStorageItem('localStorage', localStorageKey, generated)) { | ||
| return generated; | ||
| } | ||
| return ''; | ||
| } | ||
| function getSessionId() { | ||
| const localStorageKey = 'session_id'; | ||
| const existingSessionId = getWindowStorageItem('sessionStorage', localStorageKey); | ||
| if (existingSessionId) { | ||
| return existingSessionId; | ||
| } | ||
| const generated = `sesid_${generateId(32)}`; | ||
| if (setWindowStorageItem('sessionStorage', localStorageKey, generated)) { | ||
| return generated; | ||
| } | ||
| return `sestp_${generateId(32)}`; | ||
| } | ||
| async function getTelemetryContext() { | ||
| telemetryContext.traits.sessionId = getSessionId(); | ||
| // Initialize the context if it hasn't been initialized yet | ||
| // (e.g. postinstall not run) | ||
| if (!telemetryContext.config.isInitialized) { | ||
| telemetryContext.traits.anonymousId = getAnonymousId(); | ||
| telemetryContext.config.isInitialized = true; | ||
| } | ||
| if (!telemetryContext.traits.fingerprint) { | ||
| telemetryContext.traits.fingerprint = await getBrowserFingerprint(); | ||
| } | ||
| return telemetryContext; | ||
| } | ||
| export default getTelemetryContext; |
| import { TelemetryEvent } from "../types.mjs"; | ||
| declare function sendMuiXTelemetryEvent(event: TelemetryEvent | null): Promise<void>; | ||
| export default sendMuiXTelemetryEvent; |
| import _extends from "@babel/runtime/helpers/esm/extends"; | ||
| import { getTelemetryEnvConfigValue } from "./config.mjs"; | ||
| import { fetchWithRetry } from "./fetcher.mjs"; | ||
| const sendMuiXTelemetryRetries = 3; | ||
| function shouldSendTelemetry(telemetryContext) { | ||
| // Disable reporting in SSR / Node.js | ||
| if (typeof window === 'undefined') { | ||
| return false; | ||
| } | ||
| // Priority to the config (e.g. in code, env) | ||
| const envIsCollecting = getTelemetryEnvConfigValue('IS_COLLECTING'); | ||
| if (typeof envIsCollecting === 'boolean') { | ||
| return envIsCollecting; | ||
| } | ||
| // Disable collection of the telemetry in CI builds, | ||
| // as it not related to development process | ||
| if (telemetryContext.traits.isCI) { | ||
| return false; | ||
| } | ||
| // Enabled by default | ||
| return true; | ||
| } | ||
| async function sendMuiXTelemetryEvent(event) { | ||
| try { | ||
| // Disable collection of the telemetry | ||
| // in production environment | ||
| if (process.env.NODE_ENV === 'production') { | ||
| return; | ||
| } | ||
| const { | ||
| default: getTelemetryContext | ||
| } = await import("./get-context.mjs"); | ||
| const telemetryContext = await getTelemetryContext(); | ||
| if (!event || !shouldSendTelemetry(telemetryContext)) { | ||
| return; | ||
| } | ||
| const eventPayload = _extends({}, event, { | ||
| context: _extends({}, telemetryContext.traits, event.context) | ||
| }); | ||
| if (getTelemetryEnvConfigValue('DEBUG')) { | ||
| console.log('[mui-x-telemetry] event', JSON.stringify(eventPayload, null, 2)); | ||
| return; | ||
| } | ||
| // TODO: batch events and send them in a single request when there will be more | ||
| await fetchWithRetry('https://x-telemetry.mui.com/v2/telemetry/record', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'X-Telemetry-Client-Version': "9.0.0-alpha.2" ?? '<dev>', | ||
| 'X-Telemetry-Node-Env': process.env.NODE_ENV ?? '<unknown>' | ||
| }, | ||
| body: JSON.stringify([eventPayload]) | ||
| }, sendMuiXTelemetryRetries); | ||
| } catch (_) { | ||
| console.log('[mui-x-telemetry] error', _); | ||
| } | ||
| } | ||
| export default sendMuiXTelemetryEvent; |
| declare const muiXTelemetrySettings: { | ||
| enableDebug: () => void; | ||
| enableTelemetry: () => void; | ||
| disableTelemetry: () => void; | ||
| }; | ||
| export default muiXTelemetrySettings; |
| import { setTelemetryEnvConfigValue } from "./config.mjs"; | ||
| const muiXTelemetrySettings = { | ||
| enableDebug: () => { | ||
| setTelemetryEnvConfigValue('DEBUG', true); | ||
| }, | ||
| enableTelemetry: () => { | ||
| setTelemetryEnvConfigValue('IS_COLLECTING', true); | ||
| }, | ||
| disableTelemetry: () => { | ||
| setTelemetryEnvConfigValue('IS_COLLECTING', false); | ||
| } | ||
| }; | ||
| export default muiXTelemetrySettings; |
| type WindowStorageType = 'localStorage' | 'sessionStorage'; | ||
| export declare function setWindowStorageItem(type: WindowStorageType, key: string, value: string): boolean; | ||
| export declare function getWindowStorageItem(type: WindowStorageType, key: string): string | null; | ||
| export {}; |
| const prefix = '__mui_x_telemetry_'; | ||
| function getStorageKey(key) { | ||
| return prefix + btoa(key); | ||
| } | ||
| export function setWindowStorageItem(type, key, value) { | ||
| try { | ||
| if (typeof window !== 'undefined' && window[type]) { | ||
| window[type].setItem(getStorageKey(key), value); | ||
| return true; | ||
| } | ||
| } catch (_) { | ||
| // Storage is unavailable, skip it | ||
| } | ||
| return false; | ||
| } | ||
| export function getWindowStorageItem(type, key) { | ||
| try { | ||
| if (typeof window !== 'undefined' && window[type]) { | ||
| return window[type].getItem(getStorageKey(key)); | ||
| } | ||
| } catch (_) { | ||
| // Storage is unavailable, skip it | ||
| } | ||
| return null; | ||
| } |
| export {}; | ||
| declare global { | ||
| interface MUIEnv { | ||
| MUI_VERSION?: string; | ||
| MUI_X_TELEMETRY_DISABLED?: string; | ||
| NEXT_PUBLIC_MUI_X_TELEMETRY_DISABLED?: string; | ||
| GATSBY_MUI_X_TELEMETRY_DISABLED?: string; | ||
| REACT_APP_MUI_X_TELEMETRY_DISABLED?: string; | ||
| PUBLIC_MUI_X_TELEMETRY_DISABLED?: string; | ||
| MUI_X_TELEMETRY_DEBUG?: string; | ||
| NEXT_PUBLIC_MUI_X_TELEMETRY_DEBUG?: string; | ||
| GATSBY_MUI_X_TELEMETRY_DEBUG?: string; | ||
| REACT_APP_MUI_X_TELEMETRY_DEBUG?: string; | ||
| PUBLIC_MUI_X_TELEMETRY_DEBUG?: string; | ||
| } | ||
| } |
| export {}; | ||
| declare global { | ||
| interface MUIEnv { | ||
| MUI_VERSION?: string; | ||
| MUI_X_TELEMETRY_DISABLED?: string; | ||
| NEXT_PUBLIC_MUI_X_TELEMETRY_DISABLED?: string; | ||
| GATSBY_MUI_X_TELEMETRY_DISABLED?: string; | ||
| REACT_APP_MUI_X_TELEMETRY_DISABLED?: string; | ||
| PUBLIC_MUI_X_TELEMETRY_DISABLED?: string; | ||
| MUI_X_TELEMETRY_DEBUG?: string; | ||
| NEXT_PUBLIC_MUI_X_TELEMETRY_DEBUG?: string; | ||
| GATSBY_MUI_X_TELEMETRY_DEBUG?: string; | ||
| REACT_APP_MUI_X_TELEMETRY_DEBUG?: string; | ||
| PUBLIC_MUI_X_TELEMETRY_DEBUG?: string; | ||
| } | ||
| } |
| export interface TelemetryEventContext { | ||
| licenseKey?: string; | ||
| xLicenseClientVersion?: string; | ||
| } | ||
| export interface TelemetryEvent { | ||
| eventName: string; | ||
| payload: Record<string, any>; | ||
| context: TelemetryEventContext; | ||
| } |
| export {}; |
+1
-1
| /** | ||
| * @mui/x-telemetry v9.0.0-alpha.0 | ||
| * @mui/x-telemetry v9.0.0-alpha.2 | ||
| * | ||
@@ -4,0 +4,0 @@ * @license SEE LICENSE IN LICENSE |
+20
-14
| { | ||
| "name": "@mui/x-telemetry", | ||
| "version": "9.0.0-alpha.0", | ||
| "version": "9.0.0-alpha.2", | ||
| "author": "MUI Team", | ||
@@ -24,3 +24,2 @@ "description": "MUI X Telemetry.", | ||
| "ci-info": "^4.3.1", | ||
| "conf": "^11.0.2", | ||
| "is-docker": "^4.0.0", | ||
@@ -33,7 +32,9 @@ "node-machine-id": "^1.1.12" | ||
| "type": "commonjs", | ||
| "main": "./index.js", | ||
| "types": "./index.d.ts", | ||
| "exports": { | ||
| "./package.json": "./package.json", | ||
| ".": { | ||
| "import": { | ||
| "types": "./index.d.mts", | ||
| "default": "./index.mjs" | ||
| }, | ||
| "require": { | ||
@@ -44,21 +45,26 @@ "types": "./index.d.ts", | ||
| "default": { | ||
| "types": "./esm/index.d.ts", | ||
| "default": "./esm/index.js" | ||
| "types": "./index.d.mts", | ||
| "default": "./index.mjs" | ||
| } | ||
| }, | ||
| "./*": { | ||
| "./postinstall": { | ||
| "import": { | ||
| "types": "./postinstall/index.d.mts", | ||
| "default": "./postinstall/index.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./*/index.d.ts", | ||
| "default": "./*/index.js" | ||
| "types": "./postinstall/index.d.ts", | ||
| "default": "./postinstall/index.js" | ||
| }, | ||
| "default": { | ||
| "types": "./esm/*/index.d.ts", | ||
| "default": "./esm/*/index.js" | ||
| "types": "./postinstall/index.d.mts", | ||
| "default": "./postinstall/index.mjs" | ||
| } | ||
| }, | ||
| "./esm": null | ||
| } | ||
| }, | ||
| "main": "./index.js", | ||
| "types": "./index.d.ts", | ||
| "scripts": { | ||
| "postinstall": "node ./esm/postinstall/index.js" | ||
| "postinstall": "node ./postinstall/index.mjs" | ||
| } | ||
| } |
@@ -8,3 +8,3 @@ "use strict"; | ||
| function notifyAboutMuiXTelemetry() { | ||
| console.log(`[Attention]: MUI X now may collect completely anonymous telemetry regarding usage.`); | ||
| console.log(`[Attention]: MUI X now collects completely anonymous telemetry regarding usage.`); | ||
| console.log(`This information is used to shape MUI's roadmap and prioritize features.`); | ||
@@ -11,0 +11,0 @@ console.log(`You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:`); |
+61
-32
@@ -8,4 +8,5 @@ "use strict"; | ||
| exports.TelemetryStorage = void 0; | ||
| var _interopRequireWildcard2 = _interopRequireDefault(require("@babel/runtime/helpers/interopRequireWildcard")); | ||
| var _crypto = require("crypto"); | ||
| var _fs = _interopRequireDefault(require("fs")); | ||
| var _os = _interopRequireDefault(require("os")); | ||
| var _path = _interopRequireDefault(require("path")); | ||
@@ -20,10 +21,39 @@ var _notify = _interopRequireDefault(require("./notify")); | ||
| const TELEMETRY_KEY_ID = `telemetry.anonymousId`; | ||
| function getStorageDirectory(distDir) { | ||
| const CONFIG_FILE_NAME = 'config.json'; | ||
| const PROJECT_NAME = 'mui-x'; | ||
| function getConfigDirectory(distDir) { | ||
| const env = (0, _getEnvironmentInfo.default)(); | ||
| const isLikelyEphemeral = env.isCI || env.isDocker; | ||
| if (isLikelyEphemeral) { | ||
| return _path.default.join(distDir, 'cache'); | ||
| return _path.default.join(distDir, 'cache', PROJECT_NAME); | ||
| } | ||
| return undefined; | ||
| const { | ||
| platform | ||
| } = process; | ||
| const homedir = _os.default.homedir(); | ||
| if (platform === 'darwin') { | ||
| return _path.default.join(homedir, 'Library', 'Preferences', PROJECT_NAME); | ||
| } | ||
| if (platform === 'win32') { | ||
| const appData = process.env.APPDATA || _path.default.join(homedir, 'AppData', 'Roaming'); | ||
| return _path.default.join(appData, PROJECT_NAME, 'Config'); | ||
| } | ||
| // Linux / others: follow XDG Base Directory specification | ||
| const xdgConfig = process.env.XDG_CONFIG_HOME || _path.default.join(homedir, '.config'); | ||
| return _path.default.join(xdgConfig, PROJECT_NAME); | ||
| } | ||
| function readConfigFile(configPath) { | ||
| try { | ||
| return JSON.parse(_fs.default.readFileSync(configPath, 'utf-8')); | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
| function writeConfigFile(configPath, data) { | ||
| const dir = _path.default.dirname(configPath); | ||
| _fs.default.mkdirSync(dir, { | ||
| recursive: true | ||
| }); | ||
| _fs.default.writeFileSync(configPath, JSON.stringify(data, null, '\t')); | ||
| } | ||
| class TelemetryStorage { | ||
@@ -33,26 +63,21 @@ static async init({ | ||
| }) { | ||
| const storageDirectory = getStorageDirectory(distDir); | ||
| let conf = null; | ||
| const configDirectory = getConfigDirectory(distDir); | ||
| let configFilePath = null; | ||
| try { | ||
| // `conf` incorrectly throws a permission error during initialization | ||
| // instead of waiting for first use. We need to handle it, otherwise the | ||
| // process may crash. | ||
| const { | ||
| default: Conf | ||
| } = await Promise.resolve().then(() => (0, _interopRequireWildcard2.default)(require('conf'))); | ||
| conf = new Conf({ | ||
| projectName: 'mui-x', | ||
| cwd: storageDirectory | ||
| configFilePath = _path.default.join(configDirectory, CONFIG_FILE_NAME); | ||
| // Verify write access by ensuring the directory exists | ||
| _fs.default.mkdirSync(configDirectory, { | ||
| recursive: true | ||
| }); | ||
| } catch (_) { | ||
| conf = null; | ||
| } catch { | ||
| configFilePath = null; | ||
| } | ||
| return new TelemetryStorage(conf); | ||
| return new TelemetryStorage(configFilePath); | ||
| } | ||
| constructor(conf) { | ||
| this.conf = conf; | ||
| constructor(filePath) { | ||
| this.configPath = filePath; | ||
| this.notify(); | ||
| } | ||
| notify = () => { | ||
| if (!this.conf) { | ||
| if (!this.configPath) { | ||
| return; | ||
@@ -65,21 +90,25 @@ } | ||
| // ever made. | ||
| if (this.conf.get(TELEMETRY_KEY_NOTIFY_DATE, '')) { | ||
| const data = readConfigFile(this.configPath); | ||
| if (data[TELEMETRY_KEY_NOTIFY_DATE]) { | ||
| return; | ||
| } | ||
| this.conf.set(TELEMETRY_KEY_NOTIFY_DATE, Date.now().toString()); | ||
| data[TELEMETRY_KEY_NOTIFY_DATE] = Date.now().toString(); | ||
| writeConfigFile(this.configPath, data); | ||
| (0, _notify.default)(); | ||
| }; | ||
| get configPath() { | ||
| return this.conf?.path; | ||
| } | ||
| get anonymousId() { | ||
| const val = this.conf && this.conf.get(TELEMETRY_KEY_ID); | ||
| if (val) { | ||
| return val; | ||
| if (this.configPath) { | ||
| const data = readConfigFile(this.configPath); | ||
| const existing = data[TELEMETRY_KEY_ID]; | ||
| if (typeof existing === 'string') { | ||
| return existing; | ||
| } | ||
| const generated = (0, _crypto.randomBytes)(32).toString('hex'); | ||
| data[TELEMETRY_KEY_ID] = generated; | ||
| writeConfigFile(this.configPath, data); | ||
| return generated; | ||
| } | ||
| const generated = (0, _crypto.randomBytes)(32).toString('hex'); | ||
| this.conf?.set(TELEMETRY_KEY_ID, generated); | ||
| return generated; | ||
| return (0, _crypto.randomBytes)(32).toString('hex'); | ||
| } | ||
| } | ||
| exports.TelemetryStorage = TelemetryStorage; |
+15
-27
| # @mui/x-telemetry | ||
| Package used by some of MUI X to collects **anonymous** telemetry data about general usage. Participation in this anonymous program is optional, and you may opt-out if you'd not like to share any information. | ||
| Package used by some of MUI X to collect **anonymous** telemetry data about general usage during development. Telemetry is **enabled by default** in development mode and is completely removed in production builds. Participation in this anonymous program is optional, and you may opt out if you'd not like to share any information. | ||
| ## How to opt-in | ||
| ## How to configure telemetry | ||
| Currently, **it's disabled by default,** and you could opt-in to it in 3 ways: | ||
| Telemetry is **enabled by default** in development mode. You can enable or disable it in any of the following ways: | ||
| 1. By setting it directly to package settings on the application start (for example, in the main file). | ||
| 1. By setting the environment variable. | ||
| ```dotenv | ||
| MUI_X_TELEMETRY_DISABLED=true # Disable telemetry | ||
| # or | ||
| MUI_X_TELEMETRY_DISABLED=false # Enable telemetry | ||
| ``` | ||
| > ⚠️ Note that some frameworks require prefixing the variable with `REACT_APP_`, `NEXT_PUBLIC_`, etc. | ||
| 2. By using the package settings on application start (for example, in the main file). | ||
| ```js | ||
@@ -21,14 +31,4 @@ import { muiXTelemetrySettings } from '@mui/x-telemetry'; | ||
| 2. By setting the environment variable. | ||
| 3. By setting the flag on the global object on application start (for example, in the main file). | ||
| ```dotenv | ||
| MUI_X_TELEMETRY_DISABLED=false # Enable telemetry | ||
| # or | ||
| MUI_X_TELEMETRY_DISABLED=true # Enable telemetry | ||
| ``` | ||
| > ⚠️ Note that some frameworks requires to prefix the variable with `REACT_APP_`, `NEXT_PUBLIC_`, etc. | ||
| 3. By setting the flag to global object on the application start (for example, in the main file). | ||
| ```js | ||
@@ -39,13 +39,1 @@ globalThis.__MUI_X_TELEMETRY_DISABLED__ = false; // enabled | ||
| ``` | ||
| OR | ||
| ```js | ||
| if (typeof window !== 'undefined') { | ||
| window.__MUI_X_TELEMETRY_DISABLED__ = false; // enabled | ||
| } | ||
| // or | ||
| if (typeof window !== 'undefined') { | ||
| window.__MUI_X_TELEMETRY_DISABLED__ = true; // disabled | ||
| } | ||
| ``` |
@@ -13,3 +13,3 @@ "use strict"; | ||
| } | ||
| throw new Error(`Request failed with status ${response.status}`); | ||
| throw /* FIXME (minify-errors-in-prod): Unminified error message in production build! */new Error(`MUI X: Request failed with status ${response.status}`); | ||
| } catch (error) { | ||
@@ -16,0 +16,0 @@ if (retries === 0) { |
@@ -31,4 +31,4 @@ "use strict"; | ||
| // Disabled by default | ||
| return false; | ||
| // Enabled by default | ||
| return true; | ||
| } | ||
@@ -62,3 +62,3 @@ async function sendMuiXTelemetryEvent(event) { | ||
| 'Content-Type': 'application/json', | ||
| 'X-Telemetry-Client-Version': "9.0.0-alpha.0" ?? '<dev>', | ||
| 'X-Telemetry-Client-Version': "9.0.0-alpha.2" ?? '<dev>', | ||
| 'X-Telemetry-Node-Env': process.env.NODE_ENV ?? '<unknown>' | ||
@@ -65,0 +65,0 @@ }, |
+1
-0
| export interface TelemetryEventContext { | ||
| licenseKey?: string; | ||
| xLicenseClientVersion?: string; | ||
| } | ||
@@ -4,0 +5,0 @@ export interface TelemetryEvent { |
| export interface TelemetryContextType { | ||
| config: { | ||
| isInitialized: boolean; | ||
| }; | ||
| traits: Record<string, any> & { | ||
| machineId?: string | null; | ||
| fingerprint?: { | ||
| fullHash?: string | null; | ||
| coreHash?: string | null; | ||
| components?: Record<string, any> | null; | ||
| } | null; | ||
| projectId?: string | null; | ||
| anonymousId?: string | null; | ||
| sessionId?: string | null; | ||
| isDocker?: boolean; | ||
| isCI?: boolean; | ||
| }; | ||
| } | ||
| declare const defaultValue: TelemetryContextType; | ||
| export default defaultValue; |
| // This file will be modified by the `postinstall` script. | ||
| // See postinstall/index.ts for more information. | ||
| const defaultValue = { | ||
| config: { | ||
| isInitialized: false | ||
| }, | ||
| traits: {} | ||
| }; | ||
| export default defaultValue; |
| import muiXTelemetryEvents from "./runtime/events.js"; | ||
| import sendMuiXTelemetryEventOriginal from "./runtime/sender.js"; | ||
| declare const sendMuiXTelemetryEvent: typeof sendMuiXTelemetryEventOriginal | (() => void); | ||
| declare const muiXTelemetrySettings: { | ||
| enableDebug: () => void; | ||
| enableTelemetry: () => void; | ||
| disableTelemetry: () => void; | ||
| }; | ||
| export { muiXTelemetryEvents, sendMuiXTelemetryEvent, muiXTelemetrySettings }; |
-22
| /** | ||
| * @mui/x-telemetry v9.0.0-alpha.0 | ||
| * | ||
| * @license SEE LICENSE IN LICENSE | ||
| * This source code is licensed under the SEE LICENSE IN LICENSE license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
| import muiXTelemetryEvents from "./runtime/events.js"; | ||
| import sendMuiXTelemetryEventOriginal from "./runtime/sender.js"; | ||
| import muiXTelemetrySettingsOriginal from "./runtime/settings.js"; | ||
| const noop = () => {}; | ||
| // To cut unused imports in production as early as possible | ||
| const sendMuiXTelemetryEvent = process.env.NODE_ENV === 'production' ? noop : sendMuiXTelemetryEventOriginal; | ||
| // To cut unused imports in production as early as possible | ||
| const muiXTelemetrySettings = process.env.NODE_ENV === 'production' ? { | ||
| enableDebug: noop, | ||
| enableTelemetry: noop, | ||
| disableTelemetry: noop | ||
| } : muiXTelemetrySettingsOriginal; | ||
| export { muiXTelemetryEvents, sendMuiXTelemetryEvent, muiXTelemetrySettings }; |
| {"type":"module","sideEffects":false} |
| interface EnvironmentInfo { | ||
| isDocker: boolean; | ||
| isCI: boolean; | ||
| } | ||
| export default function getEnvironmentInfo(): EnvironmentInfo; | ||
| export {}; |
| import isDockerFunction from 'is-docker'; | ||
| import ciEnvironment from 'ci-info'; | ||
| let traits; | ||
| export default function getEnvironmentInfo() { | ||
| if (!traits) { | ||
| traits = { | ||
| isDocker: isDockerFunction(), | ||
| isCI: ciEnvironment.isCI | ||
| }; | ||
| } | ||
| return traits; | ||
| } |
| export default function getAnonymousMachineId(): Promise<string | null>; |
| import { createHash } from 'crypto'; | ||
| export default async function getAnonymousMachineId() { | ||
| try { | ||
| const nodeMachineId = await import('node-machine-id'); | ||
| const rawMachineId = await nodeMachineId.machineId(true); | ||
| if (!rawMachineId) { | ||
| return null; | ||
| } | ||
| return createHash('sha256').update(rawMachineId).digest('hex'); | ||
| } catch (_) { | ||
| // Ignore any errors | ||
| return null; | ||
| } | ||
| } |
| export default function getAnonymousProjectId(): Promise<string>; |
| import { exec } from 'child_process'; | ||
| import { createHash } from 'crypto'; | ||
| import util from 'util'; | ||
| const asyncExec = util.promisify(exec); | ||
| async function execCLI(command) { | ||
| try { | ||
| const response = await asyncExec(command, { | ||
| timeout: 1000, | ||
| windowsHide: true | ||
| }); | ||
| return String(response).trim(); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| // Q: Why does MUI need a project ID? Why is it looking at my git remote? | ||
| // A: | ||
| // MUI's telemetry always anonymizes these values. We need a way to | ||
| // differentiate different projects to track feature usage accurately. | ||
| // For example, to prevent a feature from appearing to be constantly `used` | ||
| // and then `unused` when switching between local projects. | ||
| async function getRawProjectId() { | ||
| return (await execCLI(`git config --local --get remote.origin.url`)) || process.env.REPOSITORY_URL || (await execCLI(`git rev-parse --show-toplevel`)) || process.cwd(); | ||
| } | ||
| export default async function getAnonymousProjectId() { | ||
| const rawProjectId = await getRawProjectId(); | ||
| return createHash('sha256').update(rawProjectId).digest('hex'); | ||
| } |
| export {}; |
| import _extends from "@babel/runtime/helpers/esm/extends"; | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import { randomBytes } from 'crypto'; | ||
| import { fileURLToPath } from 'url'; | ||
| import getEnvironmentInfo from "./get-environment-info.js"; | ||
| import getAnonymousProjectId from "./get-project-id.js"; | ||
| import getAnonymousMachineId from "./get-machine-id.js"; | ||
| import { TelemetryStorage } from "./storage.js"; | ||
| const dirname = typeof __dirname === 'string' ? __dirname // cjs build in root dir | ||
| : (() => { | ||
| const filename = fileURLToPath(import.meta.url); | ||
| // esm build in `esm` directory, so we need to go up two levels | ||
| return path.dirname(path.dirname(filename)); | ||
| })(); | ||
| (async () => { | ||
| // If Node.js support permissions, we need to check if the current user has | ||
| // the necessary permissions to write to the file system. | ||
| if (typeof process.permission !== 'undefined' && !(process.permission.has('fs.read') && process.permission.has('fs.write'))) { | ||
| return; | ||
| } | ||
| const storage = await TelemetryStorage.init({ | ||
| distDir: process.cwd() | ||
| }); | ||
| const [environmentInfo, projectId, machineId] = await Promise.all([getEnvironmentInfo(), getAnonymousProjectId(), getAnonymousMachineId()]); | ||
| const contextData = { | ||
| config: { | ||
| isInitialized: true | ||
| }, | ||
| traits: _extends({}, environmentInfo, { | ||
| machineId, | ||
| projectId, | ||
| sessionId: randomBytes(32).toString('hex'), | ||
| anonymousId: storage.anonymousId | ||
| }) | ||
| }; | ||
| const writeContextData = (filePath, format) => { | ||
| const targetPath = path.resolve(dirname, '..', filePath, 'context.js'); | ||
| fs.writeFileSync(targetPath, format(JSON.stringify(contextData, null, 2))); | ||
| }; | ||
| writeContextData('esm', content => `export default ${content};`); | ||
| writeContextData('', content => [`"use strict";`, `Object.defineProperty(exports, "__esModule", { value: true });`, `exports.default = void 0;`, `var _default = exports.default = ${content};`].join('\n')); | ||
| })().catch(error => { | ||
| console.error('[telemetry] Failed to make initialization. Please, report error to MUI X team:\n' + 'https://mui.com/r/x-telemetry-postinstall-troubleshoot\n', error); | ||
| }); |
| export default function notifyAboutMuiXTelemetry(): void; |
| export default function notifyAboutMuiXTelemetry() { | ||
| console.log(`[Attention]: MUI X now may collect completely anonymous telemetry regarding usage.`); | ||
| console.log(`This information is used to shape MUI's roadmap and prioritize features.`); | ||
| console.log(`You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:`); | ||
| console.log('https://mui.com/x/guides/telemetry'); | ||
| console.log(); | ||
| } |
| export declare class TelemetryStorage { | ||
| private readonly conf; | ||
| static init({ | ||
| distDir | ||
| }: { | ||
| distDir: string; | ||
| }): Promise<TelemetryStorage>; | ||
| private constructor(); | ||
| private notify; | ||
| get configPath(): string | undefined; | ||
| get anonymousId(): string; | ||
| } |
| import { randomBytes } from 'crypto'; | ||
| import path from 'path'; | ||
| import notifyAboutMuiXTelemetry from "./notify.js"; | ||
| import getEnvironmentInfo from "./get-environment-info.js"; | ||
| // This is the key that specifies when the user was informed about telemetry collection. | ||
| const TELEMETRY_KEY_NOTIFY_DATE = 'telemetry.notifiedAt'; | ||
| // This is a quasi-persistent identifier used to dedupe recurring events. It's | ||
| // generated from random data and completely anonymous. | ||
| const TELEMETRY_KEY_ID = `telemetry.anonymousId`; | ||
| function getStorageDirectory(distDir) { | ||
| const env = getEnvironmentInfo(); | ||
| const isLikelyEphemeral = env.isCI || env.isDocker; | ||
| if (isLikelyEphemeral) { | ||
| return path.join(distDir, 'cache'); | ||
| } | ||
| return undefined; | ||
| } | ||
| export class TelemetryStorage { | ||
| static async init({ | ||
| distDir | ||
| }) { | ||
| const storageDirectory = getStorageDirectory(distDir); | ||
| let conf = null; | ||
| try { | ||
| // `conf` incorrectly throws a permission error during initialization | ||
| // instead of waiting for first use. We need to handle it, otherwise the | ||
| // process may crash. | ||
| const { | ||
| default: Conf | ||
| } = await import('conf'); | ||
| conf = new Conf({ | ||
| projectName: 'mui-x', | ||
| cwd: storageDirectory | ||
| }); | ||
| } catch (_) { | ||
| conf = null; | ||
| } | ||
| return new TelemetryStorage(conf); | ||
| } | ||
| constructor(conf) { | ||
| this.conf = conf; | ||
| this.notify(); | ||
| } | ||
| notify = () => { | ||
| if (!this.conf) { | ||
| return; | ||
| } | ||
| // The end-user has already been notified about our telemetry integration. We | ||
| // don't need to constantly annoy them about it. | ||
| // We will re-inform users about the telemetry if significant changes are | ||
| // ever made. | ||
| if (this.conf.get(TELEMETRY_KEY_NOTIFY_DATE, '')) { | ||
| return; | ||
| } | ||
| this.conf.set(TELEMETRY_KEY_NOTIFY_DATE, Date.now().toString()); | ||
| notifyAboutMuiXTelemetry(); | ||
| }; | ||
| get configPath() { | ||
| return this.conf?.path; | ||
| } | ||
| get anonymousId() { | ||
| const val = this.conf && this.conf.get(TELEMETRY_KEY_ID); | ||
| if (val) { | ||
| return val; | ||
| } | ||
| const generated = randomBytes(32).toString('hex'); | ||
| this.conf?.set(TELEMETRY_KEY_ID, generated); | ||
| return generated; | ||
| } | ||
| } |
| interface TelemetryEnvConfig { | ||
| NODE_ENV: string | '<unknown>'; | ||
| IS_COLLECTING: boolean | undefined; | ||
| DEBUG: boolean; | ||
| } | ||
| declare global { | ||
| var __MUI_X_TELEMETRY_DISABLED__: boolean | undefined; | ||
| } | ||
| export declare function getTelemetryEnvConfig(skipCache?: boolean): TelemetryEnvConfig; | ||
| export declare function getTelemetryEnvConfigValue<K extends keyof TelemetryEnvConfig>(key: K): TelemetryEnvConfig[K]; | ||
| export declare function setTelemetryEnvConfigValue<K extends keyof TelemetryEnvConfig>(key: K, value: NonNullable<TelemetryEnvConfig[K]>): void; | ||
| export {}; |
| const envEnabledValues = ['1', 'true', 'yes', 'y']; | ||
| const envDisabledValues = ['0', 'false', 'no', 'n']; | ||
| function getBooleanEnv(value) { | ||
| if (!value) { | ||
| return undefined; | ||
| } | ||
| if (envEnabledValues.includes(value)) { | ||
| return true; | ||
| } | ||
| if (envDisabledValues.includes(value)) { | ||
| return false; | ||
| } | ||
| return undefined; | ||
| } | ||
| function getBooleanEnvFromEnvObject(envKey, envObj) { | ||
| const keys = Object.keys(envObj); | ||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i]; | ||
| if (!key.endsWith(envKey)) { | ||
| continue; | ||
| } | ||
| const value = getBooleanEnv(envObj[key]?.toLowerCase()); | ||
| if (typeof value === 'boolean') { | ||
| return value; | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
| function getIsTelemetryCollecting() { | ||
| // Check global variable | ||
| // eslint-disable-next-line no-underscore-dangle | ||
| const globalValue = globalThis.__MUI_X_TELEMETRY_DISABLED__; | ||
| if (typeof globalValue === 'boolean') { | ||
| // If disabled=true, telemetry is disabled | ||
| // If disabled=false, telemetry is enabled | ||
| return !globalValue; | ||
| } | ||
| try { | ||
| if (typeof process !== 'undefined' && process.env && typeof process.env === 'object') { | ||
| const result = getBooleanEnvFromEnvObject('MUI_X_TELEMETRY_DISABLED', process.env); | ||
| if (typeof result === 'boolean') { | ||
| // If disabled=true, telemetry is disabled | ||
| // If disabled=false, telemetry is enabled | ||
| return !result; | ||
| } | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| try { | ||
| // Some build tools replace env variables on compilation | ||
| // e.g. Next.js, webpack EnvironmentPlugin | ||
| const envValue = process.env.MUI_X_TELEMETRY_DISABLED || process.env.NEXT_PUBLIC_MUI_X_TELEMETRY_DISABLED || process.env.GATSBY_MUI_X_TELEMETRY_DISABLED || process.env.REACT_APP_MUI_X_TELEMETRY_DISABLED || process.env.PUBLIC_MUI_X_TELEMETRY_DISABLED; | ||
| const result = getBooleanEnv(envValue); | ||
| if (typeof result === 'boolean') { | ||
| // If disabled=true, telemetry is disabled | ||
| // If disabled=false, telemetry is enabled | ||
| return !result; | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| return undefined; | ||
| } | ||
| function getIsDebugModeEnabled() { | ||
| try { | ||
| // Check global variable | ||
| // eslint-disable-next-line no-underscore-dangle | ||
| const globalValue = globalThis.__MUI_X_TELEMETRY_DEBUG__; | ||
| if (typeof globalValue === 'boolean') { | ||
| return globalValue; | ||
| } | ||
| if (typeof process !== 'undefined' && process.env && typeof process.env === 'object') { | ||
| const result = getBooleanEnvFromEnvObject('MUI_X_TELEMETRY_DEBUG', process.env); | ||
| if (typeof result === 'boolean') { | ||
| return result; | ||
| } | ||
| } | ||
| // e.g. Webpack EnvironmentPlugin | ||
| if (process.env.MUI_X_TELEMETRY_DEBUG) { | ||
| const result = getBooleanEnv(process.env.MUI_X_TELEMETRY_DEBUG); | ||
| if (typeof result === 'boolean') { | ||
| return result; | ||
| } | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| try { | ||
| // e.g. Next.js, webpack EnvironmentPlugin | ||
| const envValue = process.env.MUI_X_TELEMETRY_DEBUG || process.env.NEXT_PUBLIC_MUI_X_TELEMETRY_DEBUG || process.env.GATSBY_MUI_X_TELEMETRY_DEBUG || process.env.REACT_APP_MUI_X_TELEMETRY_DEBUG || process.env.PUBLIC_MUI_X_TELEMETRY_DEBUG; | ||
| const result = getBooleanEnv(envValue); | ||
| if (typeof result === 'boolean') { | ||
| return result; | ||
| } | ||
| } catch (_) { | ||
| // If there is an error, return the default value | ||
| } | ||
| return false; | ||
| } | ||
| function getNodeEnv() { | ||
| try { | ||
| return process.env.NODE_ENV ?? '<unknown>'; | ||
| } catch (_) { | ||
| return '<unknown>'; | ||
| } | ||
| } | ||
| let cachedEnv = null; | ||
| export function getTelemetryEnvConfig(skipCache = false) { | ||
| if (skipCache || !cachedEnv) { | ||
| cachedEnv = { | ||
| NODE_ENV: getNodeEnv(), | ||
| IS_COLLECTING: getIsTelemetryCollecting(), | ||
| DEBUG: getIsDebugModeEnabled() | ||
| }; | ||
| } | ||
| return cachedEnv; | ||
| } | ||
| export function getTelemetryEnvConfigValue(key) { | ||
| return getTelemetryEnvConfig()[key]; | ||
| } | ||
| export function setTelemetryEnvConfigValue(key, value) { | ||
| getTelemetryEnvConfig()[key] = value; | ||
| } |
| import { TelemetryEventContext } from "../types.js"; | ||
| declare const muiXTelemetryEvents: { | ||
| licenseVerification: (() => null) | ((context: TelemetryEventContext, payload: { | ||
| packageReleaseInfo: string; | ||
| packageName: string; | ||
| licenseStatus?: string; | ||
| }) => { | ||
| eventName: string; | ||
| payload: { | ||
| packageReleaseInfo: string; | ||
| packageName: string; | ||
| licenseStatus?: string; | ||
| }; | ||
| context: TelemetryEventContext; | ||
| }); | ||
| }; | ||
| export default muiXTelemetryEvents; |
| const noop = () => null; | ||
| const muiXTelemetryEvents = { | ||
| licenseVerification: process.env.NODE_ENV === 'production' ? noop : (context, payload) => ({ | ||
| eventName: 'licenseVerification', | ||
| payload, | ||
| context | ||
| }) | ||
| }; | ||
| export default muiXTelemetryEvents; |
| declare function fetchWithRetry(url: string, options: RequestInit, retries?: number): Promise<Response>; | ||
| export { fetchWithRetry }; |
| async function fetchWithRetry(url, options, retries = 3) { | ||
| try { | ||
| const response = await fetch(url, options); | ||
| if (response.ok) { | ||
| return response; | ||
| } | ||
| throw new Error(`Request failed with status ${response.status}`); | ||
| } catch (error) { | ||
| if (retries === 0) { | ||
| throw error; | ||
| } | ||
| return new Promise(resolve => { | ||
| setTimeout(() => { | ||
| resolve(fetchWithRetry(url, options, retries - 1)); | ||
| }, Math.random() * 3_000); | ||
| }); | ||
| } | ||
| } | ||
| export { fetchWithRetry }; |
| import type { TelemetryContextType } from "../context.js"; | ||
| declare function getTelemetryContext(): Promise<TelemetryContextType>; | ||
| export { TelemetryContextType }; | ||
| export default getTelemetryContext; |
| import _extends from "@babel/runtime/helpers/esm/extends"; | ||
| import telemetryContext from "../context.js"; | ||
| import { getWindowStorageItem, setWindowStorageItem } from "./window-storage.js"; | ||
| function generateId(length) { | ||
| let result = ''; | ||
| const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
| const charactersLength = characters.length; | ||
| let counter = 0; | ||
| while (counter < length) { | ||
| result += characters.charAt(Math.floor(Math.random() * charactersLength)); | ||
| counter += 1; | ||
| } | ||
| return result; | ||
| } | ||
| function pick(obj, keys) { | ||
| return keys.reduce((acc, key) => { | ||
| acc[key] = obj[key]; | ||
| return acc; | ||
| }, {}); | ||
| } | ||
| const getBrowserFingerprint = typeof window === 'undefined' || process.env.NODE_ENV === 'test' ? () => undefined : async () => { | ||
| const fingerprintLCKey = 'fingerprint'; | ||
| try { | ||
| const existingFingerprint = getWindowStorageItem('localStorage', fingerprintLCKey); | ||
| if (existingFingerprint) { | ||
| return JSON.parse(existingFingerprint); | ||
| } | ||
| const FingerprintJS = await import('@fingerprintjs/fingerprintjs'); | ||
| const fp = await FingerprintJS.load({ | ||
| monitoring: false | ||
| }); | ||
| const fpResult = await fp.get(); | ||
| const components = _extends({}, fpResult.components); | ||
| delete components.cookiesEnabled; | ||
| const fullHash = FingerprintJS.hashComponents(components); | ||
| const coreHash = FingerprintJS.hashComponents(_extends({}, pick(components, ['fonts', 'audio', 'languages', 'deviceMemory', 'timezone', 'sessionStorage', 'localStorage', 'indexedDB', 'openDatabase', 'platform', 'canvas', 'vendor', 'vendorFlavors', 'colorGamut', 'forcedColors', 'monochrome', 'contrast', 'reducedMotion', 'math', 'videoCard', 'architecture']))); | ||
| const result = { | ||
| fullHash, | ||
| coreHash | ||
| }; | ||
| setWindowStorageItem('localStorage', fingerprintLCKey, JSON.stringify(result)); | ||
| return result; | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| }; | ||
| function getAnonymousId() { | ||
| const localStorageKey = 'anonymous_id'; | ||
| const existingAnonymousId = getWindowStorageItem('localStorage', localStorageKey); | ||
| if (existingAnonymousId) { | ||
| return existingAnonymousId; | ||
| } | ||
| const generated = `anid_${generateId(32)}`; | ||
| if (setWindowStorageItem('localStorage', localStorageKey, generated)) { | ||
| return generated; | ||
| } | ||
| return ''; | ||
| } | ||
| function getSessionId() { | ||
| const localStorageKey = 'session_id'; | ||
| const existingSessionId = getWindowStorageItem('sessionStorage', localStorageKey); | ||
| if (existingSessionId) { | ||
| return existingSessionId; | ||
| } | ||
| const generated = `sesid_${generateId(32)}`; | ||
| if (setWindowStorageItem('sessionStorage', localStorageKey, generated)) { | ||
| return generated; | ||
| } | ||
| return `sestp_${generateId(32)}`; | ||
| } | ||
| async function getTelemetryContext() { | ||
| telemetryContext.traits.sessionId = getSessionId(); | ||
| // Initialize the context if it hasn't been initialized yet | ||
| // (e.g. postinstall not run) | ||
| if (!telemetryContext.config.isInitialized) { | ||
| telemetryContext.traits.anonymousId = getAnonymousId(); | ||
| telemetryContext.config.isInitialized = true; | ||
| } | ||
| if (!telemetryContext.traits.fingerprint) { | ||
| telemetryContext.traits.fingerprint = await getBrowserFingerprint(); | ||
| } | ||
| return telemetryContext; | ||
| } | ||
| export default getTelemetryContext; |
| import { TelemetryEvent } from "../types.js"; | ||
| declare function sendMuiXTelemetryEvent(event: TelemetryEvent | null): Promise<void>; | ||
| export default sendMuiXTelemetryEvent; |
| import _extends from "@babel/runtime/helpers/esm/extends"; | ||
| import { getTelemetryEnvConfigValue } from "./config.js"; | ||
| import { fetchWithRetry } from "./fetcher.js"; | ||
| const sendMuiXTelemetryRetries = 3; | ||
| function shouldSendTelemetry(telemetryContext) { | ||
| // Disable reporting in SSR / Node.js | ||
| if (typeof window === 'undefined') { | ||
| return false; | ||
| } | ||
| // Priority to the config (e.g. in code, env) | ||
| const envIsCollecting = getTelemetryEnvConfigValue('IS_COLLECTING'); | ||
| if (typeof envIsCollecting === 'boolean') { | ||
| return envIsCollecting; | ||
| } | ||
| // Disable collection of the telemetry in CI builds, | ||
| // as it not related to development process | ||
| if (telemetryContext.traits.isCI) { | ||
| return false; | ||
| } | ||
| // Disabled by default | ||
| return false; | ||
| } | ||
| async function sendMuiXTelemetryEvent(event) { | ||
| try { | ||
| // Disable collection of the telemetry | ||
| // in production environment | ||
| if (process.env.NODE_ENV === 'production') { | ||
| return; | ||
| } | ||
| const { | ||
| default: getTelemetryContext | ||
| } = await import("./get-context.js"); | ||
| const telemetryContext = await getTelemetryContext(); | ||
| if (!event || !shouldSendTelemetry(telemetryContext)) { | ||
| return; | ||
| } | ||
| const eventPayload = _extends({}, event, { | ||
| context: _extends({}, telemetryContext.traits, event.context) | ||
| }); | ||
| if (getTelemetryEnvConfigValue('DEBUG')) { | ||
| console.log('[mui-x-telemetry] event', JSON.stringify(eventPayload, null, 2)); | ||
| return; | ||
| } | ||
| // TODO: batch events and send them in a single request when there will be more | ||
| await fetchWithRetry('https://x-telemetry.mui.com/v2/telemetry/record', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'X-Telemetry-Client-Version': "9.0.0-alpha.0" ?? '<dev>', | ||
| 'X-Telemetry-Node-Env': process.env.NODE_ENV ?? '<unknown>' | ||
| }, | ||
| body: JSON.stringify([eventPayload]) | ||
| }, sendMuiXTelemetryRetries); | ||
| } catch (_) { | ||
| console.log('[mui-x-telemetry] error', _); | ||
| } | ||
| } | ||
| export default sendMuiXTelemetryEvent; |
| declare const muiXTelemetrySettings: { | ||
| enableDebug: () => void; | ||
| enableTelemetry: () => void; | ||
| disableTelemetry: () => void; | ||
| }; | ||
| export default muiXTelemetrySettings; |
| import { setTelemetryEnvConfigValue } from "./config.js"; | ||
| const muiXTelemetrySettings = { | ||
| enableDebug: () => { | ||
| setTelemetryEnvConfigValue('DEBUG', true); | ||
| }, | ||
| enableTelemetry: () => { | ||
| setTelemetryEnvConfigValue('IS_COLLECTING', true); | ||
| }, | ||
| disableTelemetry: () => { | ||
| setTelemetryEnvConfigValue('IS_COLLECTING', false); | ||
| } | ||
| }; | ||
| export default muiXTelemetrySettings; |
| type WindowStorageType = 'localStorage' | 'sessionStorage'; | ||
| export declare function setWindowStorageItem(type: WindowStorageType, key: string, value: string): boolean; | ||
| export declare function getWindowStorageItem(type: WindowStorageType, key: string): string | null; | ||
| export {}; |
| const prefix = '__mui_x_telemetry_'; | ||
| function getStorageKey(key) { | ||
| return prefix + btoa(key); | ||
| } | ||
| export function setWindowStorageItem(type, key, value) { | ||
| try { | ||
| if (typeof window !== 'undefined' && window[type]) { | ||
| window[type].setItem(getStorageKey(key), value); | ||
| return true; | ||
| } | ||
| } catch (_) { | ||
| // Storage is unavailable, skip it | ||
| } | ||
| return false; | ||
| } | ||
| export function getWindowStorageItem(type, key) { | ||
| try { | ||
| if (typeof window !== 'undefined' && window[type]) { | ||
| return window[type].getItem(getStorageKey(key)); | ||
| } | ||
| } catch (_) { | ||
| // Storage is unavailable, skip it | ||
| } | ||
| return null; | ||
| } |
| export interface TelemetryEventContext { | ||
| licenseKey?: string; | ||
| } | ||
| export interface TelemetryEvent { | ||
| eventName: string; | ||
| payload: Record<string, any>; | ||
| context: TelemetryEventContext; | ||
| } |
| export {}; |
| interface EnvironmentInfo { | ||
| isDocker: boolean; | ||
| isCI: boolean; | ||
| } | ||
| export default function getEnvironmentInfo(): EnvironmentInfo; | ||
| export {}; |
| export default function getAnonymousMachineId(): Promise<string | null>; |
| export default function getAnonymousProjectId(): Promise<string>; |
| export {}; |
| export default function notifyAboutMuiXTelemetry(): void; |
| export declare class TelemetryStorage { | ||
| private readonly conf; | ||
| static init({ | ||
| distDir | ||
| }: { | ||
| distDir: string; | ||
| }): Promise<TelemetryStorage>; | ||
| private constructor(); | ||
| private notify; | ||
| get configPath(): string | undefined; | ||
| get anonymousId(): string; | ||
| } |
Sorry, the diff of this file is too big to display
Install scripts
Supply chain riskInstall scripts are run when the package is installed or built. Malicious packages often use scripts that run automatically to execute payloads or fetch additional code.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 15 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed or built. Malicious packages often use scripts that run automatically to execute payloads or fetch additional code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 13 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
383369
3.52%5
-16.67%58
-15.94%1290
-2.42%38
-24%61
10.91%5
25%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed