@saurabhreo/package-tracker 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # Package Tracker
2
+
3
+ A lightweight npm package tracking utility similar to Reo. Tracks package installations and sends analytics data to your backend service.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Zero Runtime Overhead** - Only runs during installation, never at runtime
8
+ - 🔒 **Privacy-First** - Respects opt-out mechanisms and user preferences
9
+ - ⚡ **Non-Blocking** - Never interrupts package installation
10
+ - 📊 **Rich Data Collection** - System info, OS stats, package metadata, and more
11
+ - 🔄 **Retry Logic** - Automatic retries with exponential backoff
12
+ - 🎛️ **Configurable** - Environment variables and package.json settings
13
+
14
+ ## Installation
15
+
16
+ Add this package as a dependency to any npm package you want to track:
17
+
18
+ ```bash
19
+ npm install --save @package-tracker/tracker
20
+ ```
21
+
22
+ Once installed, the tracker will automatically run after `npm install` completes.
23
+
24
+ ## Configuration
25
+
26
+ ### Environment Variables
27
+
28
+ - `PACKAGE_TRACKER_ANALYTICS=false` - Disable tracking (opt-out)
29
+ - `PACKAGE_TRACKER_ANALYTICS=true` - Enable tracking (for opt-in mode)
30
+ - `PACKAGE_TRACKER_VERBOSE=true` - Enable verbose logging
31
+ - `PACKAGE_TRACKER_ENDPOINT=https://telemetry.reo.dev/data` - Set tracking endpoint URL
32
+
33
+ ### Package.json Settings
34
+
35
+ Add configuration to your `package.json`:
36
+
37
+ ```json
38
+ {
39
+ "reoSettings": {
40
+ "defaultOptIn": false,
41
+ "endpoint": "https://telemetry.reo.dev/data"
42
+ }
43
+ }
44
+ ```
45
+
46
+ #### Settings
47
+
48
+ - `defaultOptIn` (boolean, default: `true`)
49
+ - `true`: Tracking enabled by default (opt-out mode)
50
+ - `false`: Tracking disabled by default (opt-in mode)
51
+
52
+ - `endpoint` (string, optional)
53
+ - Custom endpoint URL for tracking data
54
+ - Falls back to `PACKAGE_TRACKER_ENDPOINT` environment variable
55
+
56
+ ## How It Works
57
+
58
+ 1. **Postinstall Hook**: The tracker runs automatically via npm's `postinstall` script
59
+ 2. **Data Collection**: Gathers system info, package metadata, and installation context
60
+ 3. **Telemetry Sending**: Sends data to your configured endpoint via HTTP POST
61
+ 4. **Non-Blocking**: All operations are asynchronous and won't block installations
62
+
63
+ ## Data Collected
64
+
65
+ The tracker collects the following information:
66
+
67
+ ### Package Information
68
+ - Package name and version
69
+ - Installation type (dependency, global, top-level)
70
+
71
+ ### System Information
72
+ - Platform (darwin, linux, win32, etc.)
73
+ - Architecture (x64, arm64, etc.)
74
+ - Node.js version
75
+ - OS type and release
76
+ - CPU count
77
+
78
+ ### Installation Context
79
+ - Timestamp
80
+ - Current working directory
81
+ - npm version
82
+ - Node environment
83
+
84
+ ### Optional Metadata
85
+ - Git user information (if available)
86
+ - Email domain (for company detection)
87
+ - CI/CD environment detection
88
+
89
+ ## Privacy & Opt-Out
90
+
91
+ ### For Package Authors
92
+
93
+ To make tracking opt-in (disabled by default):
94
+
95
+ ```json
96
+ {
97
+ "reoSettings": {
98
+ "defaultOptIn": false
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### For End Users
104
+
105
+ Users can opt-out by setting:
106
+
107
+ ```bash
108
+ export PACKAGE_TRACKER_ANALYTICS=false
109
+ npm install
110
+ ```
111
+
112
+ Or on Windows:
113
+
114
+ ```cmd
115
+ set PACKAGE_TRACKER_ANALYTICS=false
116
+ npm install
117
+ ```
118
+
119
+ ## Integration Example
120
+
121
+ ### Basic Integration
122
+
123
+ 1. Add the tracker to your package:
124
+
125
+ ```bash
126
+ npm install --save @package-tracker/tracker
127
+ ```
128
+
129
+ 2. Configure your endpoint in `package.json`:
130
+
131
+ ```json
132
+ {
133
+ "reoSettings": {
134
+ "endpoint": "https://telemetry.reo.dev/data"
135
+ }
136
+ }
137
+ ```
138
+
139
+ 3. Publish your package - tracking will work automatically!
140
+
141
+ ### Advanced Configuration
142
+
143
+ ```json
144
+ {
145
+ "name": "my-package",
146
+ "version": "1.0.0",
147
+ "reoSettings": {
148
+ "defaultOptIn": false,
149
+ "endpoint": "https://telemetry.reo.dev/data"
150
+ },
151
+ "dependencies": {
152
+ "@package-tracker/tracker": "^1.0.0"
153
+ }
154
+ }
155
+ ```
156
+
157
+ ## Backend API Requirements
158
+
159
+ Your backend endpoint should:
160
+
161
+ 1. Accept POST requests with JSON payload
162
+ 2. Return 2xx status codes for success
163
+ 3. Handle the following payload structure:
164
+
165
+ ```json
166
+ {
167
+ "package": {
168
+ "name": "package-name",
169
+ "version": "1.0.0"
170
+ },
171
+ "system": {
172
+ "platform": "darwin",
173
+ "arch": "x64",
174
+ "nodeVersion": "v18.0.0",
175
+ "osType": "Darwin",
176
+ "osRelease": "21.0.0",
177
+ "osPlatform": "darwin",
178
+ "cpuCount": 8
179
+ },
180
+ "installation": {
181
+ "timestamp": "2026-02-08T12:00:00.000Z",
182
+ "cwd": "/path/to/project",
183
+ "isTopLevel": false,
184
+ "npmVersion": "9.0.0",
185
+ "nodeEnv": "production"
186
+ },
187
+ "metadata": {
188
+ "emailDomain": "example.com",
189
+ "ci": [{"GITHUB_ACTIONS": "GitHub Actions"}]
190
+ }
191
+ }
192
+ ```
193
+
194
+ ## Development
195
+
196
+ ### Testing
197
+
198
+ See [TESTING.md](./TESTING.md) for comprehensive testing guide.
199
+
200
+ **Quick Test:**
201
+
202
+ ```bash
203
+ # Run test suite
204
+ npm test
205
+
206
+ # Start mock server for testing
207
+ npm run test:server
208
+
209
+ # Test directly with verbose output
210
+ PACKAGE_TRACKER_VERBOSE=true \
211
+ PACKAGE_TRACKER_ENDPOINT=https://httpbin.org/post \
212
+ node index.js
213
+ ```
214
+
215
+ ### Testing Locally
216
+
217
+ 1. Set verbose mode:
218
+ ```bash
219
+ export PACKAGE_TRACKER_VERBOSE=true
220
+ ```
221
+
222
+ 2. Test with a mock endpoint or your backend:
223
+ ```bash
224
+ export PACKAGE_TRACKER_ENDPOINT=https://telemetry.reo.dev/data
225
+ npm install
226
+ ```
227
+
228
+ ### Debugging
229
+
230
+ Enable verbose logging to see what data is being collected and sent:
231
+
232
+ ```bash
233
+ PACKAGE_TRACKER_VERBOSE=true npm install
234
+ ```
235
+
236
+ ## License
237
+
238
+ MIT
239
+
240
+ ## Similar Projects
241
+
242
+ This package is inspired by [@reo/reo](https://www.npmjs.com/package/@reo/reo) and provides similar functionality for custom backend services.
package/index.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Entry point for package tracker
5
+ * This file is executed when the postinstall script runs
6
+ */
7
+
8
+ const Tracker = require('./src/tracker');
9
+
10
+ // Run tracker asynchronously with a maximum timeout
11
+ // This ensures it never blocks the installation process
12
+ const tracker = new Tracker();
13
+
14
+ // Maximum time to wait before exiting (10 seconds)
15
+ const MAX_WAIT_TIME = 10000;
16
+
17
+ // Execute tracking with timeout protection
18
+ const trackingPromise = tracker.track().catch(() => {
19
+ // Silently handle any errors - never break installations
20
+ });
21
+
22
+ // Race between tracking completion and maximum wait time
23
+ Promise.race([
24
+ trackingPromise,
25
+ new Promise((resolve) => setTimeout(resolve, MAX_WAIT_TIME)),
26
+ ]).finally(() => {
27
+ // Exit after tracking completes or timeout, ensuring we don't hang
28
+ process.exit(0);
29
+ });
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@saurabhreo/package-tracker",
3
+ "version": "1.0.0",
4
+ "description": "Package tracking utility similar to Reo - tracks npm package installations",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "postinstall": "node index.js",
8
+ "test": "node test.js",
9
+ "test:server": "node test-mock-server.js",
10
+ "prepublishOnly": "npm test",
11
+ "publish:patch": "npm version patch && npm publish --access public",
12
+ "publish:minor": "npm version minor && npm publish --access public",
13
+ "publish:major": "npm version major && npm publish --access public"
14
+ },
15
+ "keywords": [
16
+ "analytics",
17
+ "tracking",
18
+ "npm",
19
+ "package-analytics",
20
+ "telemetry"
21
+ ],
22
+ "author": "Saurabh <saurabh@reo.dev>",
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=12.0.0"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/reodotdev/package_tracker.git"
30
+ },
31
+ "files": [
32
+ "index.js",
33
+ "src/**/*",
34
+ "README.md"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
@@ -0,0 +1,160 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ /**
6
+ * Data collector for package tracking
7
+ * Gathers system information, package metadata, and installation context
8
+ */
9
+ class Collector {
10
+ constructor(config) {
11
+ this.config = config;
12
+ }
13
+
14
+ /**
15
+ * Collect all tracking data
16
+ */
17
+ collect() {
18
+ try {
19
+ const data = {
20
+ // Package information
21
+ package: {
22
+ name: this.config.getPackageName(),
23
+ version: this.config.getPackageVersion(),
24
+ },
25
+
26
+ // System information
27
+ system: {
28
+ platform: process.platform,
29
+ arch: process.arch,
30
+ nodeVersion: process.version,
31
+ osType: os.type(),
32
+ osRelease: os.release(),
33
+ osPlatform: os.platform(),
34
+ cpuCount: os.cpus().length,
35
+ },
36
+
37
+ // Installation context
38
+ installation: {
39
+ timestamp: new Date().toISOString(),
40
+ cwd: process.cwd(),
41
+ isTopLevel: this.config.isTopLevelInstall(),
42
+ npmVersion: process.env.npm_version || 'unknown',
43
+ nodeEnv: process.env.NODE_ENV || 'unknown',
44
+ },
45
+
46
+ // Optional: Company/user information
47
+ metadata: this.collectMetadata(),
48
+ };
49
+
50
+ return data;
51
+ } catch (error) {
52
+ // Return minimal data if collection fails
53
+ return {
54
+ package: {
55
+ name: this.config.getPackageName(),
56
+ version: this.config.getPackageVersion(),
57
+ },
58
+ error: 'Collection failed',
59
+ timestamp: new Date().toISOString(),
60
+ };
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Collect optional metadata (company info, git info, etc.)
66
+ */
67
+ collectMetadata() {
68
+ const metadata = {};
69
+
70
+ try {
71
+ // Try to get git user info
72
+ const gitConfig = this.getGitConfig();
73
+ if (gitConfig.user) {
74
+ metadata.gitUser = gitConfig.user;
75
+ }
76
+ if (gitConfig.email) {
77
+ // Only include domain, not full email for privacy
78
+ const emailDomain = gitConfig.email.split('@')[1];
79
+ if (emailDomain) {
80
+ metadata.emailDomain = emailDomain;
81
+ }
82
+ }
83
+
84
+ // Try to detect CI/CD environment
85
+ const ciInfo = this.detectCI();
86
+ if (ciInfo) {
87
+ metadata.ci = ciInfo;
88
+ }
89
+
90
+ // Try to get npm user info (if available)
91
+ const npmUser = process.env.npm_config_user;
92
+ if (npmUser) {
93
+ metadata.npmUser = npmUser;
94
+ }
95
+ } catch (error) {
96
+ // Silently fail - metadata is optional
97
+ }
98
+
99
+ return metadata;
100
+ }
101
+
102
+ /**
103
+ * Read git config to get user information
104
+ */
105
+ getGitConfig() {
106
+ const gitConfig = {};
107
+ const gitConfigPath = path.join(os.homedir(), '.gitconfig');
108
+
109
+ try {
110
+ if (fs.existsSync(gitConfigPath)) {
111
+ const content = fs.readFileSync(gitConfigPath, 'utf8');
112
+
113
+ // Simple parsing of git config
114
+ const userMatch = content.match(/\[user\]\s+name\s*=\s*(.+)/i);
115
+ const emailMatch = content.match(/\[user\]\s+email\s*=\s*(.+)/i);
116
+
117
+ if (userMatch) {
118
+ gitConfig.user = userMatch[1].trim();
119
+ }
120
+ if (emailMatch) {
121
+ gitConfig.email = emailMatch[1].trim();
122
+ }
123
+ }
124
+ } catch (error) {
125
+ // Silently fail
126
+ }
127
+
128
+ return gitConfig;
129
+ }
130
+
131
+ /**
132
+ * Detect CI/CD environment
133
+ */
134
+ detectCI() {
135
+ const ciEnvs = {
136
+ CI: process.env.CI,
137
+ GITHUB_ACTIONS: process.env.GITHUB_ACTIONS ? 'GitHub Actions' : null,
138
+ GITLAB_CI: process.env.GITLAB_CI ? 'GitLab CI' : null,
139
+ JENKINS: process.env.JENKINS_URL ? 'Jenkins' : null,
140
+ TRAVIS: process.env.TRAVIS ? 'Travis CI' : null,
141
+ CIRCLE_CI: process.env.CIRCLE_CI ? 'CircleCI' : null,
142
+ BITBUCKET_BUILD_NUMBER: process.env.BITBUCKET_BUILD_NUMBER ? 'Bitbucket Pipelines' : null,
143
+ };
144
+
145
+ const detected = Object.entries(ciEnvs)
146
+ .filter(([_, value]) => value !== null && value !== undefined)
147
+ .map(([key, value]) => ({ [key]: value }));
148
+
149
+ return detected.length > 0 ? detected : null;
150
+ }
151
+
152
+ /**
153
+ * Format data for sending (can be overridden for custom formatting)
154
+ */
155
+ format(data) {
156
+ return JSON.stringify(data);
157
+ }
158
+ }
159
+
160
+ module.exports = Collector;
package/src/config.js ADDED
@@ -0,0 +1,132 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Configuration handler for package tracker
6
+ * Reads environment variables and package.json settings
7
+ */
8
+ class Config {
9
+ constructor() {
10
+ this.packageJsonPath = this.findPackageJson();
11
+ this.packageJson = this.loadPackageJson();
12
+ this.reoSettings = this.packageJson?.reoSettings || {};
13
+ }
14
+
15
+ /**
16
+ * Find package.json by walking up the directory tree
17
+ */
18
+ findPackageJson() {
19
+ let currentDir = process.cwd();
20
+ const root = path.parse(currentDir).root;
21
+
22
+ while (currentDir !== root) {
23
+ const packagePath = path.join(currentDir, 'package.json');
24
+ if (fs.existsSync(packagePath)) {
25
+ return packagePath;
26
+ }
27
+ currentDir = path.dirname(currentDir);
28
+ }
29
+
30
+ // Fallback to current directory
31
+ return path.join(process.cwd(), 'package.json');
32
+ }
33
+
34
+ /**
35
+ * Load package.json content
36
+ */
37
+ loadPackageJson() {
38
+ try {
39
+ if (fs.existsSync(this.packageJsonPath)) {
40
+ const content = fs.readFileSync(this.packageJsonPath, 'utf8');
41
+ return JSON.parse(content);
42
+ }
43
+ } catch (error) {
44
+ // Silently fail - package.json might not exist or be invalid
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Check if tracking is enabled
51
+ * Respects environment variable and package.json settings
52
+ */
53
+ isTrackingEnabled() {
54
+ // Check environment variable first (highest priority)
55
+ const envOptOut = process.env.PACKAGE_TRACKER_ANALYTICS === 'false';
56
+ if (envOptOut) {
57
+ return false;
58
+ }
59
+
60
+ // Check if defaultOptIn is set to false in package.json
61
+ if (this.reoSettings.defaultOptIn === false) {
62
+ // In opt-in mode, check if explicitly enabled
63
+ return process.env.PACKAGE_TRACKER_ANALYTICS === 'true';
64
+ }
65
+
66
+ // Default: opt-out mode (enabled by default)
67
+ return true;
68
+ }
69
+
70
+ /**
71
+ * Check if verbose logging is enabled
72
+ */
73
+ isVerbose() {
74
+ return process.env.PACKAGE_TRACKER_VERBOSE === 'true';
75
+ }
76
+
77
+ /**
78
+ * Get tracking endpoint URL
79
+ * Can be configured via environment variable or package.json
80
+ */
81
+ getEndpointUrl() {
82
+ return (
83
+ process.env.PACKAGE_TRACKER_ENDPOINT ||
84
+ this.reoSettings.endpoint ||
85
+ 'https://telemetry.reo.dev/data' // Default endpoint
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Get package name from package.json
91
+ */
92
+ getPackageName() {
93
+ return this.packageJson?.name || 'unknown';
94
+ }
95
+
96
+ /**
97
+ * Get package version from package.json
98
+ */
99
+ getPackageVersion() {
100
+ return this.packageJson?.version || 'unknown';
101
+ }
102
+
103
+ /**
104
+ * Check if this is a top-level installation
105
+ * Reo only tracks when installed as dependency or globally
106
+ */
107
+ isTopLevelInstall() {
108
+ // Check if we're in node_modules (dependency) or global install
109
+ const cwd = process.cwd();
110
+ const isInNodeModules = cwd.includes('node_modules');
111
+ const isGlobal = process.env.npm_config_global === 'true';
112
+
113
+ // If not in node_modules and not global, it's likely top-level
114
+ return !isInNodeModules && !isGlobal;
115
+ }
116
+
117
+ /**
118
+ * Get all configuration for logging
119
+ */
120
+ getConfig() {
121
+ return {
122
+ enabled: this.isTrackingEnabled(),
123
+ verbose: this.isVerbose(),
124
+ endpoint: this.getEndpointUrl(),
125
+ packageName: this.getPackageName(),
126
+ packageVersion: this.getPackageVersion(),
127
+ isTopLevel: this.isTopLevelInstall(),
128
+ };
129
+ }
130
+ }
131
+
132
+ module.exports = Config;
package/src/sender.js ADDED
@@ -0,0 +1,143 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ /**
5
+ * HTTP client for sending tracking data
6
+ * Implements retry logic, timeouts, and error handling
7
+ */
8
+ class Sender {
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.endpoint = config.getEndpointUrl();
12
+ this.maxRetries = 3;
13
+ this.timeout = 5000; // 5 seconds per request
14
+ this.maxTotalTimeout = 10000; // 10 seconds total maximum (including retries)
15
+ }
16
+
17
+ /**
18
+ * Send tracking data to the endpoint
19
+ * Non-blocking and best-effort (never throws)
20
+ */
21
+ async send(data) {
22
+ if (!this.config.isTrackingEnabled()) {
23
+ if (this.config.isVerbose()) {
24
+ console.log('[package-tracker] Tracking disabled');
25
+ }
26
+ return { success: false, reason: 'disabled' };
27
+ }
28
+
29
+ // Don't track top-level installations by default (like Reo)
30
+ if (this.config.isTopLevelInstall()) {
31
+ if (this.config.isVerbose()) {
32
+ console.log('[package-tracker] Skipping top-level installation');
33
+ }
34
+ return { success: false, reason: 'top-level-install' };
35
+ }
36
+
37
+ const payload = typeof data === 'string' ? data : JSON.stringify(data);
38
+
39
+ if (this.config.isVerbose()) {
40
+ console.log('[package-tracker] Sending data:', payload);
41
+ }
42
+
43
+ try {
44
+ // Enforce maximum total timeout to prevent hanging
45
+ const timeoutPromise = this.sleep(this.maxTotalTimeout).then(() => {
46
+ throw new Error('Maximum timeout exceeded');
47
+ });
48
+
49
+ const result = await Promise.race([
50
+ this.sendWithRetry(payload),
51
+ timeoutPromise,
52
+ ]);
53
+ return result;
54
+ } catch (error) {
55
+ // Never throw - this is best-effort
56
+ if (this.config.isVerbose()) {
57
+ console.error('[package-tracker] Failed to send:', error.message);
58
+ }
59
+ return { success: false, error: error.message };
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Send with retry logic
65
+ */
66
+ async sendWithRetry(payload, retryCount = 0) {
67
+ try {
68
+ const result = await this.makeRequest(payload);
69
+ return { success: true, statusCode: result.statusCode };
70
+ } catch (error) {
71
+ if (retryCount < this.maxRetries) {
72
+ // Exponential backoff: 1s, 2s, 4s
73
+ const delay = Math.pow(2, retryCount) * 1000;
74
+ await this.sleep(delay);
75
+ return this.sendWithRetry(payload, retryCount + 1);
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Make HTTP/HTTPS request
83
+ */
84
+ makeRequest(payload) {
85
+ return new Promise((resolve, reject) => {
86
+ const url = new URL(this.endpoint);
87
+ const isHttps = url.protocol === 'https:';
88
+ const client = isHttps ? https : http;
89
+
90
+ const options = {
91
+ hostname: url.hostname,
92
+ port: url.port || (isHttps ? 443 : 80),
93
+ path: url.pathname + url.search,
94
+ method: 'POST',
95
+ headers: {
96
+ 'Content-Type': 'application/json',
97
+ 'Content-Length': Buffer.byteLength(payload),
98
+ 'User-Agent': `package-tracker/${this.config.getPackageVersion()}`,
99
+ },
100
+ timeout: this.timeout,
101
+ };
102
+
103
+ const req = client.request(options, (res) => {
104
+ let data = '';
105
+
106
+ res.on('data', (chunk) => {
107
+ data += chunk;
108
+ });
109
+
110
+ res.on('end', () => {
111
+ if (res.statusCode >= 200 && res.statusCode < 300) {
112
+ resolve({ statusCode: res.statusCode, data });
113
+ } else {
114
+ reject(
115
+ new Error(`Request failed with status ${res.statusCode}`)
116
+ );
117
+ }
118
+ });
119
+ });
120
+
121
+ req.on('error', (error) => {
122
+ reject(error);
123
+ });
124
+
125
+ req.on('timeout', () => {
126
+ req.destroy();
127
+ reject(new Error('Request timeout'));
128
+ });
129
+
130
+ req.write(payload);
131
+ req.end();
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Sleep utility for retry delays
137
+ */
138
+ sleep(ms) {
139
+ return new Promise((resolve) => setTimeout(resolve, ms));
140
+ }
141
+ }
142
+
143
+ module.exports = Sender;
package/src/tracker.js ADDED
@@ -0,0 +1,69 @@
1
+ const Config = require('./config');
2
+ const Collector = require('./collector');
3
+ const Sender = require('./sender');
4
+
5
+ /**
6
+ * Main tracker orchestrator
7
+ * Coordinates configuration, data collection, and sending
8
+ */
9
+ class Tracker {
10
+ constructor() {
11
+ this.config = new Config();
12
+ this.collector = new Collector(this.config);
13
+ this.sender = new Sender(this.config);
14
+ }
15
+
16
+ /**
17
+ * Run the tracking process
18
+ * This is the main entry point
19
+ */
20
+ async track() {
21
+ try {
22
+ // Log tracking status if verbose
23
+ if (this.config.isVerbose()) {
24
+ const config = this.config.getConfig();
25
+ console.log('[package-tracker] Configuration:', JSON.stringify(config, null, 2));
26
+ }
27
+
28
+ // Check if tracking is enabled
29
+ if (!this.config.isTrackingEnabled()) {
30
+ if (this.config.isVerbose()) {
31
+ console.log('[package-tracker] Tracking is disabled');
32
+ }
33
+ return { success: false, reason: 'disabled' };
34
+ }
35
+
36
+ // Collect data
37
+ const data = this.collector.collect();
38
+
39
+ if (this.config.isVerbose()) {
40
+ console.log('[package-tracker] Collected data:', JSON.stringify(data, null, 2));
41
+ }
42
+
43
+ // Send data (non-blocking, best-effort)
44
+ const result = await this.sender.send(data);
45
+
46
+ // Log result if verbose
47
+ if (this.config.isVerbose()) {
48
+ console.log('[package-tracker] Send result:', result);
49
+ }
50
+
51
+ return result;
52
+ } catch (error) {
53
+ // Never throw - this should never break installations
54
+ if (this.config.isVerbose()) {
55
+ console.error('[package-tracker] Error:', error.message);
56
+ }
57
+ return { success: false, error: error.message };
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get current configuration (for debugging)
63
+ */
64
+ getConfig() {
65
+ return this.config.getConfig();
66
+ }
67
+ }
68
+
69
+ module.exports = Tracker;