vg-coder-cli 2.0.22 → 2.0.23
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/.vg/tree-state.json +9 -0
- package/package.json +3 -1
- package/scripts/build.js +49 -6
- package/src/server/api-server.js +46 -0
- package/src/server/views/css/structure.css +4 -1
- package/src/server/views/dashboard.css +24 -1
- package/src/server/views/dashboard.html +2 -0
- package/src/server/views/js/api.js +24 -0
- package/src/server/views/js/features/resize.js +57 -0
- package/src/server/views/js/features/structure.js +109 -16
- package/src/server/views/js/main.js +5 -0
- package/src/server/views/vg-coder/background.js +48201 -2
- package/src/server/views/vg-coder/controller.js +496 -1
- package/src/server/views/vg-coder/manifest.json +13 -5
- package/src/server/views/vg-coder/{options.css → sidepanel.css} +34 -32
- package/src/server/views/vg-coder/{options.html → sidepanel.html} +2 -2
- package/src/server/views/vg-coder/sidepanel.js +347 -0
- package/vetgo-auto/README.md +3 -0
- package/vetgo-auto/chrome/CSP_IMPROVEMENTS.md +147 -0
- package/vetgo-auto/chrome/MANIFEST_V3_MIGRATION.md +123 -0
- 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 +13 -0
- package/vetgo-auto/chrome/manifest.json +66 -0
- package/vetgo-auto/chrome/rules.json +23 -0
- package/vetgo-auto/chrome/src/background.ts +200 -0
- package/vetgo-auto/chrome/src/controller.ts +98 -0
- package/vetgo-auto/chrome/src/controllers/common.firebase.ts +31 -0
- package/vetgo-auto/chrome/src/controllers/firebase-crud.ts +147 -0
- package/vetgo-auto/chrome/src/controllers/load-common-fuc.controller.ts +24 -0
- package/vetgo-auto/chrome/src/controllers/load-script.controller.ts +23 -0
- package/vetgo-auto/chrome/src/script-injector.ts +305 -0
- package/vetgo-auto/chrome/src/sidepanel.css +166 -0
- package/vetgo-auto/chrome/src/sidepanel.html +48 -0
- package/vetgo-auto/chrome/src/sidepanel.ts +127 -0
- package/vetgo-auto/chrome/src/utils/db-utils.ts +2 -0
- package/vetgo-auto/chrome/src/utils/environment-storage.service.ts +85 -0
- package/vetgo-auto/chrome/webpack.config.js +53 -0
- package/vetgo-auto/chrome/webpack.config.prod.js +54 -0
- package/vetgo-auto/package.json +30 -0
- package/vetgo-auto/tsconfig.json +27 -0
- package/vg-coder-cli-2.0.23.tgz +0 -0
- package/src/server/views/vg-coder/background.js.LICENSE.txt +0 -118
- package/src/server/views/vg-coder/options.js +0 -1
- package/vg-coder-cli-2.0.21.tgz +0 -0
- package/vg-coder-cli-2.0.22.tgz +0 -0
- package/vg-coder.zip +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import firebase from 'firebase/compat/app';
|
|
2
|
+
import 'firebase/compat/firestore';
|
|
3
|
+
import "firebase/compat/database";
|
|
4
|
+
import { EnvironmentStorageService } from '../utils/environment-storage.service';
|
|
5
|
+
|
|
6
|
+
// Không export trực tiếp biến đã khởi tạo nữa vì cần chờ async config
|
|
7
|
+
// export const FirebaseDatabase = firebase.database();
|
|
8
|
+
|
|
9
|
+
let initializedApp: firebase.app.App = null;
|
|
10
|
+
|
|
11
|
+
export const getFirebaseDatabase = async () => {
|
|
12
|
+
if (!initializedApp) {
|
|
13
|
+
// Lấy config từ storage hoặc mặc định
|
|
14
|
+
const config = await EnvironmentStorageService.getFirebaseConfig();
|
|
15
|
+
|
|
16
|
+
// Kiểm tra xem đã có app nào tên mặc định chưa để tránh lỗi duplicate app
|
|
17
|
+
if (firebase.apps.length === 0) {
|
|
18
|
+
initializedApp = firebase.initializeApp(config);
|
|
19
|
+
} else {
|
|
20
|
+
// Nếu đã có rồi (do reload extension context) thì dùng lại,
|
|
21
|
+
// nhưng nếu config thay đổi thì cần logic phức tạp hơn (xóa app cũ).
|
|
22
|
+
// Ở đây giả định đơn giản là dùng app hiện tại hoặc tái khởi tạo nếu cần.
|
|
23
|
+
// Với extension service worker, biến toàn cục có thể bị reset.
|
|
24
|
+
|
|
25
|
+
// Để an toàn, ta delete app cũ và init lại với config mới nhất
|
|
26
|
+
await firebase.app().delete().catch(() => { });
|
|
27
|
+
initializedApp = firebase.initializeApp(config);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return initializedApp.database();
|
|
31
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as cloneDeep from 'lodash/cloneDeep';
|
|
2
|
+
import { uuid } from "../utils/db-utils";
|
|
3
|
+
interface EntityModel {
|
|
4
|
+
id?: string; // Optional for new entities
|
|
5
|
+
seqNo: number;
|
|
6
|
+
deleted: boolean;
|
|
7
|
+
}
|
|
8
|
+
export function firebaseSerialize<T>(object: T) {
|
|
9
|
+
return cloneDeep(object);
|
|
10
|
+
}
|
|
11
|
+
export class RealtimeDbCrud<T extends EntityModel> {
|
|
12
|
+
constructor(
|
|
13
|
+
protected database: any
|
|
14
|
+
) {
|
|
15
|
+
this.database = database;
|
|
16
|
+
}
|
|
17
|
+
getCurrentTimestamp() {
|
|
18
|
+
return new Date().getTime();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
addAll(list, collectionName: string) {
|
|
22
|
+
const updates = {};
|
|
23
|
+
for (let item of list) {
|
|
24
|
+
if (!item.id) {
|
|
25
|
+
item.id = uuid();
|
|
26
|
+
}
|
|
27
|
+
item = { ...item, seqNo: this.getCurrentTimestamp(), deleted: false };
|
|
28
|
+
updates[`/${collectionName}/${item.id}`] = firebaseSerialize(item);
|
|
29
|
+
}
|
|
30
|
+
return this.database.ref().update(updates).then(() => list);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
list(collectionName: string) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
this.database
|
|
36
|
+
.ref(collectionName)
|
|
37
|
+
.once('value')
|
|
38
|
+
.then((snapshot) => {
|
|
39
|
+
const data = snapshot.val();
|
|
40
|
+
const list = Object.keys(data).map((key) => ({ id: key, ...data[key] }));
|
|
41
|
+
resolve(list);
|
|
42
|
+
})
|
|
43
|
+
.catch((error) => {
|
|
44
|
+
reject(error);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// work okie
|
|
49
|
+
getRT(id: string,collectionName: string, callback: (item: T) => void ) {
|
|
50
|
+
const databaseRef = this.database.ref(`${collectionName}/${id}`);
|
|
51
|
+
databaseRef.on('value', (snapshot) => {
|
|
52
|
+
const data = snapshot.val();
|
|
53
|
+
if (data) {
|
|
54
|
+
callback(data);
|
|
55
|
+
} else {
|
|
56
|
+
callback(null);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// work okie
|
|
61
|
+
get(id: string,collectionName: string) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
this.database
|
|
64
|
+
.ref(`${collectionName}/${id}`)
|
|
65
|
+
.once('value')
|
|
66
|
+
.then((snapshot) => {
|
|
67
|
+
const data = snapshot.val();
|
|
68
|
+
if (data) {
|
|
69
|
+
console.log(data);
|
|
70
|
+
resolve({ id, ...data });
|
|
71
|
+
} else {
|
|
72
|
+
resolve(null);
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.catch((error) => {
|
|
76
|
+
reject(error);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
add(entity, collectionName: string) {
|
|
82
|
+
entity = { ...entity, seqNo: this.getCurrentTimestamp(), deleted: false };
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
if (entity.id) {
|
|
85
|
+
const saveData = firebaseSerialize(entity);
|
|
86
|
+
this.database
|
|
87
|
+
.ref(`${collectionName}/${entity.id}`)
|
|
88
|
+
.set(saveData)
|
|
89
|
+
.then(() => {
|
|
90
|
+
resolve(entity);
|
|
91
|
+
})
|
|
92
|
+
.catch((error) => {
|
|
93
|
+
reject(error);
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
const id = uuid();
|
|
97
|
+
const newEntity = { id, ...entity };
|
|
98
|
+
this.database
|
|
99
|
+
.ref(collectionName)
|
|
100
|
+
.push(firebaseSerialize(newEntity))
|
|
101
|
+
.then(() => {
|
|
102
|
+
resolve(newEntity);
|
|
103
|
+
})
|
|
104
|
+
.catch((error) => {
|
|
105
|
+
reject(error);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
update(entity, collectionName: string) {
|
|
112
|
+
return this.add(entity, collectionName );
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
listBySeq(maxSeqNo, collectionName: string) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
this.database
|
|
118
|
+
.ref(collectionName)
|
|
119
|
+
.orderByChild('seqNo')
|
|
120
|
+
.startAt(maxSeqNo)
|
|
121
|
+
.once('value')
|
|
122
|
+
.then((snapshot) => {
|
|
123
|
+
const data = snapshot.val();
|
|
124
|
+
const list = Object.keys(data).map((key) => ({ id: key, ...data[key] }));
|
|
125
|
+
resolve(list);
|
|
126
|
+
})
|
|
127
|
+
.catch((error) => {
|
|
128
|
+
reject(error);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
delete(entity,collectionName: string) {
|
|
134
|
+
entity = { ...entity, seqNo: this.getCurrentTimestamp(), deleted: true };
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
this.database
|
|
137
|
+
.ref(`${collectionName}/${entity.id}`)
|
|
138
|
+
.set(firebaseSerialize(entity))
|
|
139
|
+
.then(() => {
|
|
140
|
+
resolve(entity);
|
|
141
|
+
})
|
|
142
|
+
.catch((error) => {
|
|
143
|
+
reject(error);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getFirebaseDatabase } from './common.firebase';
|
|
2
|
+
import { RealtimeDbCrud } from "./firebase-crud";
|
|
3
|
+
import { EnvironmentStorageService } from "../utils/environment-storage.service";
|
|
4
|
+
|
|
5
|
+
const COMMON_FUC = 'COMMON-FUNC';
|
|
6
|
+
|
|
7
|
+
export module CommonFunc {
|
|
8
|
+
export const load = async (): Promise<any> => {
|
|
9
|
+
const environmentName = await EnvironmentStorageService.getEnvironmentName();
|
|
10
|
+
|
|
11
|
+
// Khởi tạo DB async
|
|
12
|
+
const database = await getFirebaseDatabase();
|
|
13
|
+
const db = new RealtimeDbCrud<any>(database);
|
|
14
|
+
|
|
15
|
+
const list = await db.list(`ENV/${environmentName}/script`) as any[];
|
|
16
|
+
const l = (list || []).filter((it: any) => it.domain === COMMON_FUC && it.actionType === 'MAIN');
|
|
17
|
+
|
|
18
|
+
if (l.length !== 0) {
|
|
19
|
+
return l[0].code;
|
|
20
|
+
} else {
|
|
21
|
+
return `console.log('empty...${COMMON_FUC}')`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getFirebaseDatabase } from './common.firebase';
|
|
2
|
+
import { RealtimeDbCrud } from "./firebase-crud";
|
|
3
|
+
import { EnvironmentStorageService } from "../utils/environment-storage.service";
|
|
4
|
+
|
|
5
|
+
export module LoadScriptController {
|
|
6
|
+
|
|
7
|
+
export const loadScriptByDomain = async (domain: string, actionType: string): Promise<any> => {
|
|
8
|
+
const environmentName = await EnvironmentStorageService.getEnvironmentName();
|
|
9
|
+
|
|
10
|
+
// Khởi tạo DB async
|
|
11
|
+
const database = await getFirebaseDatabase();
|
|
12
|
+
const db = new RealtimeDbCrud<any>(database);
|
|
13
|
+
|
|
14
|
+
const list = await db.list(`ENV/${environmentName}/script`) as any[];
|
|
15
|
+
const l = (list || []).filter((it: any) => it.domain === domain && it.actionType === actionType.toUpperCase());
|
|
16
|
+
|
|
17
|
+
if (l.length !== 0) {
|
|
18
|
+
return l[0].code;
|
|
19
|
+
} else {
|
|
20
|
+
return "console.log('empty...')";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
}
|