Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:

env:
TELEMETRY_TRACKING_TOKEN: ${{ secrets.TELEMETRY_TRACKING_TOKEN }}
VSCODE_TELEMETRY_TRACKING_TOKEN: ${{ secrets.VSCODE_TELEMETRY_TRACKING_TOKEN }}
DO_NOT_TRACK: '1'

permissions:
Expand Down
9 changes: 8 additions & 1 deletion packages/ide/vscode/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import config from '@zenstackhq/eslint-config/base.js';

/** @type {import("eslint").Linter.Config} */
export default config;
export default [
...config,
{
rules: {
'no-prototype-builtins': 'off',
},
},
];
7 changes: 5 additions & 2 deletions packages/ide/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"url": "https://github.com/zenstackhq/zenstack"
},
"scripts": {
"build": "tsc --noEmit && tsup",
"build": "tsc --noEmit && tsup && tsx scripts/post-build.ts",
"watch": "tsup --watch",
"lint": "eslint src --ext ts",
"vscode:publish": "pnpm build && vsce publish --no-dependencies --follow-symlinks",
Expand All @@ -33,13 +33,16 @@
"dependencies": {
"@zenstackhq/language": "workspace:*",
"langium": "catalog:",
"mixpanel": "^0.18.0",
"uuid": "^11.1.0",
"vscode-languageclient": "^9.0.1",
"vscode-languageserver": "^9.0.1"
},
"devDependencies": {
"@types/vscode": "^1.90.0",
"@zenstackhq/eslint-config": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*"
"@zenstackhq/typescript-config": "workspace:*",
"dotenv": "^17.2.3"
},
"files": [
"dist",
Expand Down
16 changes: 16 additions & 0 deletions packages/ide/vscode/scripts/post-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import dotenv from 'dotenv';
import fs from 'node:fs';

dotenv.config({ path: './.env.local' });
dotenv.config({ path: './.env' });

const telemetryToken = process.env.VSCODE_TELEMETRY_TRACKING_TOKEN;
if (!telemetryToken) {
console.error('Error: VSCODE_TELEMETRY_TRACKING_TOKEN environment variable is not set');
process.exit(1);
}
const file = 'dist/extension.js';
let content = fs.readFileSync(file, 'utf-8');
content = content.replace('<VSCODE_TELEMETRY_TRACKING_TOKEN>', telemetryToken);
fs.writeFileSync(file, content, 'utf-8');
console.log('Telemetry token injected into dist/extension.js');
73 changes: 73 additions & 0 deletions packages/ide/vscode/src/extension/machine-id-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// modified from https://github.com/automation-stack/node-machine-id

import { execSync } from 'child_process';
import { createHash } from 'crypto';
import { v4 as uuid } from 'uuid';

const { platform } = process;
const win32RegBinPath = {
native: '%windir%\\System32',
mixed: '%windir%\\sysnative\\cmd.exe /c %windir%\\System32',
};
const guid = {
darwin: 'ioreg -rd1 -c IOPlatformExpertDevice',
win32:
`${win32RegBinPath[isWindowsProcessMixedOrNativeArchitecture()]}\\REG.exe ` +
'QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography ' +
'/v MachineGuid',
linux: '( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname 2> /dev/null) | head -n 1 || :',
freebsd: 'kenv -q smbios.system.uuid || sysctl -n kern.hostuuid',
};

function isWindowsProcessMixedOrNativeArchitecture() {
if (process.arch === 'ia32' && process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) {
return 'mixed';
}
return 'native';
}

function hash(guid: string): string {
return createHash('sha256').update(guid).digest('hex');
}

function expose(result: string): string {
switch (platform) {
case 'darwin':
return result
.split('IOPlatformUUID')[1]!
.split('\n')[0]!
.replace(/=|\s+|"/gi, '')
.toLowerCase();
case 'win32':
return result
.toString()
.split('REG_SZ')[1]!
.replace(/\r+|\n+|\s+/gi, '')
.toLowerCase();
case 'linux':
return result
.toString()
.replace(/\r+|\n+|\s+/gi, '')
.toLowerCase();
case 'freebsd':
return result
.toString()
.replace(/\r+|\n+|\s+/gi, '')
.toLowerCase();
default:
throw new Error(`Unsupported platform: ${process.platform}`);
}
}

export function getMachineId() {
if (!(platform in guid)) {
return uuid();
}
try {
const value = execSync(guid[platform as keyof typeof guid]);
const id = expose(value.toString());
return hash(id);
} catch {
return uuid();
}
}
2 changes: 2 additions & 0 deletions packages/ide/vscode/src/extension/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import * as path from 'node:path';
import type * as vscode from 'vscode';
import type { LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node.js';
import { LanguageClient, TransportKind } from 'vscode-languageclient/node.js';
import telemetry from './vscode-telemetry';

let client: LanguageClient;

// This function is called when the extension is activated.
export function activate(context: vscode.ExtensionContext): void {
client = startLanguageClient(context);
telemetry.track('extension:activate');
}

// This function is called when the extension is deactivated.
Expand Down
62 changes: 62 additions & 0 deletions packages/ide/vscode/src/extension/vscode-telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { init } from 'mixpanel';
import type { Mixpanel } from 'mixpanel';
import * as os from 'os';
import * as vscode from 'vscode';
import { getMachineId } from './machine-id-utils';
import { v5 as uuidv5 } from 'uuid';
import { version as extensionVersion } from '../../package.json';

export const VSCODE_TELEMETRY_TRACKING_TOKEN = '<VSCODE_TELEMETRY_TRACKING_TOKEN>';

export type TelemetryEvents = 'extension:activate' | 'extension:zmodel-preview' | 'extension:zmodel-save';

export class VSCodeTelemetry {
private readonly mixpanel: Mixpanel | undefined;
private readonly deviceId = this.getDeviceId();
private readonly _os_type = os.type();
private readonly _os_release = os.release();
private readonly _os_arch = os.arch();
private readonly _os_version = os.version();
private readonly _os_platform = os.platform();
private readonly vscodeAppName = vscode.env.appName;
private readonly vscodeVersion = vscode.version;
private readonly vscodeAppHost = vscode.env.appHost;

constructor() {
if (vscode.env.isTelemetryEnabled) {
this.mixpanel = init(VSCODE_TELEMETRY_TRACKING_TOKEN, {
geolocate: true,
});
}
}

private getDeviceId() {
const hostId = getMachineId();
// namespace UUID for generating UUIDv5 from DNS 'zenstack.dev'
return uuidv5(hostId, '133cac15-3efb-50fa-b5fc-4b90e441e563');
}

track(event: TelemetryEvents, properties: Record<string, unknown> = {}) {
if (this.mixpanel) {
const payload = {
distinct_id: this.deviceId,
time: new Date(),
$os: this._os_type,
osType: this._os_type,
osRelease: this._os_release,
osPlatform: this._os_platform,
osArch: this._os_arch,
osVersion: this._os_version,
nodeVersion: process.version,
vscodeAppName: this.vscodeAppName,
vscodeVersion: this.vscodeVersion,
vscodeAppHost: this.vscodeAppHost,
extensionVersion,
...properties,
};
this.mixpanel.track(event, payload);
}
}
}

export default new VSCodeTelemetry();
33 changes: 24 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading