@dynamicu/chromedebug-mcp 2.3.1 → 2.4.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 CHANGED
@@ -123,6 +123,56 @@ Chrome Debug includes a Chrome extension that enables visual element selection f
123
123
  - Console logs can be included in the recording
124
124
  - Workflows can be replayed later for debugging
125
125
 
126
+ ### Managing License Activations
127
+
128
+ Chrome Debug uses a license activation system to prevent activation limit exhaustion when reinstalling the extension.
129
+
130
+ #### The Problem
131
+
132
+ When you uninstall and reinstall the Chrome extension, it clears local storage and generates a new activation instance. With a typical 3-activation limit, frequent reinstalls can exhaust your available slots.
133
+
134
+ #### The Solution: Activation Manager
135
+
136
+ Chrome Debug now includes an **Activation Manager** that automatically handles this scenario:
137
+
138
+ **How It Works:**
139
+ 1. When activation limit is reached, the Activation Manager opens automatically
140
+ 2. Shows all your current activations with device info (e.g., "macOS 14.0 • Chrome 121")
141
+ 3. Identifies your current device (highlighted in green)
142
+ 4. You select which activation to deactivate
143
+ 5. Extension automatically retries activation on current device
144
+
145
+ **Example: Reinstalling on Same Machine**
146
+
147
+ ```
148
+ You reinstall the extension on your laptop:
149
+ → Try to activate license
150
+ → Activation Manager shows:
151
+ • Activation 1: "macOS 14.0 • Chrome 120" (Oct 10) ← OLD instance
152
+ • Activation 2: "Windows 11 • Chrome 121" (Oct 12)
153
+ • Activation 3: "Linux • Firefox 119" (Oct 14)
154
+ → You recognize #1 is this same laptop
155
+ → Click "Deactivate" on #1
156
+ → Extension automatically activates new instance
157
+ → Result: Still 3/3 slots, seamless experience
158
+ ```
159
+
160
+ **Best Practices:**
161
+ - Before uninstalling: No action needed - Activation Manager handles it
162
+ - Multiple devices: Activation Manager helps track which is which
163
+ - Unused devices: Periodically deactivate machines you no longer use
164
+
165
+ #### FAQ
166
+
167
+ **Q: What happens if I forget which activation is which?**
168
+ A: The Activation Manager shows device information (OS, browser version) and activation dates. Your current device is highlighted in green.
169
+
170
+ **Q: Can I deactivate my current device?**
171
+ A: No, the button is disabled for your current device to prevent accidental lockout.
172
+
173
+ **Q: Does reinstalling the browser consume an activation?**
174
+ A: Yes, because local storage is cleared. Use the Activation Manager to deactivate the old instance first or after reinstalling.
175
+
126
176
 
127
177
  ## Development
128
178
 
