Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@mui/x-telemetry

Package Overview
Dependencies
Maintainers
16
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mui/x-telemetry - npm Package Compare versions

Comparing version
9.0.0-alpha.2
to
9.0.0-rc.0
+10
postinstall/hash.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sha256 = sha256;
var _crypto = require("crypto");
function sha256(value) {
return (0, _crypto.createHash)('sha256').update(value).digest('hex');
}
import { createHash } from 'crypto';
export function sha256(value) {
return createHash('sha256').update(value).digest('hex');
}
export declare function hashString(str: string): Promise<string>;
export declare function hashString(str: string): Promise<string>;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.hashString = hashString;
async function hashString(str) {
const data = new TextEncoder().encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
export async function hashString(str) {
const data = new TextEncoder().encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
+6
-0
export interface TelemetryContextType {
config: {
isInitialized: boolean;
/** Tracks whether we've already attempted runtime projectId resolution (avoids repeated fetch) */
runtimePackageNameHashResolved?: boolean;
};

@@ -12,2 +14,6 @@ traits: Record<string, any> & {

} | null;
repoHash?: string | null;
postinstallPackageNameHash?: string | null;
runtimePackageNameHash?: string | null;
rootPathHash?: string | null;
projectId?: string | null;

@@ -14,0 +20,0 @@ anonymousId?: string | null;

export interface TelemetryContextType {
config: {
isInitialized: boolean;
/** Tracks whether we've already attempted runtime projectId resolution (avoids repeated fetch) */
runtimePackageNameHashResolved?: boolean;
};

@@ -12,2 +14,6 @@ traits: Record<string, any> & {

} | null;
repoHash?: string | null;
postinstallPackageNameHash?: string | null;
runtimePackageNameHash?: string | null;
rootPathHash?: string | null;
projectId?: string | null;

@@ -14,0 +20,0 @@ anonymousId?: string | null;

+1
-1
/**
* @mui/x-telemetry v9.0.0-alpha.2
* @mui/x-telemetry v9.0.0-rc.0
*

@@ -4,0 +4,0 @@ * @license SEE LICENSE IN LICENSE

/**
* @mui/x-telemetry v9.0.0-alpha.2
* @mui/x-telemetry v9.0.0-rc.0
*

@@ -4,0 +4,0 @@ * @license SEE LICENSE IN LICENSE

{
"name": "@mui/x-telemetry",
"version": "9.0.0-alpha.2",
"version": "9.0.0-rc.0",
"author": "MUI Team",

@@ -23,3 +23,3 @@ "description": "MUI X Telemetry.",

"@fingerprintjs/fingerprintjs": "^3.4.2",
"ci-info": "^4.3.1",
"ci-info": "^4.4.0",
"is-docker": "^4.0.0",

@@ -26,0 +26,0 @@ "node-machine-id": "^1.1.12"

@@ -9,15 +9,71 @@ "use strict";

var _interopRequireWildcard2 = _interopRequireDefault(require("@babel/runtime/helpers/interopRequireWildcard"));
var _crypto = require("crypto");
var _fs = _interopRequireDefault(require("fs"));
var _isDocker = _interopRequireDefault(require("is-docker"));
var _os = _interopRequireDefault(require("os"));
var _hash = require("./hash");
// Q: Why does MUI need a machine ID?
// A:
// MUI's telemetry uses a hashed machine ID to approximate unique developer counts.
// Without a stable machine ID, every session in containers or CI creates a new
// persona, inflating seat counts.
//
// Fallback chain:
// 1. node-machine-id — most reliable, uses platform-specific APIs
// (IOPlatformUUID on macOS, MachineGuid on Windows, /var/lib/dbus/machine-id on Linux)
// 2. /etc/machine-id — present in most Linux distros and containers,
// stable across container restarts (only changes on image rebuild)
// 3. /var/lib/dbus/machine-id — alternative Linux path, some distros
// use this instead of /etc/machine-id
// 4. os.hostname() — used on macOS/Windows where hostnames are user-set and stable,
// and in Docker where the hostname is the container ID (random but unique per container,
// stable for the container's lifetime). Skipped on non-Docker Linux because hostnames
// are often generic defaults (e.g., "localhost", "ubuntu") which would conflate
// different machines into the same identity.
async function getAnonymousMachineId() {
// 1. Try node-machine-id (platform-specific, most reliable)
try {
const nodeMachineId = await Promise.resolve().then(() => (0, _interopRequireWildcard2.default)(require('node-machine-id')));
const rawMachineId = await nodeMachineId.machineId(true);
if (!rawMachineId) {
return null;
const id = await nodeMachineId.machineId(true);
if (id) {
return (0, _hash.sha256)(id);
}
return (0, _crypto.createHash)('sha256').update(rawMachineId).digest('hex');
} catch (_) {
// Ignore any errors
return null;
} catch {
// Not available (e.g., sandboxed env, missing native deps)
}
// 2. Try /etc/machine-id (Linux, most containers)
try {
const id = _fs.default.readFileSync('/etc/machine-id', 'utf-8').trim();
if (id) {
return (0, _hash.sha256)(id);
}
} catch {
// File doesn't exist (macOS, Windows, or minimal container image)
}
// 3. Try /var/lib/dbus/machine-id (alternative Linux path)
try {
const id = _fs.default.readFileSync('/var/lib/dbus/machine-id', 'utf-8').trim();
if (id) {
return (0, _hash.sha256)(id);
}
} catch {
// File doesn't exist
}
// 4. Try hostname — only on macOS/Windows where it's user-set and stable.
// On Linux, container runtimes assign random hostnames per run,
// which would generate a different hash each session and inflate counts.
if (process.platform !== 'linux' || (0, _isDocker.default)()) {
try {
const hostname = _os.default.hostname();
if (hostname) {
return (0, _hash.sha256)(hostname);
}
} catch {
// os.hostname() failed
}
}
return null;
}

@@ -1,14 +0,71 @@

import { createHash } from 'crypto';
import fs from 'fs';
import isDocker from 'is-docker';
import os from 'os';
import { sha256 } from "./hash.mjs";
// Q: Why does MUI need a machine ID?
// A:
// MUI's telemetry uses a hashed machine ID to approximate unique developer counts.
// Without a stable machine ID, every session in containers or CI creates a new
// persona, inflating seat counts.
//
// Fallback chain:
// 1. node-machine-id — most reliable, uses platform-specific APIs
// (IOPlatformUUID on macOS, MachineGuid on Windows, /var/lib/dbus/machine-id on Linux)
// 2. /etc/machine-id — present in most Linux distros and containers,
// stable across container restarts (only changes on image rebuild)
// 3. /var/lib/dbus/machine-id — alternative Linux path, some distros
// use this instead of /etc/machine-id
// 4. os.hostname() — used on macOS/Windows where hostnames are user-set and stable,
// and in Docker where the hostname is the container ID (random but unique per container,
// stable for the container's lifetime). Skipped on non-Docker Linux because hostnames
// are often generic defaults (e.g., "localhost", "ubuntu") which would conflate
// different machines into the same identity.
export default async function getAnonymousMachineId() {
// 1. Try node-machine-id (platform-specific, most reliable)
try {
const nodeMachineId = await import('node-machine-id');
const rawMachineId = await nodeMachineId.machineId(true);
if (!rawMachineId) {
return null;
const id = await nodeMachineId.machineId(true);
if (id) {
return sha256(id);
}
return createHash('sha256').update(rawMachineId).digest('hex');
} catch (_) {
// Ignore any errors
return null;
} catch {
// Not available (e.g., sandboxed env, missing native deps)
}
// 2. Try /etc/machine-id (Linux, most containers)
try {
const id = fs.readFileSync('/etc/machine-id', 'utf-8').trim();
if (id) {
return sha256(id);
}
} catch {
// File doesn't exist (macOS, Windows, or minimal container image)
}
// 3. Try /var/lib/dbus/machine-id (alternative Linux path)
try {
const id = fs.readFileSync('/var/lib/dbus/machine-id', 'utf-8').trim();
if (id) {
return sha256(id);
}
} catch {
// File doesn't exist
}
// 4. Try hostname — only on macOS/Windows where it's user-set and stable.
// On Linux, container runtimes assign random hostnames per run,
// which would generate a different hash each session and inflate counts.
if (process.platform !== 'linux' || isDocker()) {
try {
const hostname = os.hostname();
if (hostname) {
return sha256(hostname);
}
} catch {
// os.hostname() failed
}
}
return null;
}

@@ -7,6 +7,11 @@ "use strict";

});
exports.default = getAnonymousProjectId;
exports.getAnonymousPackageNameHash = getAnonymousPackageNameHash;
exports.getAnonymousRepoHash = getAnonymousRepoHash;
exports.getAnonymousRootPathHash = getAnonymousRootPathHash;
exports.getPackageName = getPackageName;
var _child_process = require("child_process");
var _crypto = require("crypto");
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _util = _interopRequireDefault(require("util"));
var _hash = require("./hash");
const asyncExec = _util.default.promisify(_child_process.exec);

@@ -19,3 +24,3 @@ async function execCLI(command) {

});
return String(response).trim();
return String(response.stdout).trim();
} catch (_) {

@@ -25,16 +30,47 @@ return null;

}
function getPackageName() {
const cwd = process.cwd();
const segments = cwd.split(_path.default.sep);
for (let i = segments.length; i > 0; i -= 1) {
const dir = segments.slice(0, i).join(_path.default.sep) || _path.default.sep;
try {
const content = _fs.default.readFileSync(_path.default.join(dir, 'package.json'), 'utf-8');
const pkg = JSON.parse(content);
if (pkg.name && typeof pkg.name === 'string') {
return pkg.name;
}
} catch (_) {
// No package.json at this level, continue walking up
}
}
return null;
}
// Q: Why does MUI need a project ID? Why is it looking at my git remote?
// Q: Why does MUI send multiple project identifiers?
// 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.
// MUI's telemetry always anonymizes these values. We send three separate
// signals (repoHash, postinstallPackageNameHash, rootPathHash) plus a computed projectId
// so we can handle monorepos (same repo, different apps) and micro-frontends
// (different repos, same app) correctly on the backend.
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();
// Raw repo identifier: git remote URL (hashed later into repoHash)
async function getRawRepoId() {
return (await execCLI(`git config --local --get remote.upstream.url`)) || (await execCLI(`git config --local --get remote.origin.url`)) || process.env.REPOSITORY_URL || null;
}
async function getAnonymousProjectId() {
const rawProjectId = await getRawProjectId();
return (0, _crypto.createHash)('sha256').update(rawProjectId).digest('hex');
// Raw root path: git root or cwd (hashed later into rootPathHash, unique per developer)
async function getRawRootPathId() {
return (await execCLI(`git rev-parse --show-toplevel`)) || process.cwd();
}
async function getAnonymousRepoHash() {
const raw = await getRawRepoId();
return raw ? (0, _hash.sha256)(raw) : null;
}
async function getAnonymousPackageNameHash() {
const raw = getPackageName();
return raw ? (0, _hash.sha256)(raw) : null;
}
async function getAnonymousRootPathHash() {
const raw = await getRawRootPathId();
return (0, _hash.sha256)(raw);
}
import { exec } from 'child_process';
import { createHash } from 'crypto';
import fs from 'fs';
import path from 'path';
import util from 'util';
import { sha256 } from "./hash.mjs";
const asyncExec = util.promisify(exec);

@@ -11,3 +13,3 @@ async function execCLI(command) {

});
return String(response).trim();
return String(response.stdout).trim();
} catch (_) {

@@ -17,16 +19,47 @@ return null;

}
export function getPackageName() {
const cwd = process.cwd();
const segments = cwd.split(path.sep);
for (let i = segments.length; i > 0; i -= 1) {
const dir = segments.slice(0, i).join(path.sep) || path.sep;
try {
const content = fs.readFileSync(path.join(dir, 'package.json'), 'utf-8');
const pkg = JSON.parse(content);
if (pkg.name && typeof pkg.name === 'string') {
return pkg.name;
}
} catch (_) {
// No package.json at this level, continue walking up
}
}
return null;
}
// Q: Why does MUI need a project ID? Why is it looking at my git remote?
// Q: Why does MUI send multiple project identifiers?
// 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.
// MUI's telemetry always anonymizes these values. We send three separate
// signals (repoHash, postinstallPackageNameHash, rootPathHash) plus a computed projectId
// so we can handle monorepos (same repo, different apps) and micro-frontends
// (different repos, same app) correctly on the backend.
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();
// Raw repo identifier: git remote URL (hashed later into repoHash)
async function getRawRepoId() {
return (await execCLI(`git config --local --get remote.upstream.url`)) || (await execCLI(`git config --local --get remote.origin.url`)) || process.env.REPOSITORY_URL || null;
}
export default async function getAnonymousProjectId() {
const rawProjectId = await getRawProjectId();
return createHash('sha256').update(rawProjectId).digest('hex');
// Raw root path: git root or cwd (hashed later into rootPathHash, unique per developer)
async function getRawRootPathId() {
return (await execCLI(`git rev-parse --show-toplevel`)) || process.cwd();
}
export async function getAnonymousRepoHash() {
const raw = await getRawRepoId();
return raw ? sha256(raw) : null;
}
export async function getAnonymousPackageNameHash() {
const raw = getPackageName();
return raw ? sha256(raw) : null;
}
export async function getAnonymousRootPathHash() {
const raw = await getRawRootPathId();
return sha256(raw);
}

@@ -10,12 +10,9 @@ "use strict";

var _getEnvironmentInfo = _interopRequireDefault(require("./get-environment-info"));
var _getProjectId = _interopRequireDefault(require("./get-project-id"));
var _getProjectId = require("./get-project-id");
var _getMachineId = _interopRequireDefault(require("./get-machine-id"));
var _storage = require("./storage");
const dirname = typeof __dirname === 'string' ? __dirname // cjs build in root dir
: (() => {
const filename = (0, _url.fileURLToPath)(require('url').pathToFileURL(__filename).toString());
// esm build in `esm` directory, so we need to go up two levels
return _path.default.dirname(_path.default.dirname(filename));
})();
// It's a flat build, both CJS and ESM files live in the same directory.
// postinstall/index.mjs is at <pkg-root>/postinstall/index.mjs,
// so we go up one level to reach the package root.
const dirname = typeof __dirname === 'string' ? __dirname : _path.default.dirname((0, _url.fileURLToPath)(require('url').pathToFileURL(__filename).toString()));
(async () => {

@@ -30,3 +27,6 @@ // If Node.js support permissions, we need to check if the current user has

});
const [environmentInfo, projectId, machineId] = await Promise.all([(0, _getEnvironmentInfo.default)(), (0, _getProjectId.default)(), (0, _getMachineId.default)()]);
const [environmentInfo, repoHash, postinstallPackageNameHash, rootPathHash, machineId] = await Promise.all([(0, _getEnvironmentInfo.default)(), (0, _getProjectId.getAnonymousRepoHash)(), (0, _getProjectId.getAnonymousPackageNameHash)(), (0, _getProjectId.getAnonymousRootPathHash)(), (0, _getMachineId.default)()]);
// Compute projectId from the resolved signals (no duplicate calls)
const projectId = repoHash || postinstallPackageNameHash || rootPathHash;
const contextData = {

@@ -38,2 +38,5 @@ config: {

machineId,
repoHash,
postinstallPackageNameHash,
rootPathHash,
projectId,

@@ -44,10 +47,12 @@ sessionId: (0, _crypto.randomBytes)(32).toString('hex'),

};
const writeContextData = (filePath, format) => {
const targetPath = _path.default.resolve(dirname, '..', filePath, 'context.js');
_fs.default.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'));
const packageRoot = _path.default.resolve(dirname, '..');
const content = JSON.stringify(contextData, null, 2);
// ESM: context.mjs
_fs.default.writeFileSync(_path.default.resolve(packageRoot, 'context.mjs'), `export default ${content};`);
// CJS: context.js
_fs.default.writeFileSync(_path.default.resolve(packageRoot, 'context.js'), [`"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);
});

@@ -7,12 +7,10 @@ import _extends from "@babel/runtime/helpers/esm/extends";

import getEnvironmentInfo from "./get-environment-info.mjs";
import getAnonymousProjectId from "./get-project-id.mjs";
import { getAnonymousRepoHash, getAnonymousPackageNameHash, getAnonymousRootPathHash } 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));
})();
// It's a flat build, both CJS and ESM files live in the same directory.
// postinstall/index.mjs is at <pkg-root>/postinstall/index.mjs,
// so we go up one level to reach the package root.
const dirname = typeof __dirname === 'string' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
(async () => {

@@ -27,3 +25,6 @@ // If Node.js support permissions, we need to check if the current user has

});
const [environmentInfo, projectId, machineId] = await Promise.all([getEnvironmentInfo(), getAnonymousProjectId(), getAnonymousMachineId()]);
const [environmentInfo, repoHash, postinstallPackageNameHash, rootPathHash, machineId] = await Promise.all([getEnvironmentInfo(), getAnonymousRepoHash(), getAnonymousPackageNameHash(), getAnonymousRootPathHash(), getAnonymousMachineId()]);
// Compute projectId from the resolved signals (no duplicate calls)
const projectId = repoHash || postinstallPackageNameHash || rootPathHash;
const contextData = {

@@ -35,2 +36,5 @@ config: {

machineId,
repoHash,
postinstallPackageNameHash,
rootPathHash,
projectId,

@@ -41,10 +45,12 @@ sessionId: randomBytes(32).toString('hex'),

};
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'));
const packageRoot = path.resolve(dirname, '..');
const content = JSON.stringify(contextData, null, 2);
// ESM: context.mjs
fs.writeFileSync(path.resolve(packageRoot, 'context.mjs'), `export default ${content};`);
// CJS: context.js
fs.writeFileSync(path.resolve(packageRoot, 'context.js'), [`"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);
});
+125
-0

@@ -38,1 +38,126 @@ # @mui/x-telemetry

```
## How to test
### Postinstall output
The postinstall script writes to two places:
1. **`<pkg-root>/context.js` + `<pkg-root>/context.mjs`** — all traits (`machineId`, `repoHash`, `postinstallPackageNameHash`, `rootPathHash`, `projectId`, `anonymousId`, `sessionId`, `isDocker`, `isCI`). Lives inside `node_modules` and gets regenerated on each install.
2. **Platform-specific config directory** — persists `anonymousId` and `notifiedAt` across reinstalls, so the `anonymousId` stays stable even if `node_modules` is wiped.
- **macOS:** `~/Library/Preferences/mui-x/config.json`
- **Windows:** `%APPDATA%\mui-x\Config\config.json`
- **Linux:** `$XDG_CONFIG_HOME/mui-x/config.json` (defaults to `~/.config/mui-x/config.json`)
- **CI/Docker:** `<cwd>/cache/mui-x/config.json` (ephemeral, inside the project)
### Testing the postinstall locally
```bash
# 1. Build the package
pnpm --filter @mui/x-telemetry run build
# 2. Clean previous output (start fresh)
rm -f packages/x-telemetry/build/context.js packages/x-telemetry/build/context.mjs ~/Library/Preferences/mui-x/config.json
# 3. Run the postinstall script
# Local (macOS/Linux)
node packages/x-telemetry/build/postinstall/index.js
# Docker
docker run --rm -v $(pwd):/repo -w /repo/packages/x-telemetry/build node:20 node ./postinstall/index.mjs
# 4. Verify the output
cat packages/x-telemetry/build/context.js # CJS context
cat packages/x-telemetry/build/context.mjs # ESM context
cat ~/Library/Preferences/mui-x/config.json # Persistent config (macOS)
```
### Testing the package.json name fallback (no git)
```bash
# 1. Build the package
pnpm --filter @mui/x-telemetry run build
# 2. Create a temp directory with only a package.json (no .git)
mkdir -p /tmp/test-pkg && echo '{"name": "my-test-app"}' > /tmp/test-pkg/package.json
# 3. Clean previous output
rm -f packages/x-telemetry/build/context.js packages/x-telemetry/build/context.mjs
# 4. Run postinstall from the temp directory
cd /tmp/test-pkg && node /path/to/mui-x/packages/x-telemetry/build/postinstall/index.js
# 5. Verify postinstallPackageNameHash and projectId match sha256("my-test-app")
grep -E 'postinstallPackageNameHash|projectId' /path/to/mui-x/packages/x-telemetry/build/context.js
echo -n "my-test-app" | shasum -a 256
# postinstallPackageNameHash and projectId should both match the hash
# 6. Go back to the repo and clean up
cd /path/to/mui-x
rm -rf /tmp/test-pkg
```
### Testing the runtime fallback (npm_package_name)
```bash
# 1. Build the package
pnpm --filter @mui/x-telemetry run build
# 2. Run with npm_package_name set (simulates npm/pnpm run context)
node -e "
process.env.npm_package_name = 'my-test-app';
import('./packages/x-telemetry/build/runtime/get-context.mjs').then(m =>
m.default().then(ctx => {
console.log('runtimePackageNameHash:', ctx.traits.runtimePackageNameHash);
console.log('projectId:', ctx.traits.projectId);
})
);
"
# 3. Verify runtimePackageNameHash matches sha256("my-test-app")
echo -n "my-test-app" | shasum -a 256
# runtimePackageNameHash should output: 04a0c785...
```
### Testing the runtime fallback (browser fetch)
```bash
# 1. Create a temp directory with a package.json
mkdir -p /tmp/test-fetch && cd /tmp/test-fetch
echo '{"name": "fetch-test-app"}' > package.json
# 2. Create an index.html that simulates the runtime fetch fallback
cat > index.html << 'HTMLEOF'
<script type="module">
async function hashString(str) {
const data = new TextEncoder().encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
const res = await fetch('/package.json');
const pkg = await res.json();
const projectId = await hashString(pkg.name);
console.log('projectId:', projectId);
document.body.innerText = 'projectId: ' + projectId;
</script>
HTMLEOF
# 3. Serve and open http://localhost:3000
npx serve .
# 4. Verify the browser console output matches sha256("fetch-test-app")
echo -n "fetch-test-app" | shasum -a 256
# Both should output: 1dcce451...
# 5. Clean up
rm -rf /tmp/test-fetch
```
### Unit tests
```bash
pnpm test:unit --project "x-telemetry" --run
```
import type { TelemetryContextType } from "../context.mjs";
declare function getRuntimePackageHash(): Promise<string | null>;
declare function getTelemetryContext(): Promise<TelemetryContextType>;
export { TelemetryContextType };
export { TelemetryContextType, getRuntimePackageHash };
export default getTelemetryContext;
import type { TelemetryContextType } from "../context.js";
declare function getRuntimePackageHash(): Promise<string | null>;
declare function getTelemetryContext(): Promise<TelemetryContextType>;
export { TelemetryContextType };
export { TelemetryContextType, getRuntimePackageHash };
export default getTelemetryContext;

@@ -8,5 +8,7 @@ "use strict";

exports.default = void 0;
exports.getRuntimePackageHash = getRuntimePackageHash;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _interopRequireWildcard2 = _interopRequireDefault(require("@babel/runtime/helpers/interopRequireWildcard"));
var _context = _interopRequireDefault(require("../context"));
var _hashString = require("./hash-string");
var _windowStorage = require("./window-storage");

@@ -80,2 +82,25 @@ function generateId(length) {

}
async function getRuntimePackageHash() {
// npm/pnpm scripts automatically set npm_package_name
if (typeof process !== 'undefined' && process.env?.npm_package_name) {
return (0, _hashString.hashString)(process.env.npm_package_name);
}
// Dev servers often serve package.json from project root
// (works with Vite, webpack-dev-server, and most static dev setups)
if (typeof window !== 'undefined') {
try {
const res = await fetch('/package.json');
if (res.ok) {
const pkg = await res.json();
if (pkg.name && typeof pkg.name === 'string') {
return (0, _hashString.hashString)(pkg.name);
}
}
} catch (_) {
// Not served by this dev server, skip
}
}
return null;
}
async function getTelemetryContext() {

@@ -90,2 +115,13 @@ _context.default.traits.sessionId = getSessionId();

}
// Always resolve runtimePackageNameHash (individual app name in monorepos)
if (!_context.default.traits.runtimePackageNameHash && !_context.default.config.runtimePackageNameHashResolved) {
_context.default.config.runtimePackageNameHashResolved = true;
const runtimePackageHash = await getRuntimePackageHash();
_context.default.traits.runtimePackageNameHash = runtimePackageHash;
if (runtimePackageHash) {
// Recompute projectId: repoHash || runtimePackageNameHash || postinstallPackageNameHash || rootPathHash
_context.default.traits.projectId = _context.default.traits.repoHash || runtimePackageHash || _context.default.traits.postinstallPackageNameHash || _context.default.traits.rootPathHash || _context.default.traits.projectId;
}
}
if (!_context.default.traits.fingerprint) {

@@ -92,0 +128,0 @@ _context.default.traits.fingerprint = await getBrowserFingerprint();

import _extends from "@babel/runtime/helpers/esm/extends";
import telemetryContext from "../context.mjs";
import { hashString } from "./hash-string.mjs";
import { getWindowStorageItem, setWindowStorageItem } from "./window-storage.mjs";

@@ -71,2 +72,25 @@ function generateId(length) {

}
async function getRuntimePackageHash() {
// npm/pnpm scripts automatically set npm_package_name
if (typeof process !== 'undefined' && process.env?.npm_package_name) {
return hashString(process.env.npm_package_name);
}
// Dev servers often serve package.json from project root
// (works with Vite, webpack-dev-server, and most static dev setups)
if (typeof window !== 'undefined') {
try {
const res = await fetch('/package.json');
if (res.ok) {
const pkg = await res.json();
if (pkg.name && typeof pkg.name === 'string') {
return hashString(pkg.name);
}
}
} catch (_) {
// Not served by this dev server, skip
}
}
return null;
}
async function getTelemetryContext() {

@@ -81,2 +105,13 @@ telemetryContext.traits.sessionId = getSessionId();

}
// Always resolve runtimePackageNameHash (individual app name in monorepos)
if (!telemetryContext.traits.runtimePackageNameHash && !telemetryContext.config.runtimePackageNameHashResolved) {
telemetryContext.config.runtimePackageNameHashResolved = true;
const runtimePackageHash = await getRuntimePackageHash();
telemetryContext.traits.runtimePackageNameHash = runtimePackageHash;
if (runtimePackageHash) {
// Recompute projectId: repoHash || runtimePackageNameHash || postinstallPackageNameHash || rootPathHash
telemetryContext.traits.projectId = telemetryContext.traits.repoHash || runtimePackageHash || telemetryContext.traits.postinstallPackageNameHash || telemetryContext.traits.rootPathHash || telemetryContext.traits.projectId;
}
}
if (!telemetryContext.traits.fingerprint) {

@@ -87,2 +122,3 @@ telemetryContext.traits.fingerprint = await getBrowserFingerprint();

}
export { getRuntimePackageHash };
export default getTelemetryContext;

@@ -61,3 +61,3 @@ "use strict";

'Content-Type': 'application/json',
'X-Telemetry-Client-Version': "9.0.0-alpha.2" ?? '<dev>',
'X-Telemetry-Client-Version': "9.0.0-rc.0" ?? '<dev>',
'X-Telemetry-Node-Env': process.env.NODE_ENV ?? '<unknown>'

@@ -64,0 +64,0 @@ },

@@ -53,3 +53,3 @@ import _extends from "@babel/runtime/helpers/esm/extends";

'Content-Type': 'application/json',
'X-Telemetry-Client-Version': "9.0.0-alpha.2" ?? '<dev>',
'X-Telemetry-Client-Version': "9.0.0-rc.0" ?? '<dev>',
'X-Telemetry-Node-Env': process.env.NODE_ENV ?? '<unknown>'

@@ -56,0 +56,0 @@ },

export {};
declare global {
interface MUIEnv {
npm_package_name?: string;
MUI_VERSION?: string;

@@ -5,0 +6,0 @@ MUI_X_TELEMETRY_DISABLED?: string;

export {};
declare global {
interface MUIEnv {
npm_package_name?: string;
MUI_VERSION?: string;

@@ -5,0 +6,0 @@ MUI_X_TELEMETRY_DISABLED?: string;

Sorry, the diff of this file is too big to display