Browse Source

各个数据埋点入口功能完善

zxlie 5 tháng trước cách đây
mục cha
commit
55eb021b77

+ 56 - 1
apps/background/awesome.js

@@ -293,6 +293,60 @@ let Awesome = (() => {
         return {get, set};
     })();
 
+    /**
+     * 采集客户端信息并发送给background
+     */
+    let collectAndSendClientInfo = () => {
+        try {
+            const nav = navigator;
+            const screenInfo = window.screen;
+            const lang = nav.language || nav.userLanguage || '';
+            const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
+            const ua = nav.userAgent;
+            const platform = nav.platform;
+            const vendor = nav.vendor;
+            const colorDepth = screenInfo.colorDepth;
+            const screenWidth = screenInfo.width;
+            const screenHeight = screenInfo.height;
+            const deviceMemory = nav.deviceMemory || '';
+            const hardwareConcurrency = nav.hardwareConcurrency || '';
+            const connection = nav.connection || nav.mozConnection || nav.webkitConnection || {};
+            const screenOrientation = screenInfo.orientation ? screenInfo.orientation.type : '';
+            const touchSupport = ('ontouchstart' in window) || (nav.maxTouchPoints > 0);
+            let memoryJSHeapSize = '';
+            if (window.performance && window.performance.memory) {
+                memoryJSHeapSize = window.performance.memory.jsHeapSizeLimit;
+            }
+            const clientInfo = {
+                language: lang,
+                timezone,
+                userAgent: ua,
+                platform,
+                vendor,
+                colorDepth,
+                screenWidth,
+                screenHeight,
+                deviceMemory,
+                hardwareConcurrency,
+                networkType: connection.effectiveType || '',
+                downlink: connection.downlink || '',
+                rtt: connection.rtt || '',
+                online: nav.onLine,
+                touchSupport,
+                cookieEnabled: nav.cookieEnabled,
+                doNotTrack: nav.doNotTrack,
+                appVersion: nav.appVersion,
+                appName: nav.appName,
+                product: nav.product,
+                vendorSub: nav.vendorSub,
+                screenOrientation,
+                memoryJSHeapSize
+            };
+            chrome.runtime.sendMessage({ type: 'clientInfo', data: clientInfo });
+        } catch (e) {
+            // 忽略采集异常
+        }
+    };
 
     return {
         StorageMgr,
@@ -307,7 +361,8 @@ let Awesome = (() => {
         gcLocalFiles,
         getAllTools,
         SortToolMgr,
-        CodeCacheMgr
+        CodeCacheMgr,
+        collectAndSendClientInfo
     }
 })();
 

+ 24 - 21
apps/background/background.js

