@dynamicu/chromedebug-mcp 2.3.0 → 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 +50 -0
- package/chrome-extension/activation-manager.html +208 -0
- package/chrome-extension/activation-manager.js +187 -0
- package/chrome-extension/background.js +5 -5
- package/chrome-extension/firebase-client.js +135 -1
- package/chrome-extension/popup.js +68 -2
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +13 -4
- package/scripts/webpack.config.free.cjs +3 -0
- package/scripts/webpack.config.pro.cjs +3 -0
- package/src/chrome-controller.js +107 -106
- package/src/config-loader.js +18 -17
- package/src/database.js +81 -80
- package/src/index.js +35 -24
- package/src/middleware/auth.js +30 -29
- package/src/port-discovery.js +7 -6
- package/src/services/failover-manager.js +19 -18
- package/src/services/project-manager.js +16 -15
- package/src/utils/logger.js +63 -0
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
|
+
}
|
|
@@ -1537,7 +1537,7 @@ async function createUserNotification(priority, message) {
|
|
|
1537
1537
|
|
|
1538
1538
|
const notificationOptions = {
|
|
1539
1539
|
type: 'basic',
|
|
1540
|
-
iconUrl: '
|
|
1540
|
+
iconUrl: chrome.runtime.getURL('icon48.png'),
|
|
1541
1541
|
title: 'Chrome Debug Error',
|
|
1542
1542
|
message: message,
|
|
1543
1543
|
priority: priority === 'CRITICAL' ? 2 : 1,
|
|
@@ -2428,7 +2428,7 @@ async function startRecording(tabId, settings = {}) {
|
|
|
2428
2428
|
// Show notification to user
|
|
2429
2429
|
chrome.notifications.create({
|
|
2430
2430
|
type: 'basic',
|
|
2431
|
-
iconUrl: '
|
|
2431
|
+
iconUrl: chrome.runtime.getURL('icon48.png'),
|
|
2432
2432
|
title: 'Recording Limit Reached',
|
|
2433
2433
|
message: licenseCheck.message || 'Upgrade to Pro for unlimited recordings.',
|
|
2434
2434
|
priority: 2
|
|
@@ -2938,7 +2938,7 @@ async function startWorkflowRecording(tabId, includeLogsInExport, sessionName =
|
|
|
2938
2938
|
// Show notification to user (same pattern as screen recording)
|
|
2939
2939
|
chrome.notifications.create({
|
|
2940
2940
|
type: 'basic',
|
|
2941
|
-
iconUrl: 'icon128.png',
|
|
2941
|
+
iconUrl: chrome.runtime.getURL('icon128.png'),
|
|
2942
2942
|
title: 'Recording Limit Reached',
|
|
2943
2943
|
message: licenseCheck.message || 'Daily limit reached. Upgrade to Pro for unlimited workflow recordings.',
|
|
2944
2944
|
buttons: [{ title: 'Upgrade to Pro' }],
|
|
@@ -3749,7 +3749,7 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
3749
3749
|
// Show user notification about log association failure
|
|
3750
3750
|
chrome.notifications.create({
|
|
3751
3751
|
type: 'basic',
|
|
3752
|
-
iconUrl: '
|
|
3752
|
+
iconUrl: chrome.runtime.getURL('icon48.png'),
|
|
3753
3753
|
title: 'Chrome Debug - Log Association Failed',
|
|
3754
3754
|
message: `${errorDetails} Recording saved but logs may be missing. Check console for details.`,
|
|
3755
3755
|
priority: 1
|
|
@@ -3766,7 +3766,7 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
3766
3766
|
// Show user notification about no logs captured
|
|
3767
3767
|
chrome.notifications.create({
|
|
3768
3768
|
type: 'basic',
|
|
3769
|
-
iconUrl: '
|
|
3769
|
+
iconUrl: chrome.runtime.getURL('icon48.png'),
|
|
3770
3770
|
title: 'Chrome Debug - No Console Logs Captured',
|
|
3771
3771
|
message: 'Recording completed but no console logs were captured. This may be expected if the page had no console activity.',
|
|
3772
3772
|
priority: 0
|
|
@@ -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 };
|