编写"谷歌扩展"便捷"自测"埋点上报信息是否正确
推荐我之前写的谷歌扩展入门&svelte入门文章:
记一次前端"chrome扩展"简单易懂的实战分享会(上)
记一次前端"chrome插件"基础实战分享会(建议收藏)(下篇)
聊聊Svelte.js技术它做了什么以及如何实现的(上)
聊聊Svelte.js技术它做了什么以及如何实现的(下)
背景
每次开发完项目的埋点功能后都需要进行埋点自测, 此时我只能通过在控制台的Network
上进行筛选, 由于埋点的请求参数是个"对象类型", 需要将传参逐一点开并且找到params
这个key, 但是params
是一个被json序列化的字符串, 需要进行JSON.parse(xxxxx.params);
, 然后再打开它里面的数组结构
因为可能是多个埋点信息一起上报所以JSON.parse
后是个数据, 最后还要将里面的十个固有参数过滤掉只看我们新增的埋点参数。
上述过程很有规律所以可以抽象成一个工具查找的逻辑, 那我们可以把问题拆解成3步:
- (展示)有合适的界面方便显示你想要的请求数据。
- (获取)拦截到你想要的请求。
- (处理)将请求的参数按固定规则解析, 剔除不需要查看的参数与父级。
- (配置)可配置哪些请求需要被监测。
当然不只是埋点
这个功能, 只要你有分析api数据的需求都可以做类似的插件。
最终效果
一、模拟数据的定义
埋点上报数据我这里展示一个大概的数据结构, 大家根据业务自行调节, 假设下面的数据结构就是埋点上报api的上报参数
[
{
events: [
{
eventName: "事件名: 点击事件",
params:
'{"default1":"xxxxx","default2":"xxxxx","default3":"xxxxx","default4":"xxxxx","default5":"xxxxx","default6":"xxxxx","startTime":"xxxxx","endTime":"xxxxxxx", "new1":"nnnnnn1", "new2":"nnnnnn2"}',
xxxxxxxx: 1636179764881,
xxxxxxx: 0,
},
{
eventName: "事件名: 取消事件",
params:
'{"default1":"xxxxx","default2":"xxxxx","default3":"xxxxx","default4":"xxxxx","default5":"xxxxx","default6":"xxxxx","startTime":"xxxxx","endTime":"xxxxxxx", "new1":"nnnnnn2", "new3":"nnnnnn3"}',
xxxxxxxx: 1636179764881,
xxxxxxx: 0,
},
],
user: {
userId: 123,
userName: "lulu",
},
header: {
app_id: 999,
os_name: "mac",
os_version: "10_15_7",
device_model: "Macintosh",
language: "zh-CN",
},
},
];
- 传参本身是个"对象类型"
- 第一层主要是"埋点事件", "用户信息", "设备信息&更多"
- "events"是事件数组
- "eventName"事件名称, "params"里面是被"JSON序列化"的事件参数
- "params"里有一些我们不用看的默认值, 因为每个埋点都会带一些像"路径信息"&"停留事件"&"用户操作路径"等等的默认信息, 这些信息不用我们手动加入所以也就不用每次都看他的正确性。
- "new1, new2, new3"是我们新加入的参数, 也就是我们这次重点验证的参数
二、模拟埋点数据的发出
编写两个按钮负责上报埋点请求, 方便我们后续的验证, 新建一个html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button onclick="send(`https://zhangzhaosong1.com/`)">点击上报1</button>
<button onclick="send(`https://zhangzhaosong2.com/`)">点击上报2</button>
<script>
function send(url) {
fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json;charset=utf-8;",
},
body: JSON.stringify([
{
events: [
{
eventName: "事件名: 点击事件",
params:
'{"default1":"xxxxx","default2":"xxxxx","default3":"xxxxx","default4":"xxxxx","default5":"xxxxx","default6":"xxxxx","startTime":"xxxxx","endTime":"xxxxxxx", "new1":"nnnnnn1", "new2":"nnnnnn2"}',
xxxxxxxx: 1636179764881,
xxxxxxx: 0,
},
{
eventName: "事件名: 取消事件",
params:
'{"default1":"xxxxx","default2":"xxxxx","default3":"xxxxx","default4":"xxxxx","default5":"xxxxx","default6":"xxxxx","startTime":"xxxxx","endTime":"xxxxxxx", "new1":"nnnnnn2", "new3":"nnnnnn3"}',
xxxxxxxx: 1636179764881,
xxxxxxx: 0,
},
],
user: {
userId: 123,
userName: "lulu",
},
header: {
app_id: 999,
os_name: "mac",
os_version: "10_15_7",
device_model: "Macintosh",
language: "zh-CN",
},
},
]),
}).then(() => {});
}
</script>
</body>
</html>
上面html里面主要做的工作是添加两个"按钮", 每次点击发出埋点的模拟上报的post请求, 每个按钮上报的不一样所以有两个按钮。
三、创建一个谷歌插件项目 & 定义图标
没看过基础课的同学一定要先看看基础文章, 否则没法转换思维。
主文件
manifest.json
{
"manifest_version": 2,
"name": "埋:更方便的观察埋点数据",
"description": "更方便的观察埋点数据",
"version": "0.1",
"browser_action": {
"default_icon": "images/logo.png"
}
}
上述的图标大家自由发挥哈
引入插件
四、定义控制台tab
在manifest.json文件里面增加
{
"manifest_version": 2,
"name": "埋:更方便的观察埋点数据",
"description": "更方便的观察埋点数据",
"version": "0.1",
"browser_action": {
"default_icon": "images/logo.png"
},
"devtools_page": "devtools/index.html" // 这句是新增的
}
devtools/index.html
主要功能只是引入js文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
devtools/index.js
主要功能是创建tab
chrome.devtools.panels.create(
"埋点验证tab, 还原埋点上报信息",
null,
"../panel/index.html",
function () {
console.log("自定义面板创建成功!");
}
);
在panel/index.html
里面才是tab的内容区样式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>创建成功</div>
</body>
</html>
五、初始化svelte项目
tab里面的内容我们懒得原生操作dom, 使用"react"框架有点重, 所以这次我选择了"svelte框架", 具体它的好处可以看看我之前写的文章(文章放在文章头部了)。
初始项目
npx degit sveltejs/template devpanel
cd ./devpanel
yarn
我们编辑一下 rollup.config.js
文件, 使得打包后的文件直接放入panel
文件夹里。
output: {
sourcemap: false,
format: "iife",
name: "app",
file: production ? "../panel/index.js" : "public/build/bundle.js",
},
并且将panel
里面的index.html
文件的文件引用指向我们的index.js文件
, 以及引入样式 <link rel="stylesheet" href="./bundle.css">
在这里运行命令yarn build
后的效果如下, 请点击刷新插件并且"F12"重新打开控制台:
开发时候在localhost:5000/
, 开发完打包就可以和插件一起上线了。
六、拦截请求
核心功能就是拦截请求, 让我们使用onRequestFinished
api
chrome.devtools.network.onRequestFinished.addListener((res) => {})
上面的返回值res的数据格式如下图:
关键信息都有, 请求方法与url可以用来比对是否为需要拦截的请求, 而我们剩下的工作就是对"text"的解析了。
要注意的是, svelte
里面无法读取到chrome
的值, 所以需要用if (chrome) {}
包裹起来, 所以在svelte里面开发时我使用的是mock数据调试。
七、处理数据
<script>
const path = "https://zhangzhaosong.com/";
import getNowTime from "./getNowTime";
if (chrome) {
let checkEvent = "";
let dataList = [];
const baseKeyArr = [
"default1",
"default2",
"default3",
"default4",
"default5",
"default6",
"startTime",
"endTime",
];
chrome.devtools.network.onRequestFinished.addListener((res) => {
const req = res.request;
if (req.method === "POST" && req.url === path) {
const eventsArr = JSON.parse(req.postData.text);
eventsArr.forEach((item) => {
if (item.events) {
item.events.forEach((event) => {
const params = JSON.parse(event.params);
const res = {};
for (let i in params) {
if (!baseKeyArr.includes(i)) {
res[i] = params[i];
}
}
dataList.unshift({
res,
time: getNowTime(),
eventName: event.eventName,
});
dataList = dataList;
});
}
});
console.log("---", dataList);
}
});
}
</script>
<main>
<div>list</div>
</main>
<style>
</style>
baseKeyArr
这个数组里面是一些埋点默认的key
, 用来过滤掉我们不关心的值path
是要拦截的请求地址, 当然也可以换成正则之类的getNowTime
获取当前的时间
八、简单设计个样式
将上面得到的数组循环显示出来, 形成一个被拦截的埋点事件的参数list:
<main>
{#each Object.keys(dataList) as item}
<div>
<header>
<span>{dataList[item].time} : </span>
<span class="title">{dataList[item].eventName}</span>
</header>
<ul>
{#each Object.keys(dataList[item].res) as key}
<li>
<span>{key} :</span>
<span>{dataList[item].res[key]}</span>
</li>
{/each}
</ul>
</div>
{/each}
</main>
<style>
.title {
background-color: bisque;
}
</style>
九、过滤功能与清除功能
为了好用我们可以添加一个过滤功能与清除功能, 这样查看起来更舒服:
end
网站快卡炸了, 这次就是这样, 希望与你一起进步。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。