本文会从浏览器插件应用场景切入,穿插插件基础能力和常见入口的介绍,核心回答如下三个问题:插件可以被使用在哪些场景?不同的使用场景我们的主要代码实现思路是怎样的?我们可以从哪些角度入手自己开发一款可以落地实用的浏览器插件?
本文会从浏览器插件应用场景切入,穿插插件基础能力和常见入口的介绍,核心回答如下三个问题:插件可以被使用在哪些场景?不同的使用场景我们的主要代码实现思路是怎样的?我们可以从哪些角度入手自己开发一款可以落地实用的浏览器插件?
首先先从:插件作为产品能力一环、作为数据采集分析工具、作为产品载体三个方面展开讲述插件的应用场景。
2.1.1 PTS流量录制插件
2.1.2 UI云测操作录制插件
2.1.3 AEM热力图插件
2.1.4 稀土掘金插件
2.2.1 行为分析插件
2.2.2 PTS和UI云测的插件本质上也是做数据采集的,但是用途不一样,这两个主要是作为产品能力的一环
2.3.1 Sider
-
简介:通过浏览器原生siderPanel的容器模式实现ai助手;
-
优点:原生实现成本低,插件作为一个独立进程运行资源消耗低;
-
缺点:兼容性不是太好;
2.3.2 Monica
2.3.3 笔记类
2.4.1 网页翻译
2.4.2 取色器
2.4.3 OneTab
2.4.2 聊天通信
2.5.1 XSwitch
chrome.webRequest.onBeforeRequest.addListener(function (details) {
if (forward[constants["s" ]] !== enums["b" ].NO) {
if (clearCacheEnabled) {
clearCache();
}
return forward.onBeforeRequestCallback(details);
}
return {};
}, {
urls: [constants["c" ]]
}, [constants["e" ]]);
chrome.webRequest.onHeadersReceived.addListener(headersReceivedListener, {
urls: [constants["c" ]]
}, [constants["e" ], constants["O" ]]);
chrome.webRequest.onBeforeSendHeaders.addListener(function (details) {
return forward.onBeforeSendHeadersCallback(details);
}, {
urls: [constants["c" ]]
}, [constants["e" ], constants["N" ]]);
2.5.2 React Developer Tools
2.5.3 Formily DevTools
2.6.1 Cookie劫持-U know
2.6.2 请求劫持-电商返利
2.6.3 chrome主题皮肤
介绍完应用场景后,再针对几个典型场景进行代码实现思路介绍。
3.1.1 插件业务背景
3.1.2 插件核心实现代码
-
-
popup面板监测到未登录,会显示一个未登录按钮;点击该按钮打开阿里云控制台官网,用户登录完后会通过注入的脚本拿到token;
setInterval(() => {
try {
const manifest = chrome.extension.getURL('manifest.json');
Promise.all([
fetch(manifest),
fetch('https://xx.com/pts-record-chrome-plugin/package.json'),
])
.then(res => {
return [ res[0].json(), res[1].json() ];
})
.then(result => {
return Promise.all([ result[0], result[1] ]);
})
.then(res => {
return [ res[0].version, res[1].version ];
})
.then(versions => {
const [ currentVersion, latestVersion ] = versions;
if (compareVersion(latestVersion, currentVersion) > 0) {
storage.set('hasNew', latestVersion);
} else {
storage.set('hasNew', false);
}
})
.catch(() => {
storage.set('hasNew', false);
});
} catch (error) {
storage.set('hasNew', false);
}
}, 1000 * 60);
function openListener() {
chrome.webRequest.onBeforeRequest.addListener(
handleBeforeRequest,
{
urls: [ '' ],
},
[ 'requestBody', 'blocking', 'extraHeaders' ],
);
chrome.webRequest.onBeforeSendHeaders.addListener(
handleBeforeSendHeaders,
{
urls: [ '' ],
},
[ 'requestHeaders', 'blocking', 'extraHeaders' ],
);
chrome.webRequest.onBeforeRedirect.addListener(
handleOnBeforeRedirect,
{
urls: [ '' ],
},
);
chrome.webRequest.onCompleted.addListener(
handleOnCompleted,
{
urls: [ '' ],
},
[ 'responseHeaders', 'extraHeaders' ],
);
chrome.runtime.onMessage.addListener(handleRuntimeMessage);
return true;
}
const send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
this.addEventListener(
'readystatechange',
function() {
if (this.readyState === 4) {
const self = this;
window.top.postMessage(
{
direction: 'from-page-monkey-patch-script',
response: 'response',
value: {
body: handleResponse(self),
responseUrl
: self.responseURL,
},
},
'*',
);
}
},
false,
);
send.apply(this, arguments as any);
};
const open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
this.addEventListener(
'readystatechange',
function() {
if (this.readyState === 4) {
const self = this;
window.top.postMessage(
{
direction: 'from-page-monkey-patch-script',
response: 'request',
value: {
body: handleResponse(self),
responseUrl: self.responseURL,
},
},
'*',
);
}
},
false,
);
open.apply(this, arguments as any);
};
const constantMock = window.fetch;
window.fetch = function() {
return new Promise((resolve, reject) => {
constantMock
.apply(this, arguments as any)
.then(response => {
const responseForText = response.clone();
responseForText.text().then(text => {
window.top.postMessage(
{
direction: 'from-page-monkey-patch-script',
response: 'fetch',
value: {
body: text,
responseUrl: response.url,
},
},
'*',
);
resolve(response);
});
})
.catch(response => {
reject(response);
});
});
};
3.2.1 插件业务背景
3.2.1 插件核心实现代码
import { MessageTarget, MessageType } from '../../contstants/message';
export const ticketEventhandler = (e) => {
if (
e.target === MessageTarget.IntelligentAssistantBackgroundScript &&
e.origin === MessageTarget.IntelligentAssistantContentScript
) {
if (e.type === MessageType.UidEvent) {
if (e.data?.uid !== (window as any)?.aes?.getConfig('uid')) {
(window as any)?.aes?.setConfig?.({
uid: e.data?.uid,
});
}
}
}
};
const useVersions = () => {
const [versions, setVersions] = useState
currentVersion?: string;
newVersion?: string;
hasNew?: boolean;
}>({});
useEffect(() => {
chrome.storage.local.get(['currentVersion'], ({ currentVersion }) => {
updateConfigInfo().then((res) => {
setVersions({
currentVersion,
hasNew: res?.version !== currentVersion,
});
});
});
}, []);
return versions;
};
import { initAES } from './aes/index';
initAes();
import AES from '@alife/aes-tracker-chrome-plugin';
import AESPluginEvent from '@ali/aes-tracker-plugin-event';
import AESPluginPV from '@ali/aes-tracker-plugin-pv-chrome-plugin';
import AESPluginAutolog from '@ali/aes-tracker-plugin-autolog';
export const initAES = () => {
const aes = new AES({
pid: 'xx',
user_type: 'xx',
app_name: 'xx',
app_version: 'xx',
requiredFields: ["uid"],
maxUrlLength: 20000
});
(window as any).aes = aes;
(window as any).AESPluginEvent = aes.use(AESPluginEvent);
(window as any).AESPluginPV = aes.use(AESPluginPV, {
autoPV: false,
autoLeave: false
});
(window as any).AESPluginAutolog = aes.use(AESPluginAutolog);
};
-
然后初始化版本信息,注册定时器和监听器,同时注册过程要函数化,因为每次更新配置信息都会经历一次清除再注册的过程,达到可以动态配置定时器执行时间间隔的效果;
-
其中比较关键的是用户页面访问行为采集方案,需要推敲一下,考虑好各种边界;
-
点击/曝光事件的采集直接用aes自带的就好;
-
-
-
页面访问上报过程中,我们要动态控制上报哪些站点的访问信息(最小化插件权限);
-
定时上报过程中,我们要动态控制定时上报的时间间隔;
-
针对插件popup实时性能分析能力,我们需要根据实际情况灵活调整采集频率 / 最大存储数据量;
-
-
结合远程配置诉求和用户可以手动关闭采集过程的诉求,针对入口文件代码进行了改造;
-
核心是 启动逻辑收敛到start函数中,监听器清除逻辑收敛到close函数中(避免内存泄露);
-
针对开启/关闭行为分析操作,重复一下 close => start 的流程;
-
针对远程配置信息采用监听方案,特定配置信息变化后才会更新特定监听器,减少不必要性能开销;
import './aplus/aplus_mini';
import { initAES } from './aes/index';
import { initVersionByManifest } from './timer/config';
import { autologEventhandler } from './listener/autolog';
import { ticketEventhandler } from './listener/ticket';
import {
tabsUpdatedEventhandler,
tabsActivatedEventHandler,
tabsRemovedEventHandler,
windowsFocusedEventHandler,
windowsRemovedEventHandler,
} from './listener/behavior';
import { collectNetworkInfo } from './timer/network';
import { collectDeviceInfo } from './timer/device';
import { updateConfigInfo } from './timer/config';
import { collectPagesCount } from