@@ -0,0 +1,208 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Manage License Activations</title>
6
+ <style>
7
+ body {
8
+ width: 450px;
9
+ padding: 20px;
10
+ font-family: Arial, sans-serif;
11
+ background: #f5f5f5;
12
+ }
13
+
14
+ h2 {
15
+ margin: 0 0 10px 0;
16
+ font-size: 20px;
17
+ color: #333;
18
+ }
19
+
20
+ .subtitle {
21
+ font-size: 14px;
22
+ color: #666;
23
+ margin-bottom: 20px;
24
+ }
25
+
26
+ .limit-info {
27
+ background: #fff3cd;
28
+ border: 1px solid #ffc107;
29
+ padding: 15px;
30
+ border-radius: 4px;
31
+ margin-bottom: 20px;
32
+ font-size: 14px;
33
+ color: #856404;
34
+ }
35
+
36
+ .limit-info strong {
37
+ color: #333;
38
+ }
39
+
40
+ .activations-list {
41
+ background: white;
42
+ border-radius: 4px;
43
+ border: 1px solid #e0e0e0;
44
+ max-height: 400px;
45
+ overflow-y: auto;
46
+ }
47
+
48
+ .activation-item {
49
+ padding: 15px;
50
+ border-bottom: 1px solid #e0e0e0;
51
+ transition: background 0.2s;
52
+ }
53
+
54
+ .activation-item:last-child {
55
+ border-bottom: none;
56
+ }
57
+
58
+ .activation-item:hover {
59
+ background: #f9f9f9;
60
+ }
61
+
62
+ .activation-item.current-device {
63
+ background: #e8f5e9;
64
+ border-left: 4px solid #4caf50;
65
+ }
66
+
67
+ .activation-header {
68
+ display: flex;
69
+ justify-content: space-between;
70
+ align-items: center;
71
+ margin-bottom: 10px;
72
+ }
73
+
74
+ .activation-number {
75
+ font-weight: bold;
76
+ font-size: 16px;
77
+ color: #333;
78
+ }
79
+
80
+ .current-badge {
81
+ background: #4caf50;
82
+ color: white;
83
+ padding: 2px 8px;
84
+ border-radius: 12px;
85
+ font-size: 11px;
86
+ font-weight: bold;
87
+ }
88
+
89
+ .device-info {
90
+ font-size: 14px;
91
+ color: #666;
92
+ margin-bottom: 8px;
93
+ }
94
+
95
+ .device-info strong {
96
+ color: #333;
97
+ }
98
+
99
+ .activation-date {
100
+ font-size: 12px;
101
+ color: #999;
102
+ margin-bottom: 10px;
103
+ }
104
+
105
+ .deactivate-btn {
106
+ padding: 8px 16px;
107
+ background: #f44336;
108
+ color: white;
109
+ border: none;
110
+ border-radius: 4px;
111
+ cursor: pointer;
112
+ font-size: 13px;
113
+ font-weight: 500;
114
+ transition: background 0.2s;
115
+ }
116
+
117
+ .deactivate-btn:hover {
118
+ background: #d32f2f;
119
+ }
120
+
121
+ .deactivate-btn:disabled {
122
+ background: #ccc;
123
+ cursor: not-allowed;
124
+ }
125
+
126
+ .actions {
127
+ margin-top: 20px;
128
+ display: flex;
129
+ gap: 10px;
130
+ }
131
+
132
+ .cancel-btn {
133
+ flex: 1;
134
+ padding: 10px;
135
+ background: #9e9e9e;
136
+ color: white;
137
+ border: none;
138
+ border-radius: 4px;
139
+ cursor: pointer;
140
+ font-size: 14px;
141
+ font-weight: 500;
142
+ }
143
+
144
+ .cancel-btn:hover {
145
+ background: #757575;
146
+ }
147
+
148
+ .loading {
149
+ text-align: center;
150
+ padding: 40px;
151
+ font-size: 14px;
152
+ color: #666;
153
+ }
154
+
155
+ .error {
156
+ background: #ffebee;
157
+ border: 1px solid #f44336;
158
+ padding: 15px;
159
+ border-radius: 4px;
160
+ color: #c62828;
161
+ font-size: 14px;
162
+ margin-bottom: 20px;
163
+ }
164
+
165
+ .spinner {
166
+ border: 3px solid #f3f3f3;
167
+ border-top: 3px solid #2196F3;
168
+ border-radius: 50%;
169
+ width: 30px;
170
+ height: 30px;
171
+ animation: spin 1s linear infinite;
172
+ margin: 20px auto;
173
+ }
174
+
175
+ @keyframes spin {
176
+ 0% { transform: rotate(0deg); }
177
+ 100% { transform: rotate(360deg); }
178
+ }
179
+ </style>
180
+ </head>
181
+ <body>
182
+ <h2>⚠️ Activation Limit Reached</h2>
183
+ <p class="subtitle">Please deactivate an existing activation to continue.</p>
184
+
185
+ <div id="error-message" class="error" style="display: none;"></div>
186
+
187
+ <div id="limit-info" class="limit-info" style="display: none;">
188
+ <strong>License Status:</strong> <span id="activation-status">Loading...</span>
189
+ </div>
190
+
191
+ <div id="loading" class="loading">
192
+ <div class="spinner"></div>
193
+ <p>Loading activations...</p>
194
+ </div>
195
+
196
+ <div id="activations-container" style="display: none;">
197
+ <div class="activations-list" id="activations-list">
198
+ <!-- Activations will be populated here -->
199
+ </div>
200
+
201
+ <div class="actions">
202
+ <button class="cancel-btn" id="cancel-btn">Cancel</button>
203
+ </div>
204
+ </div>
205
+
206
+ <script type="module" src="activation-manager.js"></script>
207
+ </body>
208
+ </html>
@@ -0,0 +1,187 @@
1
+ // Activation Manager for Chrome Debug Extension
2
+ import { FirebaseLicenseClient } from './firebase-client.js';
3
+
4
+ const licenseClient = new FirebaseLicenseClient();
5
+
6
+ let licenseKey = '';
7
+ let currentInstanceId = '';
8
+ let activations = [];
9
+
10
+ // Initialize on page load
11
+ document.addEventListener('DOMContentLoaded', async () => {
12
+ await loadActivations();
13
+
14
+ // Set up event listeners
15
+ document.getElementById('cancel-btn').addEventListener('click', () => {
16
+ window.close();
17
+ });
18
+ });
19
+
20
+ /**
21
+ * Load activations from storage and Firebase
22
+ */
23
+ async function loadActivations() {
24
+ try {
25
+ // Get license key and instance ID from storage
26
+ const stored = await chrome.storage.local.get(['ls_license_key', 'ls_instance_id', 'chromedebug_instance_id']);
27
+ licenseKey = stored.ls_license_key;
28
+ currentInstanceId = stored.chromedebug_instance_id;
29
+
30
+ if (!licenseKey) {
31
+ showError('License key not found. Please activate your license first.');
32
+ return;
33
+ }
34
+
35
+ // Fetch activations from Firebase
36
+ const data = await licenseClient.listActivations(licenseKey);
37
+ activations = data.activations || [];
38
+
39
+ // Show activation status
40
+ document.getElementById('activation-status').textContent =
41
+ `${data.activationUsage} of ${data.activationLimit} activations used`;
42
+ document.getElementById('limit-info').style.display = 'block';
43
+
44
+ // Hide loading, show activations
45
+ document.getElementById('loading').style.display = 'none';
46
+ document.getElementById('activations-container').style.display = 'block';
47
+
48
+ // Render activations list
49
+ renderActivations();
50
+
51
+ } catch (error) {
52
+ console.error('[Activation Manager] Error loading activations:', error);
53
+ showError(`Failed to load activations: ${error.message}`);
54
+ document.getElementById('loading').style.display = 'none';
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Render the list of activations
60
+ */
61
+ function renderActivations() {
62
+ const container = document.getElementById('activations-list');
63
+ container.innerHTML = '';
64
+
65
+ if (activations.length === 0) {
66
+ container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No activations found.</div>';
67
+ return;
68
+ }
69
+
70
+ activations.forEach((activation, index) => {
71
+ const item = document.createElement('div');
72
+ item.className = 'activation-item';
73
+
74
+ // Check if this is the current device
75
+ const isCurrentDevice = activation.name === currentInstanceId ||
76
+ activation.identifier === currentInstanceId;
77
+
78
+ if (isCurrentDevice) {
79
+ item.classList.add('current-device');
80
+ }
81
+
82
+ // Format date
83
+ const date = new Date(activation.createdAt);
84
+ const formattedDate = date.toLocaleString('en-US', {
85
+ month: 'short',
86
+ day: 'numeric',
87
+ year: 'numeric',
88
+ hour: '2-digit',
89
+ minute: '2-digit'
90
+ });
91
+
92
+ // Build device info
93
+ let deviceDisplay = 'Unknown Device';
94
+ if (activation.deviceInfo) {
95
+ deviceDisplay = activation.deviceInfo.deviceName ||
96
+ `${activation.deviceInfo.platform || 'Unknown'} • ${activation.deviceInfo.browser || 'Unknown'}`;
97
+ } else if (activation.name && activation.name !== currentInstanceId) {
98
+ deviceDisplay = activation.name;
99
+ }
100
+
101
+ item.innerHTML = `
102
+ <div class="activation-header">
103
+ <span class="activation-number">Activation ${index + 1}</span>
104
+ ${isCurrentDevice ? '<span class="current-badge">CURRENT DEVICE</span>' : ''}
105
+ </div>
106
+ <div class="device-info">
107
+ <strong>${deviceDisplay}</strong>
108
+ </div>
109
+ <div class="activation-date">
110
+ Activated on ${formattedDate}
111
+ </div>
112
+ <button
113
+ class="deactivate-btn"
114
+ data-instance-id="${activation.identifier}"
115
+ ${isCurrentDevice ? 'disabled title="Cannot deactivate current device"' : ''}
116
+ >
117
+ ${isCurrentDevice ? '✓ Current Device' : 'Deactivate'}
118
+ </button>
119
+ `;
120
+
121
+ // Add click handler for deactivate button
122
+ if (!isCurrentDevice) {
123
+ const deactivateBtn = item.querySelector('.deactivate-btn');
124
+ deactivateBtn.addEventListener('click', () => {
125
+ handleDeactivate(activation.identifier, index + 1);
126
+ });
127
+ }
128
+
129
+ container.appendChild(item);
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Handle deactivation of an instance
135
+ */
136
+ async function handleDeactivate(instanceId, activationNumber) {
137
+ const confirmed = confirm(
138
+ `Are you sure you want to deactivate Activation ${activationNumber}?\n\n` +
139
+ `This will free up an activation slot, allowing you to activate on this device.`
140
+ );
141
+
142
+ if (!confirmed) {
143
+ return;
144
+ }
145
+
146
+ try {
147
+ // Disable all deactivate buttons immediately
148
+ const buttons = document.querySelectorAll('.deactivate-btn');
149
+ buttons.forEach(btn => btn.disabled = true);
150
+
151
+ // Call deactivate API
152
+ const result = await licenseClient.deactivateInstance(licenseKey, instanceId);
153
+
154
+ if (result.deactivated) {
155
+ // Show success message
156
+ alert('Instance deactivated successfully! Click OK to retry activation.');
157
+
158
+ // Close this window and retry activation
159
+ // Send message to popup to retry activation
160
+ chrome.runtime.sendMessage({
161
+ type: 'RETRY_ACTIVATION',
162
+ licenseKey: licenseKey
163
+ });
164
+
165
+ window.close();
166
+ } else {
167
+ throw new Error('Deactivation failed');
168
+ }
169
+
170
+ } catch (error) {
171
+ console.error('[Activation Manager] Deactivation error:', error);
172
+ showError(`Failed to deactivate: ${error.message}`);
173
+
174
+ // Re-enable buttons
175
+ const buttons = document.querySelectorAll('.deactivate-btn:not([data-current])');
176
+ buttons.forEach(btn => btn.disabled = false);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Show error message
182
+ */
183
+ function showError(message) {
184
+ const errorDiv = document.getElementById('error-message');
185
+ errorDiv.textContent = message;
186
+ errorDiv.style.display = 'block';
187
+ }
@@ -29,6 +29,68 @@ class FirebaseLicenseClient {
29
29
  return instanceId;
30
30
  }
31
31
 
32
+ /**
33
+ * Collect device context information for activation tracking
34
+ * @returns {Object} Device information
35
+ */
36
+ getDeviceInfo() {
37
+ const ua = navigator.userAgent;
38
+ let browser = "Unknown";
39
+ let browserVersion = "Unknown";
40
+ let platform = "Unknown";
41
+
42
+ // Detect browser
43
+ if (ua.indexOf("Chrome") > -1 && ua.indexOf("Edg") === -1) {
44
+ browser = "Chrome";
45
+ const match = ua.match(/Chrome\/(\d+)/);
46
+ browserVersion = match ? match[1] : "Unknown";
47
+ } else if (ua.indexOf("Edg") > -1) {
48
+ browser = "Edge";
49
+ const match = ua.match(/Edg\/(\d+)/);
50
+ browserVersion = match ? match[1] : "Unknown";
51
+ } else if (ua.indexOf("Firefox") > -1) {
52
+ browser = "Firefox";
53
+ const match = ua.match(/Firefox\/(\d+)/);
54
+ browserVersion = match ? match[1] : "Unknown";
55
+ } else if (ua.indexOf("Safari") > -1) {
56
+ browser = "Safari";
57
+ const match = ua.match(/Version\/(\d+)/);
58
+ browserVersion = match ? match[1] : "Unknown";
59
+ }
60
+
61
+ // Detect platform
62
+ if (ua.indexOf("Win") > -1) {
63
+ platform = "Windows";
64
+ // Try to extract Windows version
65
+ if (ua.indexOf("Windows NT 10.0") > -1) platform = "Windows 10/11";
66
+ else if (ua.indexOf("Windows NT 6.3") > -1) platform = "Windows 8.1";
67
+ else if (ua.indexOf("Windows NT 6.2") > -1) platform = "Windows 8";
68
+ else if (ua.indexOf("Windows NT 6.1") > -1) platform = "Windows 7";
69
+ } else if (ua.indexOf("Mac") > -1) {
70
+ platform = "macOS";
71
+ // Try to extract macOS version from user agent
72
+ const match = ua.match(/Mac OS X (\d+[._]\d+)/);
73
+ if (match) {
74
+ const version = match[1].replace('_', '.');
75
+ platform = `macOS ${version}`;
76
+ }
77
+ } else if (ua.indexOf("Linux") > -1) {
78
+ platform = "Linux";
79
+ } else if (ua.indexOf("CrOS") > -1) {
80
+ platform = "Chrome OS";
81
+ }
82
+
83
+ return {
84
+ platform,
85
+ browser,
86
+ browserVersion,
87
+ userAgent: ua,
88
+ screenResolution: `${screen.width}x${screen.height}`,
89
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
90
+ language: navigator.language
91
+ };
92
+ }
93
+
32
94
  /**
33
95
  * Activate license with LemonSqueezy (first-time activation)
34
96
  * @param {string} licenseKey - License key to activate
@@ -36,16 +98,24 @@ class FirebaseLicenseClient {
36
98
  */
37
99
  async activateLicense(licenseKey) {
38
100
  const deviceId = await this.generateInstanceId();
101
+ const deviceInfo = this.getDeviceInfo();
39
102
 
40
103
  try {
41
104
  console.log(`[License] Activating license ${licenseKey} with device ${deviceId}`);
105
+ console.log('[License] Device info:', deviceInfo);
42
106
 
43
107
  const response = await fetch(`${this.functionsUrl}/activateLicense`, {
44
108
  method: 'POST',
45
109
  headers: {'Content-Type': 'application/json'},
46
110
  body: JSON.stringify({
47
111
  licenseKey: licenseKey.trim(),
48
- instanceName: deviceId
112
+ instanceName: deviceId,
113
+ deviceInfo: {
114
+ platform: deviceInfo.platform,
115
+ browser: deviceInfo.browser,
116
+ browserVersion: deviceInfo.browserVersion,
117
+ deviceName: `${deviceInfo.platform} • ${deviceInfo.browser} ${deviceInfo.browserVersion}`
118
+ }
49
119
  })
50
120
  });
51
121
 
@@ -273,6 +343,70 @@ class FirebaseLicenseClient {
273
343
  async clearLicenseCache() {
274
344
  await chrome.storage.local.remove(this.cacheKey);
275
345
  }
346
+
347
+ /**
348
+ * List all activations for a license key
349
+ * @param {string} licenseKey - License key
350
+ * @returns {Promise<{activations: Array, activationLimit: number, activationUsage: number, availableSlots: number}>}
351
+ */
352
+ async listActivations(licenseKey) {
353
+ try {
354
+ console.log(`[License] Listing activations for ${licenseKey}`);
355
+
356
+ const response = await fetch(`${this.functionsUrl}/listActivations`, {
357
+ method: 'POST',
358
+ headers: {'Content-Type': 'application/json'},
359
+ body: JSON.stringify({ licenseKey: licenseKey.trim() })
360
+ });
361
+
362
+ if (!response.ok) {
363
+ const errorData = await response.json();
364
+ throw new Error(errorData.error || `HTTP ${response.status}`);
365
+ }
366
+
367
+ const data = await response.json();
368
+ console.log('[License] Activations:', data);
369
+
370
+ return data;
371
+ } catch (error) {
372
+ console.error('[License] Failed to list activations:', error);
373
+ throw error;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Deactivate a specific license instance
379
+ * @param {string} licenseKey - License key
380
+ * @param {string} instanceId - Instance ID to deactivate
381
+ * @returns {Promise<{deactivated: boolean, message?: string}>}
382
+ */
383
+ async deactivateInstance(licenseKey, instanceId) {
384
+ try {
385
+ console.log(`[License] Deactivating instance ${instanceId}`);
386
+
387
+ const response = await fetch(`${this.functionsUrl}/deactivateInstance`, {
388
+ method: 'POST',
389
+ headers: {'Content-Type': 'application/json'},
390
+ body: JSON.stringify({
391
+ licenseKey: licenseKey.trim(),
392
+ instanceId
393
+ })
394
+ });
395
+
396
+ if (!response.ok) {
397
+ const errorData = await response.json();
398
+ throw new Error(errorData.error || `HTTP ${response.status}`);
399
+ }
400
+
401
+ const data = await response.json();
402
+ console.log('[License] Deactivation result:', data);
403
+
404
+ return data;
405
+ } catch (error) {
406
+ console.error('[License] Failed to deactivate instance:', error);
407
+ throw error;
408
+ }
409
+ }
276
410
  }
277
411
 
278
412
  export { FirebaseLicenseClient };
@@ -880,6 +880,8 @@ async function handleLicenseActivation() {
880
880
 
881
881
  const result = await licenseClient.validateLicense(licenseKey);
882
882
  console.log('[License] Validation result:', result);
883
+ console.log('[License] Error value:', result.error);
884
+ console.log('[License] Error type:', typeof result.error);
883
885
 
884
886
  if (result.valid) {
885
887
  messageDiv.textContent = 'License activated successfully!';
@@ -887,8 +889,54 @@ async function handleLicenseActivation() {
887
889
  document.getElementById('license-key-input').value = '';
888
890
  await initializeLicenseUI(); // Refresh UI
889
891
  } else {
890
- messageDiv.textContent = result.error || 'Invalid license key';
891
- messageDiv.style.color = '#f44336';
892
+ // Check if activation limit reached
893
+ console.log('[License] Checking if activation limit reached...');
894
+ console.log('[License] result.error === "ACTIVATION_LIMIT_REACHED":', result.error === 'ACTIVATION_LIMIT_REACHED');
895
+ console.log('[License] result.error includes "activation limit":', result.error && result.error.toLowerCase().includes('activation limit'));
896
+
897
+ if (result.error === 'ACTIVATION_LIMIT_REACHED' ||
898
+ (result.error && result.error.toLowerCase().includes('activation limit'))) {
899
+ console.log('[License] Activation limit reached, opening activation manager');
900
+ messageDiv.textContent = 'Opening activation manager...';
901
+ messageDiv.style.color = '#ff9800';
902
+
903
+ // Store the license key temporarily so activation manager can access it
904
+ await chrome.storage.local.set({
905
+ 'ls_license_key': licenseKey.trim(),
906
+ 'activation_manager_open': true
907
+ });
908
+
909
+ // Open activation manager in a new window
910
+ const width = 500;
911
+ const height = 600;
912
+ const left = Math.round((screen.width - width) / 2);
913
+ const top = Math.round((screen.height - height) / 2);
914
+
915
+ const activationManagerUrl = chrome.runtime.getURL('activation-manager.html');
916
+ console.log('[License] Opening activation manager at URL:', activationManagerUrl);
917
+
918
+ chrome.windows.create({
919
+ url: activationManagerUrl,
920
+ type: 'popup',
921
+ width: width,
922
+ height: height,
923
+ left: left,
924
+ top: top
925
+ }, (window) => {
926
+ if (chrome.runtime.lastError) {
927
+ console.error('[License] Failed to open activation manager:', chrome.runtime.lastError);
928
+ messageDiv.textContent = 'Error: Could not open activation manager. See console.';
929
+ messageDiv.style.color = '#f44336';
930
+ } else {
931
+ console.log('[License] Activation manager window created:', window);
932
+ messageDiv.textContent = 'Please deactivate an existing activation to continue';
933
+ messageDiv.style.color = '#ff9800';
934
+ }
935
+ });
936
+ } else {
937
+ messageDiv.textContent = result.error || 'Invalid license key';
938
+ messageDiv.style.color = '#f44336';
939
+ }
892
940
  }
893
941
  }
894
942
 
@@ -898,6 +946,24 @@ function handleUpgradeClick() {
898
946
  chrome.tabs.create({url: LEMONSQUEEZY_CHECKOUT_URL});
899
947
  }
900
948
 
949
+ // Listen for messages from activation manager
950
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
951
+ if (message.type === 'RETRY_ACTIVATION' && message.licenseKey) {
952
+ console.log('[License] Retrying activation after deactivation');
953
+
954
+ // Set the license key in the input
955
+ const licenseKeyInput = document.getElementById('license-key-input');
956
+ if (licenseKeyInput) {
957
+ licenseKeyInput.value = message.licenseKey;
958
+ }
959
+
960
+ // Retry activation
961
+ handleLicenseActivation().then(() => {
962
+ console.log('[License] Retry activation complete');
963
+ });
964
+ }
965
+ });
966
+
901
967
  document.addEventListener('DOMContentLoaded', () => {
902
968
  console.log('DOM loaded, initializing popup');
903
969
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamicu/chromedebug-mcp",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "ChromeDebug MCP - MCP server that provides full control over a Chrome browser instance for debugging and automation with AI assistants like Claude Code",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -37,6 +37,7 @@ module.exports = {
37
37
  { from: 'chrome-extension/popup.html', to: 'popup.html' },
38
38
  { from: 'chrome-extension/options.html', to: 'options.html' },
39
39
  { from: 'chrome-extension/offscreen.html', to: 'offscreen.html' },
40
+ { from: 'chrome-extension/activation-manager.html', to: 'activation-manager.html' },
40
41
 
41
42
  // Copy icons
42
43
  { from: 'chrome-extension/*.png', to: '[name][ext]' },
@@ -60,6 +61,8 @@ module.exports = {
60
61
  { from: 'chrome-extension/license-helper.js', to: 'license-helper.js' },
61
62
  { from: 'chrome-extension/firebase-client.js', to: 'firebase-client.js' },
62
63
  { from: 'chrome-extension/firebase-config.js', to: 'firebase-config.js' },
64
+ { from: 'chrome-extension/firebase-config.module.js', to: 'firebase-config.module.js' },
65
+ { from: 'chrome-extension/activation-manager.js', to: 'activation-manager.js' },
63
66
  { from: 'chrome-extension/frame-capture.js', to: 'frame-capture.js' },
64
67
  { from: 'chrome-extension/chrome-session-manager.js', to: 'chrome-session-manager.js' },
65
68
 
@@ -38,6 +38,7 @@ module.exports = {
38
38
  { from: 'chrome-extension/popup.html', to: 'popup.html' },
39
39
  { from: 'chrome-extension/options.html', to: 'options.html' },
40
40
  { from: 'chrome-extension/offscreen.html', to: 'offscreen.html' },
41
+ { from: 'chrome-extension/activation-manager.html', to: 'activation-manager.html' },
41
42
  { from: 'chrome-extension/pro/frame-editor.html', to: 'pro/frame-editor.html' },
42
43
 
43
44
  // Copy icons
@@ -62,6 +63,8 @@ module.exports = {
62
63
  { from: 'chrome-extension/license-helper.js', to: 'license-helper.js' },
63
64
  { from: 'chrome-extension/firebase-client.js', to: 'firebase-client.js' },
64
65
  { from: 'chrome-extension/firebase-config.js', to: 'firebase-config.js' },
66
+ { from: 'chrome-extension/firebase-config.module.js', to: 'firebase-config.module.js' },
67
+ { from: 'chrome-extension/activation-manager.js', to: 'activation-manager.js' },
65
68
  { from: 'chrome-extension/frame-capture.js', to: 'frame-capture.js' },
66
69
  { from: 'chrome-extension/chrome-session-manager.js', to: 'chrome-session-manager.js' },
67
70