vg-coder-cli 2.0.32 → 2.0.34
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/package.json +2 -2
- package/.vgignore +0 -10
- package/ARCHITECTURE.md +0 -255
- package/change.sh +0 -0
- package/gulpfile.js +0 -111
- package/vetgo-auto/README.md +0 -3
- package/vetgo-auto/chrome/CSP_IMPROVEMENTS.md +0 -147
- package/vetgo-auto/chrome/MANIFEST_V3_MIGRATION.md +0 -123
- package/vetgo-auto/chrome/assets/icon128.png +0 -0
- package/vetgo-auto/chrome/assets/icon16.png +0 -0
- package/vetgo-auto/chrome/assets/icon48.png +0 -0
- package/vetgo-auto/chrome/environments/environment.ts +0 -13
- package/vetgo-auto/chrome/manifest.json +0 -66
- package/vetgo-auto/chrome/rules.json +0 -23
- package/vetgo-auto/chrome/src/background.ts +0 -200
- package/vetgo-auto/chrome/src/controller.ts +0 -172
- package/vetgo-auto/chrome/src/controllers/common.firebase.ts +0 -31
- package/vetgo-auto/chrome/src/controllers/firebase-crud.ts +0 -147
- package/vetgo-auto/chrome/src/controllers/load-common-fuc.controller.ts +0 -24
- package/vetgo-auto/chrome/src/controllers/load-script.controller.ts +0 -23
- package/vetgo-auto/chrome/src/script-injector.ts +0 -305
- package/vetgo-auto/chrome/src/sidepanel.css +0 -166
- package/vetgo-auto/chrome/src/sidepanel.html +0 -48
- package/vetgo-auto/chrome/src/sidepanel.ts +0 -127
- package/vetgo-auto/chrome/src/utils/ai-domains.ts +0 -33
- package/vetgo-auto/chrome/src/utils/db-utils.ts +0 -2
- package/vetgo-auto/chrome/src/utils/environment-storage.service.ts +0 -85
- package/vetgo-auto/chrome/src/utils/injector-script.ts +0 -47
- package/vetgo-auto/chrome/webpack.config.js +0 -53
- package/vetgo-auto/chrome/webpack.config.prod.js +0 -54
- package/vetgo-auto/package.json +0 -30
- package/vetgo-auto/tsconfig.json +0 -27
- package/vetgo-auto/vg-coder.zip +0 -0
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
// Alternative script injection methods for CSP-restricted environments
|
|
2
|
-
|
|
3
|
-
export class ScriptInjector {
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Detect if site has strict CSP that blocks blob/data URLs
|
|
7
|
-
*/
|
|
8
|
-
private static detectStrictCSP(): boolean {
|
|
9
|
-
try {
|
|
10
|
-
// Check for CSP meta tags
|
|
11
|
-
const cspMetas = document.querySelectorAll('meta[http-equiv="Content-Security-Policy"]');
|
|
12
|
-
for (let i = 0; i < cspMetas.length; i++) {
|
|
13
|
-
const meta = cspMetas[i];
|
|
14
|
-
const content = meta.getAttribute('content') || '';
|
|
15
|
-
if (content.includes('script-src') && !content.includes('blob:') && !content.includes('data:')) {
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Check common strict CSP domains
|
|
21
|
-
const strictDomains = [
|
|
22
|
-
'midjourney.com',
|
|
23
|
-
'openai.com',
|
|
24
|
-
'github.com',
|
|
25
|
-
'google.com',
|
|
26
|
-
'googletagmanager.com',
|
|
27
|
-
'facebook.com',
|
|
28
|
-
'twitter.com',
|
|
29
|
-
'linkedin.com'
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const hostname = window.location.hostname.toLowerCase();
|
|
33
|
-
return strictDomains.some(domain => hostname.includes(domain));
|
|
34
|
-
} catch (error) {
|
|
35
|
-
console.error('CSP detection error:', error);
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Safely get head element or wait for it
|
|
42
|
-
*/
|
|
43
|
-
private static getHeadElement(): Promise<HTMLHeadElement> {
|
|
44
|
-
return new Promise((resolve) => {
|
|
45
|
-
const tryGetHead = () => {
|
|
46
|
-
const head = document.head || document.getElementsByTagName('head')[0];
|
|
47
|
-
if (head) {
|
|
48
|
-
resolve(head as HTMLHeadElement);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// If no head, wait for DOM
|
|
53
|
-
if (document.readyState === 'loading') {
|
|
54
|
-
document.addEventListener('DOMContentLoaded', tryGetHead, { once: true });
|
|
55
|
-
} else {
|
|
56
|
-
// Create head if it doesn't exist
|
|
57
|
-
const newHead = document.createElement('head');
|
|
58
|
-
if (document.documentElement) {
|
|
59
|
-
document.documentElement.appendChild(newHead);
|
|
60
|
-
resolve(newHead);
|
|
61
|
-
} else {
|
|
62
|
-
// Last resort: wait a bit and try again
|
|
63
|
-
setTimeout(tryGetHead, 100);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
tryGetHead();
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Method 1: Blob URL injection (works in most cases)
|
|
74
|
-
*/
|
|
75
|
-
static async injectViaBlob(script: string, actionType: string): Promise<boolean> {
|
|
76
|
-
try {
|
|
77
|
-
const blob = new Blob([script], { type: 'application/javascript' });
|
|
78
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
79
|
-
|
|
80
|
-
const scriptElement = document.createElement('script');
|
|
81
|
-
scriptElement.type = 'text/javascript';
|
|
82
|
-
scriptElement.id = actionType;
|
|
83
|
-
scriptElement.src = blobUrl;
|
|
84
|
-
|
|
85
|
-
return new Promise(async (resolve) => {
|
|
86
|
-
scriptElement.onload = () => {
|
|
87
|
-
URL.revokeObjectURL(blobUrl);
|
|
88
|
-
resolve(true);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
scriptElement.onerror = () => {
|
|
92
|
-
URL.revokeObjectURL(blobUrl);
|
|
93
|
-
resolve(false);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const head = await this.getHeadElement();
|
|
98
|
-
head.appendChild(scriptElement);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error('Failed to get head element:', error);
|
|
101
|
-
URL.revokeObjectURL(blobUrl);
|
|
102
|
-
resolve(false);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
} catch (error) {
|
|
106
|
-
console.error('Blob injection failed:', error);
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Method 2: Data URL injection (fallback)
|
|
113
|
-
*/
|
|
114
|
-
static async injectViaDataUrl(script: string, actionType: string): Promise<boolean> {
|
|
115
|
-
try {
|
|
116
|
-
const dataUrl = 'data:application/javascript;base64,' + btoa(script);
|
|
117
|
-
const scriptElement = document.createElement('script');
|
|
118
|
-
scriptElement.type = 'text/javascript';
|
|
119
|
-
scriptElement.id = actionType + '_data';
|
|
120
|
-
scriptElement.src = dataUrl;
|
|
121
|
-
|
|
122
|
-
return new Promise(async (resolve) => {
|
|
123
|
-
scriptElement.onload = () => resolve(true);
|
|
124
|
-
scriptElement.onerror = () => resolve(false);
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const head = await this.getHeadElement();
|
|
128
|
-
head.appendChild(scriptElement);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error('Failed to get head element for data URL:', error);
|
|
131
|
-
resolve(false);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error('Data URL injection failed:', error);
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Method 3: PostMessage bridge injection
|
|
142
|
-
*/
|
|
143
|
-
static injectViaPostMessage(script: string, actionType: string): Promise<boolean> {
|
|
144
|
-
return new Promise((resolve) => {
|
|
145
|
-
try {
|
|
146
|
-
// Create a bridge script that listens for postMessage
|
|
147
|
-
const bridgeScript = `
|
|
148
|
-
window.addEventListener('message', function(event) {
|
|
149
|
-
if (event.source !== window || !event.data.type || event.data.type !== 'VETGO_SCRIPT_INJECT') {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
const func = new Function(event.data.script);
|
|
154
|
-
func();
|
|
155
|
-
window.postMessage({type: 'VETGO_SCRIPT_SUCCESS', actionType: event.data.actionType}, '*');
|
|
156
|
-
} catch (error) {
|
|
157
|
-
console.error('PostMessage script execution failed:', error);
|
|
158
|
-
window.postMessage({type: 'VETGO_SCRIPT_ERROR', actionType: event.data.actionType, error: error.message}, '*');
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
`;
|
|
162
|
-
|
|
163
|
-
// First inject the bridge
|
|
164
|
-
this.injectViaBlob(bridgeScript, actionType + '_bridge').then((bridgeSuccess) => {
|
|
165
|
-
if (bridgeSuccess) {
|
|
166
|
-
// Listen for response
|
|
167
|
-
const responseHandler = (event: MessageEvent) => {
|
|
168
|
-
if (event.source !== window || !event.data.type) return;
|
|
169
|
-
|
|
170
|
-
if (event.data.type === 'VETGO_SCRIPT_SUCCESS' && event.data.actionType === actionType) {
|
|
171
|
-
window.removeEventListener('message', responseHandler);
|
|
172
|
-
resolve(true);
|
|
173
|
-
} else if (event.data.type === 'VETGO_SCRIPT_ERROR' && event.data.actionType === actionType) {
|
|
174
|
-
window.removeEventListener('message', responseHandler);
|
|
175
|
-
resolve(false);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
window.addEventListener('message', responseHandler);
|
|
180
|
-
|
|
181
|
-
// Send script via postMessage
|
|
182
|
-
window.postMessage({
|
|
183
|
-
type: 'VETGO_SCRIPT_INJECT',
|
|
184
|
-
script: script,
|
|
185
|
-
actionType: actionType
|
|
186
|
-
}, '*');
|
|
187
|
-
|
|
188
|
-
// Timeout after 5 seconds
|
|
189
|
-
setTimeout(() => {
|
|
190
|
-
window.removeEventListener('message', responseHandler);
|
|
191
|
-
resolve(false);
|
|
192
|
-
}, 5000);
|
|
193
|
-
} else {
|
|
194
|
-
resolve(false);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.error('PostMessage injection failed:', error);
|
|
199
|
-
resolve(false);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Method 4: Background script injection via chrome.scripting
|
|
206
|
-
*/
|
|
207
|
-
static injectViaBackground(script: string, actionType: string): Promise<boolean> {
|
|
208
|
-
return new Promise((resolve) => {
|
|
209
|
-
try {
|
|
210
|
-
chrome.runtime.sendMessage({
|
|
211
|
-
action: "INJECT_SCRIPT",
|
|
212
|
-
script: script,
|
|
213
|
-
actionType: actionType
|
|
214
|
-
}, (response) => {
|
|
215
|
-
if (chrome.runtime.lastError) {
|
|
216
|
-
console.error('Background injection failed:', chrome.runtime.lastError);
|
|
217
|
-
resolve(false);
|
|
218
|
-
} else {
|
|
219
|
-
resolve(response && response.success);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
} catch (error) {
|
|
223
|
-
console.error('Background script injection failed:', error);
|
|
224
|
-
resolve(false);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Method 5: Direct eval in content script context (last resort)
|
|
231
|
-
*/
|
|
232
|
-
static injectViaEval(script: string, actionType: string): Promise<boolean> {
|
|
233
|
-
return new Promise((resolve) => {
|
|
234
|
-
try {
|
|
235
|
-
console.log(`Attempting direct eval for ${actionType}`);
|
|
236
|
-
|
|
237
|
-
// Create isolated function to avoid polluting global scope
|
|
238
|
-
const isolatedExecution = new Function('script', `
|
|
239
|
-
try {
|
|
240
|
-
${script}
|
|
241
|
-
return true;
|
|
242
|
-
} catch (error) {
|
|
243
|
-
console.error('Eval execution error:', error);
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
`);
|
|
247
|
-
|
|
248
|
-
const success = isolatedExecution(script);
|
|
249
|
-
resolve(success);
|
|
250
|
-
} catch (error) {
|
|
251
|
-
console.error('Eval injection failed:', error);
|
|
252
|
-
resolve(false);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Try all methods in smart order based on CSP detection
|
|
259
|
-
*/
|
|
260
|
-
static async injectScript(script: string, actionType: string): Promise<boolean> {
|
|
261
|
-
const hasStrictCSP = this.detectStrictCSP();
|
|
262
|
-
|
|
263
|
-
// Smart method ordering based on CSP detection
|
|
264
|
-
const methods = hasStrictCSP ? [
|
|
265
|
-
// For strict CSP sites, try background injection first
|
|
266
|
-
() => this.injectViaBackground(script, actionType),
|
|
267
|
-
() => this.injectViaEval(script, actionType),
|
|
268
|
-
() => this.injectViaPostMessage(script, actionType),
|
|
269
|
-
() => this.injectViaBlob(script, actionType),
|
|
270
|
-
() => this.injectViaDataUrl(script, actionType)
|
|
271
|
-
] : [
|
|
272
|
-
// For normal sites, try blob first (fastest)
|
|
273
|
-
() => this.injectViaBlob(script, actionType),
|
|
274
|
-
() => this.injectViaDataUrl(script, actionType),
|
|
275
|
-
() => this.injectViaBackground(script, actionType),
|
|
276
|
-
() => this.injectViaEval(script, actionType),
|
|
277
|
-
() => this.injectViaPostMessage(script, actionType)
|
|
278
|
-
];
|
|
279
|
-
|
|
280
|
-
console.log(`Injecting script for ${actionType} (Strict CSP: ${hasStrictCSP})`);
|
|
281
|
-
|
|
282
|
-
for (let i = 0; i < methods.length; i++) {
|
|
283
|
-
const method = methods[i];
|
|
284
|
-
const methodName = hasStrictCSP ?
|
|
285
|
-
['Background', 'Eval', 'PostMessage', 'Blob', 'DataURL'][i] :
|
|
286
|
-
['Blob', 'DataURL', 'Background', 'Eval', 'PostMessage'][i];
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
console.log(`Trying ${methodName} injection...`);
|
|
290
|
-
const success = await method();
|
|
291
|
-
if (success) {
|
|
292
|
-
console.log(`✅ Script injection successful via ${methodName} for ${actionType}`);
|
|
293
|
-
return true;
|
|
294
|
-
} else {
|
|
295
|
-
console.log(`❌ ${methodName} injection failed for ${actionType}`);
|
|
296
|
-
}
|
|
297
|
-
} catch (error) {
|
|
298
|
-
console.error(`❌ ${methodName} injection error:`, error);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
console.error(`❌ All injection methods failed for ${actionType}`);
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
* {
|
|
2
|
-
margin: 0;
|
|
3
|
-
padding: 0;
|
|
4
|
-
box-sizing: border-box;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
body {
|
|
8
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
9
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
10
|
-
min-height: 100vh;
|
|
11
|
-
display: flex;
|
|
12
|
-
align-items: flex-start;
|
|
13
|
-
justify-content: center;
|
|
14
|
-
padding: 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.container {
|
|
18
|
-
background: white;
|
|
19
|
-
border-radius: 0;
|
|
20
|
-
box-shadow: none;
|
|
21
|
-
padding: 24px;
|
|
22
|
-
width: 100%;
|
|
23
|
-
min-height: 100vh;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
h1 {
|
|
27
|
-
color: #333;
|
|
28
|
-
font-size: 24px;
|
|
29
|
-
margin-bottom: 8px;
|
|
30
|
-
font-weight: 600;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.subtitle {
|
|
34
|
-
color: #666;
|
|
35
|
-
font-size: 13px;
|
|
36
|
-
margin-bottom: 24px;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.form-group {
|
|
40
|
-
margin-bottom: 20px;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
label {
|
|
44
|
-
display: block;
|
|
45
|
-
color: #444;
|
|
46
|
-
font-size: 13px;
|
|
47
|
-
font-weight: 500;
|
|
48
|
-
margin-bottom: 6px;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
input[type="text"] {
|
|
52
|
-
width: 100%;
|
|
53
|
-
padding: 10px 12px;
|
|
54
|
-
border: 2px solid #e0e0e0;
|
|
55
|
-
border-radius: 6px;
|
|
56
|
-
font-size: 14px;
|
|
57
|
-
transition: all 0.3s ease;
|
|
58
|
-
outline: none;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
input[type="text"]:focus {
|
|
62
|
-
border-color: #667eea;
|
|
63
|
-
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.hint {
|
|
67
|
-
color: #888;
|
|
68
|
-
font-size: 11px;
|
|
69
|
-
margin-top: 4px;
|
|
70
|
-
font-style: italic;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.button-group {
|
|
74
|
-
display: flex;
|
|
75
|
-
gap: 10px;
|
|
76
|
-
margin-top: 24px;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
button {
|
|
80
|
-
flex: 1;
|
|
81
|
-
padding: 10px 20px;
|
|
82
|
-
border: none;
|
|
83
|
-
border-radius: 6px;
|
|
84
|
-
font-size: 14px;
|
|
85
|
-
font-weight: 600;
|
|
86
|
-
cursor: pointer;
|
|
87
|
-
transition: all 0.3s ease;
|
|
88
|
-
outline: none;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.btn-primary {
|
|
92
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
93
|
-
color: white;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.btn-primary:hover {
|
|
97
|
-
transform: translateY(-1px);
|
|
98
|
-
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.btn-primary:active {
|
|
102
|
-
transform: translateY(0);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.btn-secondary {
|
|
106
|
-
background: #f5f5f5;
|
|
107
|
-
color: #666;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.btn-secondary:hover {
|
|
111
|
-
background: #e0e0e0;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.message {
|
|
115
|
-
padding: 10px 14px;
|
|
116
|
-
border-radius: 6px;
|
|
117
|
-
margin-bottom: 16px;
|
|
118
|
-
font-size: 13px;
|
|
119
|
-
display: none;
|
|
120
|
-
animation: slideIn 0.3s ease;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
@keyframes slideIn {
|
|
124
|
-
from {
|
|
125
|
-
opacity: 0;
|
|
126
|
-
transform: translateY(-10px);
|
|
127
|
-
}
|
|
128
|
-
to {
|
|
129
|
-
opacity: 1;
|
|
130
|
-
transform: translateY(0);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.message.success {
|
|
135
|
-
background: #d4edda;
|
|
136
|
-
color: #155724;
|
|
137
|
-
border: 1px solid #c3e6cb;
|
|
138
|
-
display: block;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.message.error {
|
|
142
|
-
background: #f8d7da;
|
|
143
|
-
color: #721c24;
|
|
144
|
-
border: 1px solid #f5c6cb;
|
|
145
|
-
display: block;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.current-value {
|
|
149
|
-
background: #f8f9fa;
|
|
150
|
-
padding: 10px 14px;
|
|
151
|
-
border-radius: 6px;
|
|
152
|
-
margin-bottom: 16px;
|
|
153
|
-
border-left: 4px solid #667eea;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.current-value strong {
|
|
157
|
-
color: #667eea;
|
|
158
|
-
font-weight: 600;
|
|
159
|
-
font-size: 13px;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.current-value span {
|
|
163
|
-
color: #333;
|
|
164
|
-
font-family: 'Courier New', monospace;
|
|
165
|
-
font-size: 13px;
|
|
166
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="vi">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>VetGo Pro - Cấu hình</title>
|
|
8
|
-
<link rel="stylesheet" href="sidepanel.css">
|
|
9
|
-
</head>
|
|
10
|
-
|
|
11
|
-
<body>
|
|
12
|
-
<div class="container">
|
|
13
|
-
<h1>⚙️ Cấu hình VetGo Pro</h1>
|
|
14
|
-
<p class="subtitle">Quản lý cài đặt môi trường cho extension</p>
|
|
15
|
-
|
|
16
|
-
<div id="message" class="message"></div>
|
|
17
|
-
|
|
18
|
-
<div class="current-value">
|
|
19
|
-
<strong>Môi trường hiện tại:</strong> <span id="currentEnv">Đang tải...</span>
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<form id="configForm">
|
|
23
|
-
<div class="form-group">
|
|
24
|
-
<label for="environmentName">Tên môi trường (Environment Name)</label>
|
|
25
|
-
<input type="text" id="environmentName" name="environmentName"
|
|
26
|
-
placeholder="Nhập tên môi trường (ví dụ: midjourney, production, test)" required />
|
|
27
|
-
<p class="hint">Tên môi trường sẽ được sử dụng để load scripts từ Firebase: ENV/{environmentName}/script</p>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<div class="form-group">
|
|
31
|
-
<label for="firebaseConfig">Cấu hình Firebase (JSON)</label>
|
|
32
|
-
<textarea id="firebaseConfig" name="firebaseConfig" rows="8"
|
|
33
|
-
placeholder='{"apiKey": "...", "authDomain": "...", "databaseURL": "..."}'
|
|
34
|
-
style="width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-family: monospace; font-size: 13px;"></textarea>
|
|
35
|
-
<p class="hint">Để trống để sử dụng cấu hình mặc định của hệ thống. Nhập JSON object chứa apiKey, authDomain, databaseURL...</p>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<div class="button-group">
|
|
39
|
-
<button type="submit" class="btn-primary">💾 Lưu cấu hình</button>
|
|
40
|
-
<button type="button" id="resetBtn" class="btn-secondary">🔄 Đặt lại mặc định</button>
|
|
41
|
-
</div>
|
|
42
|
-
</form>
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<script src="sidepanel.js"></script>
|
|
46
|
-
</body>
|
|
47
|
-
|
|
48
|
-
</html>
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { EnvironmentStorageService } from './utils/environment-storage.service';
|
|
2
|
-
import { environment } from '../environments/environment';
|
|
3
|
-
|
|
4
|
-
// DOM Elements
|
|
5
|
-
const form = document.getElementById('configForm') as HTMLFormElement;
|
|
6
|
-
const environmentInput = document.getElementById('environmentName') as HTMLInputElement;
|
|
7
|
-
const firebaseConfigInput = document.getElementById('firebaseConfig') as HTMLTextAreaElement;
|
|
8
|
-
const resetBtn = document.getElementById('resetBtn') as HTMLButtonElement;
|
|
9
|
-
const messageDiv = document.getElementById('message') as HTMLDivElement;
|
|
10
|
-
const currentEnvSpan = document.getElementById('currentEnv') as HTMLSpanElement;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Hiển thị thông báo cho người dùng
|
|
14
|
-
*/
|
|
15
|
-
function showMessage(text: string, type: 'success' | 'error') {
|
|
16
|
-
messageDiv.textContent = text;
|
|
17
|
-
messageDiv.className = `message ${type}`;
|
|
18
|
-
|
|
19
|
-
// Tự động ẩn thông báo sau 3 giây
|
|
20
|
-
setTimeout(() => {
|
|
21
|
-
messageDiv.className = 'message';
|
|
22
|
-
}, 3000);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Load và hiển thị giá trị hiện tại
|
|
27
|
-
*/
|
|
28
|
-
async function loadCurrentConfig() {
|
|
29
|
-
try {
|
|
30
|
-
// Load Env Name
|
|
31
|
-
const envName = await EnvironmentStorageService.getEnvironmentName();
|
|
32
|
-
currentEnvSpan.textContent = envName;
|
|
33
|
-
environmentInput.value = envName;
|
|
34
|
-
|
|
35
|
-
// Load Firebase Config
|
|
36
|
-
const fbConfig = await EnvironmentStorageService.getFirebaseConfig();
|
|
37
|
-
|
|
38
|
-
// Kiểm tra xem có phải config mặc định không
|
|
39
|
-
const isDefault = JSON.stringify(fbConfig) === JSON.stringify(environment.firebaseConfig);
|
|
40
|
-
|
|
41
|
-
if (!isDefault) {
|
|
42
|
-
firebaseConfigInput.value = JSON.stringify(fbConfig, null, 2);
|
|
43
|
-
} else {
|
|
44
|
-
firebaseConfigInput.value = ''; // Để trống nếu đang dùng default
|
|
45
|
-
firebaseConfigInput.placeholder = `Đang sử dụng mặc định:\n${JSON.stringify(environment.firebaseConfig, null, 2)}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error('Error loading config:', error);
|
|
50
|
-
currentEnvSpan.textContent = 'Lỗi khi tải cấu hình';
|
|
51
|
-
showMessage('Không thể tải cấu hình hiện tại', 'error');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Xử lý sự kiện submit form
|
|
57
|
-
*/
|
|
58
|
-
async function handleSubmit(event: Event) {
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
|
|
61
|
-
const newEnvName = environmentInput.value.trim();
|
|
62
|
-
const firebaseConfigStr = firebaseConfigInput.value.trim();
|
|
63
|
-
|
|
64
|
-
if (!newEnvName) {
|
|
65
|
-
showMessage('Vui lòng nhập tên môi trường', 'error');
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
// 1. Lưu Environment Name
|
|
71
|
-
await EnvironmentStorageService.setEnvironmentName(newEnvName);
|
|
72
|
-
currentEnvSpan.textContent = newEnvName;
|
|
73
|
-
|
|
74
|
-
// 2. Lưu Firebase Config
|
|
75
|
-
if (firebaseConfigStr) {
|
|
76
|
-
try {
|
|
77
|
-
const configObj = JSON.parse(firebaseConfigStr);
|
|
78
|
-
// Validate sơ bộ
|
|
79
|
-
if (!configObj.apiKey || !configObj.databaseURL) {
|
|
80
|
-
throw new Error("Config thiếu apiKey hoặc databaseURL");
|
|
81
|
-
}
|
|
82
|
-
await EnvironmentStorageService.setFirebaseConfig(configObj);
|
|
83
|
-
} catch (e) {
|
|
84
|
-
showMessage('❌ JSON Firebase Config không hợp lệ: ' + (e as Error).message, 'error');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
// Nếu để trống, xóa custom config để dùng default
|
|
89
|
-
await new Promise<void>((resolve) => {
|
|
90
|
-
chrome.storage.sync.remove('firebaseConfig', () => resolve());
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
showMessage('✅ Lưu cấu hình thành công!', 'success');
|
|
95
|
-
// Reload lại hiển thị để update placeholder/value
|
|
96
|
-
loadCurrentConfig();
|
|
97
|
-
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.error('Error saving settings:', error);
|
|
100
|
-
showMessage('❌ Lỗi khi lưu cấu hình: ' + (error as Error).message, 'error');
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Xử lý sự kiện reset về mặc định
|
|
106
|
-
*/
|
|
107
|
-
async function handleReset() {
|
|
108
|
-
if (!confirm('Bạn có chắc muốn đặt lại tất cả về mặc định?')) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
await EnvironmentStorageService.resetToDefault();
|
|
114
|
-
await loadCurrentConfig(); // Reload UI
|
|
115
|
-
showMessage('✅ Đã đặt lại về cấu hình mặc định!', 'success');
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error('Error resetting environment:', error);
|
|
118
|
-
showMessage('❌ Lỗi khi đặt lại cấu hình', 'error');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Event Listeners
|
|
123
|
-
form.addEventListener('submit', handleSubmit);
|
|
124
|
-
resetBtn.addEventListener('click', handleReset);
|
|
125
|
-
|
|
126
|
-
// Load giá trị hiện tại khi trang được mở
|
|
127
|
-
loadCurrentConfig();
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// AI Chat Providers Configuration
|
|
2
|
-
// Domains for automatic VG Coder iframe injection
|
|
3
|
-
|
|
4
|
-
export const AI_DOMAINS = [
|
|
5
|
-
'chat.openai.com', // ChatGPT (old URL)
|
|
6
|
-
'chatgpt.com', // ChatGPT (new URL)
|
|
7
|
-
'gemini.google.com', // Google Gemini
|
|
8
|
-
'aistudio.google.com', // Google AI Studio
|
|
9
|
-
'chat.deepseek.com', // DeepSeek
|
|
10
|
-
'kimi.com', // Kimi AI
|
|
11
|
-
'www.kimi.com', // Kimi AI (www)
|
|
12
|
-
'grok.com', // Grok
|
|
13
|
-
'claude.ai', // Claude (Anthropic)
|
|
14
|
-
'poe.com', // Poe
|
|
15
|
-
'perplexity.ai', // Perplexity
|
|
16
|
-
'www.perplexity.ai', // Perplexity (www)
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Check if domain should have VG Coder iframe injected
|
|
21
|
-
*/
|
|
22
|
-
export function isAIChatDomain(hostname: string): boolean {
|
|
23
|
-
const cleanHostname = hostname.toLowerCase().replace(/^www\./, '');
|
|
24
|
-
console.log('🔍 Checking AI domain:', cleanHostname);
|
|
25
|
-
|
|
26
|
-
const isMatch = AI_DOMAINS.some(domain => {
|
|
27
|
-
const cleanDomain = domain.toLowerCase().replace(/^www\./, '');
|
|
28
|
-
return cleanHostname === cleanDomain || cleanHostname.endsWith('.' + cleanDomain);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
console.log('🎯 AI domain match:', isMatch);
|
|
32
|
-
return isMatch;
|
|
33
|
-
}
|