@@ -24,6 +24,9 @@ let BgPageInstance = (function () {
         /^https:\/\/chrome\.google\.com/
     ];
 
+    // 全局缓存最新的客户端信息
+    let FH_CLIENT_INFO = {};
+
     /**
      * 文本格式,可以设置一个图标和标题
      * @param {Object} options
@@ -342,14 +345,14 @@ let BgPageInstance = (function () {
     };
 
     let _codeBeautify = function(params){
-        if (['javascript', 'css'].includes(params.fileType)) {
-            Awesome.StorageMgr.get('JS_CSS_PAGE_BEAUTIFY').then(val => {
-                if(val !== '0') {
-                    let js = `window._codebutifydetect_('${params.fileType}')`;
-                    InjectTools.inject(params.tabId, { js });
-                }
-            });
-        }
+        Awesome.StorageMgr.get('JS_CSS_PAGE_BEAUTIFY').then(val => {
+            if(val !== '0') {
+                let js = `window._codebutifydetect_('${params.fileType}')`;
+                InjectTools.inject(params.tabId, { js });
+                // 记录工具使用
+                Statistics.recordToolUsage('code-beautify');
+            }
+        });
     };
 
     /**
@@ -453,8 +456,6 @@ let BgPageInstance = (function () {
                         return true; // 这个返回true是非常重要的!!!要不然callback会拿不到结果
                     case 'code-beautify':
                         _codeBeautify(request.params);
-                        // 记录工具使用
-                        Statistics.recordToolUsage('code-beautify');
                         break;
                     case 'close-beautify':
                         Awesome.StorageMgr.set('JS_CSS_PAGE_BEAUTIFY',0);
@@ -507,7 +508,7 @@ let BgPageInstance = (function () {
                     case 'open-donate-modal':
                         chrome.gotoDonateModal(request.params.toolName);
                         break;
-                    case 'load-json-script':
+                    case 'load-local-script':
                         // 处理加载JSON格式化相关脚本的请求
                         fetch(request.script)
                             .then(response => response.text())
@@ -519,6 +520,10 @@ let BgPageInstance = (function () {
                                 callback && callback(null);
                             });
                         return true; // 异步响应需要返回true
+                    case 'statistics-tool-usage':
+                        // 埋点:自动触发json-format-auto
+                        Statistics.recordToolUsage(request.params.tool_name,request.params);
+                        break;
                 }
                 callback && callback(request.params);
             } else {
@@ -574,16 +579,7 @@ let BgPageInstance = (function () {
         });
         
         // 卸载
-        chrome.runtime.setUninstallURL(chrome.runtime.getManifest().homepage_url, () => {
-            // 记录卸载事件
-            try {
-                import('./statistics.js').then(({default: Statistics}) => {
-                    Statistics.recordUninstall();
-                });
-            } catch (e) {
-                // 忽略异常
-            }
-        });
+        chrome.runtime.setUninstallURL(chrome.runtime.getManifest().homepage_url);
     };
 
     /**
@@ -652,6 +648,13 @@ let BgPageInstance = (function () {
         });
     }
 
+    // 监听options页面传递的客户端信息
+    chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
+        if (request && request.type === 'clientInfo' && request.data) {
+            FH_CLIENT_INFO = request.data;
+        }
+    });
+
     return {
         pageCapture: _captureVisibleTab,
         init: _init

+ 21 - 52
apps/background/statistics.js

@@ -9,9 +9,9 @@ import Awesome from './awesome.js';
 let manifest = chrome.runtime.getManifest();
 let SERVER_TRACK_URL = '';
 if (manifest.name && manifest.name.endsWith('-Dev')) {
-    SERVER_TRACK_URL = 'https://chrome.fehelper.com/api/track';
+    SERVER_TRACK_URL = 'http://localhost:3001/api/track';
 } else {
-    SERVER_TRACK_URL = 'https://localhost:3001/api/track';
+    SERVER_TRACK_URL = 'https://chrome.fehelper.com/api/track';
 }
 
 // 用户ID存储键名
@@ -96,59 +96,12 @@ let Statistics = (function() {
     };
     
     /**
-     * 获取客户端详细信息,对标百度统计/GA/友盟
+     * 获取客户端详细信息(仅background可用字段)
      * @returns {Object}
      */
     const getClientInfo = () => {
-        const nav = navigator;
-        const screenInfo = window.screen;
-        const lang = nav.language || nav.userLanguage || '';
-        const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
-        const ua = nav.userAgent;
-        const platform = nav.platform;
-        const vendor = nav.vendor;
-        const colorDepth = screenInfo.colorDepth;
-        const screenWidth = screenInfo.width;
-        const screenHeight = screenInfo.height;
-        const deviceMemory = nav.deviceMemory || '';
-        const hardwareConcurrency = nav.hardwareConcurrency || '';
-        // 网络信息
-        const connection = nav.connection || nav.mozConnection || nav.webkitConnection || {};
-        // 屏幕方向
-        const screenOrientation = screenInfo.orientation ? screenInfo.orientation.type : '';
-        // 触摸支持
-        const touchSupport = ('ontouchstart' in window) || (nav.maxTouchPoints > 0);
-        // JS堆内存(仅Chrome)
-        let memoryJSHeapSize = '';
-        if (window.performance && window.performance.memory) {
-            memoryJSHeapSize = window.performance.memory.jsHeapSizeLimit;
-        }
         return {
-            language: lang,
-            timezone,
-            userAgent: ua,
-            platform,
-            vendor,
-            colorDepth,
-            screenWidth,
-            screenHeight,
-            deviceMemory,
-            hardwareConcurrency,
             extensionVersion: chrome.runtime.getManifest().version,
-            // 新增字段
-            networkType: connection.effectiveType || '',
-            downlink: connection.downlink || '',
-            rtt: connection.rtt || '',
-            online: nav.onLine,
-            touchSupport,
-            cookieEnabled: nav.cookieEnabled,
-            doNotTrack: nav.doNotTrack,
-            appVersion: nav.appVersion,
-            appName: nav.appName,
-            product: nav.product,
-            vendorSub: nav.vendorSub,
-            screenOrientation,
-            memoryJSHeapSize,
             timeOpened: FH_TIME_OPENED
         };
     };
@@ -161,12 +114,27 @@ let Statistics = (function() {
     const sendToServer = async (eventName, params = {}) => {
         const uid = await getUserId();
         const clientInfo = getClientInfo();
+        // 合并background全局的FH_CLIENT_INFO
+        let extraInfo = {};
+        try {
+            if (typeof chrome !== 'undefined' && chrome && chrome.runtime && chrome.runtime.getBackgroundPage) {
+                await new Promise(resolve => {
+                    chrome.runtime.getBackgroundPage(bg => {
+                        if (bg && bg.FH_CLIENT_INFO) {
+                            extraInfo = bg.FH_CLIENT_INFO;
+                        }
+                        resolve();
+                    });
+                });
+            }
+        } catch(e) {}
         const payload = {
             event: eventName,
             userId: uid,
             date: todayStr,
             timestamp: Date.now(),
             ...clientInfo,
+            ...extraInfo,
             ...params
         };
         try {
@@ -242,7 +210,7 @@ let Statistics = (function() {
      * 记录工具使用情况
      * @param {string} toolName - 工具名称
      */
-    const recordToolUsage = async (toolName) => {
+    const recordToolUsage = async (toolName, params = {}) => {
         // 确保今天的记录存在
         if (!usageData.dailyUsage[todayStr]) {
             usageData.dailyUsage[todayStr] = {
@@ -269,7 +237,8 @@ let Statistics = (function() {
         // 发送工具使用记录到自建服务器
         sendToServer('tool_used', {
             tool_name: toolName,
-            date: todayStr
+            date: todayStr,
+            ...params
         });
     };
     

+ 35 - 11
apps/code-beautify/content-script.js

@@ -6,13 +6,35 @@ window.codebeautifyContentScript = (() => {
         if (location.protocol === 'chrome-extension:' || chrome.runtime && chrome.runtime.getURL) {
             url = chrome.runtime.getURL('code-beautify/' + filename);
         }
-        fetch(url).then(resp => resp.text()).then(jsText => {
-            if(window.evalCore && window.evalCore.getEvalInstance){
-                return window.evalCore.getEvalInstance(window)(jsText);
+        // 使用chrome.runtime.sendMessage向background请求加载脚本
+        chrome.runtime.sendMessage({
+            type: 'fh-dynamic-any-thing',
+            thing: 'load-local-script',
+            script: url
+        }, (scriptContent) => {
+            if (!scriptContent) {
+                console.error('Failed to load script:', filename);
+                return;
+            }
+            
+            // 如果有evalCore则使用它
+            if (window.evalCore && window.evalCore.getEvalInstance) {
+                try {
+                    window.evalCore.getEvalInstance(window)(scriptContent);
+                } catch(e) {
+                    console.error('Failed to evaluate script using evalCore:', e);
+                }
+            } else {
+                // 创建一个函数来执行脚本
+                try {
+                    // 使用Function构造函数创建一个函数,并在当前窗口上下文中执行
+                    // 这比动态创建script元素更安全,因为它不涉及DOM操作
+                    const executeScript = new Function(scriptContent);
+                    executeScript.call(window);
+                } catch(e) {
+                    console.error('Failed to execute script:', filename, e);
+                }
             }
-            let el = document.createElement('script');
-            el.textContent = jsText;
-            document.head.appendChild(el);
         });
     };
 
@@ -191,11 +213,13 @@ window.codebeautifyContentScript = (() => {
             fileType = undefined;
         }
 
-        chrome.runtime.sendMessage({
-            type: 'fh-dynamic-any-thing',
-            thing: 'code-beautify',
-            params: { fileType, tabId: window.__FH_TAB_ID__ || null }
-        });
+        if (['javascript', 'css'].includes(fileType)) {
+            chrome.runtime.sendMessage({
+                type: 'fh-dynamic-any-thing',
+                thing: 'code-beautify',
+                params: { fileType, tabId: window.__FH_TAB_ID__ || null }
+            });
+        }
     };
 
 })();

+ 24 - 1
apps/json-format/content-script.js

@@ -18,7 +18,7 @@ window.JsonAutoFormat = (() => {
         // 使用chrome.runtime.sendMessage向background请求加载脚本
         chrome.runtime.sendMessage({
             type: 'fh-dynamic-any-thing',
-            thing: 'load-json-script',
+            thing: 'load-local-script',
             script: url
         }, (scriptContent) => {
             if (!scriptContent) {
@@ -437,6 +437,17 @@ window.JsonAutoFormat = (() => {
                 $('#jfCallbackName_end').html(')');
             }
         }
+        
+        // 埋点:自动触发json-format-auto
+        chrome.runtime.sendMessage({
+            type: 'fh-dynamic-any-thing',
+            thing: 'statistics-tool-usage',
+            params: {
+                tool_name: 'json-format',
+                url: location.href
+            }
+        });
+        
     };
 
     let _getCorrectContent = function () {
@@ -699,6 +710,18 @@ window.JsonAutoFormat = (() => {
         }
     };
 
+    // 页面加载后自动采集
+    try {
+        if (window.chrome && chrome.runtime && chrome.runtime.sendMessage && window.Awesome && window.Awesome.collectAndSendClientInfo) {
+            window.Awesome.collectAndSendClientInfo();
+        } else {
+            // fallback: 动态加载Awesome模块
+            import(chrome.runtime.getURL('background/awesome.js')).then(module => {
+                module.default.collectAndSendClientInfo();
+            }).catch(() => {});
+        }
+    } catch(e) {}
+
     return {
         format: () => _getAllOptions(options => {
             if(options.JSON_PAGE_FORMAT) {

+ 3 - 0
apps/manifest.json

@@ -61,6 +61,9 @@
             "static/vendor/evalCore.min.js",
             "screenshot/content-script.js",
 
+            "background/awesome.js",
+            "background/tools.js",
+
             "code-beautify/beautify.js",
             "code-beautify/beautify-css.js",
 

+ 1 - 1
apps/options/index.html

@@ -321,7 +321,7 @@
 </div>
 
 <script src="../static/js/dark-mode.js"></script>
-<script type="module" src="market.js"></script>
+<script type="module" src="index.js"></script>
 
 </body>
 </html> 

+ 15 - 1
apps/options/market.js → apps/options/index.js

@@ -82,6 +82,15 @@ new Vue({
         
         // 检查URL中是否有donate_from参数
         this.checkDonateParam();
+
+        // 埋点:自动触发options
+        chrome.runtime.sendMessage({
+            type: 'fh-dynamic-any-thing',
+            thing: 'statistics-tool-usage',
+            params: {
+                tool_name: 'options'
+            }
+        });
     },
 
     computed: {
@@ -1374,4 +1383,9 @@ window.addEventListener('scroll', () => {
         header.classList.remove('scrolled');
         sidebar && sidebar.classList.remove('scrolled');
     }
-}); 
+});
+
+// 页面加载后自动采集
+if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) {
+    Awesome.collectAndSendClientInfo();
+} 

+ 15 - 0
apps/popup/index.js

@@ -47,6 +47,16 @@ new Vue({
 
         // 自动开关灯
         DarkModeMgr.turnLightAuto();
+
+        // 记录工具使用
+        // 埋点:自动触发json-format-auto
+        chrome.runtime.sendMessage({
+            type: 'fh-dynamic-any-thing',
+            thing: 'statistics-tool-usage',
+            params: {
+                tool_name: 'popup'
+            }
+        });
     },
 
     mounted: function () {
@@ -97,6 +107,11 @@ new Vue({
                 return false;
             };
         });
+
+        // 页面加载后自动采集
+        if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) {
+            Awesome.collectAndSendClientInfo();
+        }
     },
 
     methods: {

+ 41 - 0
server/README.md

@@ -0,0 +1,41 @@
+# FeHelper Server
+
+本服务为FeHelper插件的数据统计与管理后台服务端,基于Node.js + Express + MongoDB。
+
+## 安装依赖
+
+```bash
+cd server
+npm install
+```
+
+## 启动服务
+
+```bash
+npm start
+```
+
+默认监听端口:3001
+
+## 配置
+
+MongoDB连接字符串在`config.js`中配置。
+
+## 埋点上报接口
+
+- POST `/api/track`
+    - Content-Type: application/json
+    - Body: 详见客户端埋点数据结构
+    - 返回:`{ code: 0, msg: '上报成功' }`
+
+## 健康检查
+
+- GET `/api/ping`
+    - 返回:pong
+
+## 代码结构
+
+- `index.js`:主服务入口,包含express服务、MongoDB连接、埋点接口
+- `config.js`:MongoDB连接配置
+- `api.js`:管理后台API扩展入口
+- `package.json`:依赖管理 

+ 9 - 0
server/api.js

@@ -0,0 +1,9 @@
+// 预留管理后台API扩展
+// 示例:后续可添加统计分析、用户管理等接口
+
+module.exports = function(app) {
+    // 例如:获取所有埋点数据
+    // app.get('/api/tracks', async (req, res) => {
+    //     // ...
+    // });
+}; 

+ 4 - 0
server/config.js

@@ -0,0 +1,4 @@
+// MongoDB配置
+module.exports = {
+    mongoUri: 'mongodb://localhost:27017/fehelper', // 请根据实际情况修改
+}; 

+ 86 - 0
server/index.js

@@ -0,0 +1,86 @@
+// FeHelper 服务端主入口
+const express = require('express');
+const mongoose = require('mongoose');
+const cors = require('cors');
+const bodyParser = require('body-parser');
+const { mongoUri } = require('./config');
+
+const app = express();
+const PORT = 3001;
+
+// 中间件
+app.use(cors());
+app.use(bodyParser.json());
+
+// 连接MongoDB
+mongoose.connect(mongoUri, {
+    useNewUrlParser: true,
+    useUnifiedTopology: true
+}).then(() => {
+    console.log('MongoDB连接成功');
+}).catch(err => {
+    console.error('MongoDB连接失败:', err);
+});
+
+// 定义埋点数据模型
+const TrackSchema = new mongoose.Schema({
+    event: String,
+    userId: String,
+    date: String,
+    timestamp: Number,
+    tool_name: String,
+    previous_version: String,
+    top_tools: String,
+    // 客户端信息
+    language: String,
+    timezone: String,
+    userAgent: String,
+    platform: String,
+    vendor: String,
+    colorDepth: Number,
+    screenWidth: Number,
+    screenHeight: Number,
+    deviceMemory: String,
+    hardwareConcurrency: String,
+    extensionVersion: String,
+    networkType: String,
+    downlink: String,
+    rtt: String,
+    online: Boolean,
+    touchSupport: Boolean,
+    cookieEnabled: Boolean,
+    doNotTrack: String,
+    appVersion: String,
+    appName: String,
+    product: String,
+    vendorSub: String,
+    screenOrientation: String,
+    memoryJSHeapSize: String,
+    timeOpened: Number
+}, { timestamps: true });
+
+const Track = mongoose.model('Track', TrackSchema);
+
+// 埋点上报接口
+app.post('/api/track', async (req, res) => {
+    try {
+        const data = req.body;
+        await Track.create(data);
+        res.json({ code: 0, msg: '上报成功' });
+    } catch (err) {
+        console.error('埋点上报失败:', err);
+        res.status(500).json({ code: 1, msg: '上报失败' });
+    }
+});
+
+// 健康检查
+app.get('/api/ping', (req, res) => {
+    res.send('pong');
+});
+
+// 加载管理后台API扩展
+require('./api')(app);
+
+app.listen(PORT, () => {
+    console.log(`FeHelper统计服务已启动,端口:${PORT}`);
+}); 

+ 15 - 0
server/package.json

@@ -0,0 +1,15 @@
+{
+  "name": "fehelper-server",
+  "version": "1.0.0",
+  "description": "FeHelper数据统计与管理后台服务端",
+  "main": "index.js",
+  "scripts": {
+    "start": "node index.js"
+  },
+  "dependencies": {
+    "express": "^4.18.2",
+    "mongoose": "^7.6.3",
+    "cors": "^2.8.5",
+    "body-parser": "^1.20.2"
+  }
+}