零基础学会如何创建第一个属于自己的chrome插件【定时刷新页面、防止WebRTC泄漏】

相信各位佬友经常会用到 Chrome浏览器 或者 类Chrome浏览器,这类浏览器有个非常好用的功能就是支持插件扩展。这个插件能让浏览器实现更多的玩法,而且让定制各类功能需求也变的得心应手。

恰巧今日得闲,花了一个小时手搓了一个简单的基础类扩展,也正好用这个实例为佬友们说说这个扩展的基本实现原理。实例的主要功能是 防止WebRTC泄露真实IP 和实现 各标签页的自动刷新

https://www.browserscan.net/zh/webrtc 来测试是否存在WebRTC泄露

教程的所有文件尽量保存为utf-8格式

主配置:
manifest.json

{
  "manifest_version": 3,
  "name": "My extension library",
  "version": "1.0",
  "description": "Welcome to my extension library",
  "permissions": ["tabs", "activeTab", "storage", "alarms", "privacy"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"],
    "run_at": "document_start",
    "all_frames": true
  }]
}

主配置中的nameversiondescription可以随意写,permissions 声明插件所需的权限,content_scripts 功能非常强大(暂定匹配所有URL)。至于插件icon不是必须的,也不是今天的重点,如果有这方面需求的可以自己在主配置中添加即可。

主脚本:
background.js

//定义一个变量(是否启用WebRTC)、一个常量(判断是不是firefox)。
let pubwebrtc=false;
const isFirefox = /Firefox/.test(navigator.userAgent) || typeof InstallTrigger !== 'undefined';

//这个是关闭标签页触发的回调,主要功能是删除计时器
chrome.tabs.onRemoved.addListener((tabId) => {
	chrome.storage.local.get(["refreshTabs"], (data) => {
		let refreshTabs = data.refreshTabs || {};
		if (refreshTabs[tabId]){
			if (refreshTabs[tabId]["timer"])chrome.alarms.clear(refreshTabs[tabId]["timer"]);
			delete refreshTabs[tabId];
			chrome.storage.local.set({ refreshTabs });
		}
	});
});

//这个是设置并开启定时刷新的功能,由popup.html(交互式窗口)触发
function setRefreshTimer(tabId, interval) {
    chrome.storage.local.get(["refreshTabs"], (data) => {
        let refreshTabs = data.refreshTabs || {};
		if (!refreshTabs[tabId]) refreshTabs[tabId]={};
		if (refreshTabs[tabId]["timer"])chrome.alarms.clear(refreshTabs[tabId]["timer"]);
		refreshTabs[tabId]["timer"]="auto"+tabId;
		chrome.alarms.create(refreshTabs[tabId]["timer"], {delayInMinutes: 0, periodInMinutes: interval/60});
		refreshTabs[tabId]["interval"] = interval;
        chrome.storage.local.set({ refreshTabs });
    });
}

//这个是接收popup.html传入的消息并做相应的处理,主要功能是开/关自动刷新、启用/禁用WebRTC
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    const { action, tabId, interval } = request;
    if (action === "start_refresh") {
        setRefreshTimer(tabId, interval);
    } else if (action === "stop_refresh") {
		chrome.storage.local.get(["refreshTabs"], (data) => {
			let refreshTabs = data.refreshTabs || {};
			if(refreshTabs[tabId]){
				if(refreshTabs[tabId]["timer"]){
					chrome.alarms.clear(refreshTabs[tabId]["timer"]);
					delete refreshTabs[tabId]["timer"];
					chrome.storage.local.set({ refreshTabs });
				}
			}
		})
    } else if (action === "start_webrtc") {
		webrtcfunc(true,true);
	} else if (action === "stop_webrtc") {
		webrtcfunc(false,true);
	}
	sendResponse({ success: true });
});

//计时器主函数,主要功能是重载页面(不受其他外在因素影响)
chrome.alarms.onAlarm.addListener((alarm) => {
	if (alarm.name.substr(0,4) == "auto") {
		let tabId=parseInt(alarm.name.substr(4));
		try{chrome.tabs.reload(tabId)}catch(e){}
	}
});

//启用或禁用WebRTC后调用的功能函数
function webrtcaction() {
	chrome.storage.local.get({
		enabled: !pubwebrtc,
		eMode: isFirefox ? 'proxy_only' : 'disable_non_proxied_udp',
		dMode: 'default_public_interface_only'
	}, prefs => {
		const value = prefs.enabled ? prefs.eMode : prefs.dMode;
		chrome.privacy.network.webRTCIPHandlingPolicy.clear({}, () => {chrome.privacy.network.webRTCIPHandlingPolicy.set({value}, () => {chrome.privacy.network.webRTCIPHandlingPolicy.get({}, s => {})})});
	});
}

//开启、禁用WebRTC
function webrtcfunc(v,s){
	let webrtc = v; pubwebrtc = webrtc;
	if (s) chrome.storage.local.set({ webrtc });
	webrtcaction();
}

//在插件加载、安装、更新时触发,主要功能是初始化WebRTC
function webrtcinit(){
	chrome.storage.onChanged.addListener(() => {webrtcaction()});
	chrome.storage.local.get(["webrtc"], (data) => {
		let webrtc = data.webrtc || false;
		webrtcfunc(webrtc,false);
	});
}

//在插件加载、安装、更新时触发,主要功能是初始化计时器本地存储
function clearStoredTabs() {
	let refreshTabs={};
	chrome.storage.local.set({ refreshTabs });
}

chrome.runtime.onStartup.addListener(() => {webrtcinit(); clearStoredTabs()});
chrome.runtime.onInstalled.addListener((details) => {webrtcinit(); if (details.reason === "install") clearStoredTabs()});

交互式页面:
popup.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Auto refresh</title>
    <style>
        body { width: 200px; font-family: Arial, sans-serif; text-align: center; }
        input { width: 30%; margin: 5px 0; }
        button { width: 90%; margin: 5px 0; padding: 5px; }
    </style>
</head>
<body>
    间隔 <input type="number" id="interval" value=60 min="1"> 秒,自动刷新
    <button id="onebutton"></button>
    <script src="popup.js"></script>
    <button id="webrtc" title="作用于全局"></button>
</body>
</html>

交互式页面配套的脚本文件:
popup.js

//定义提示文字常量
const strs=["开始刷新", "停止刷新", "<font color=green>当前无WebRTC泄漏</font>", "<font color=red>存在WebRTC泄漏风险</font>"]

//popup.html加载完成后执行,主要功能是和用户交互,并传递相应参数给background.js
document.addEventListener("DOMContentLoaded", () => {
    const btn = document.getElementById("onebutton");
	const wtn = document.getElementById("webrtc");
    const intervalInput = document.getElementById("interval");
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        if (tabs.length === 0) return;
        const tabId = tabs[0].id;
		btn.innerHTML=strs[0];
		intervalInput.focus();intervalInput.select();
        chrome.storage.local.get(["refreshTabs"], (data) => {
            let refreshTabs = data.refreshTabs || {};
			if (refreshTabs[tabId]){
				if (refreshTabs[tabId]["interval"]){
					intervalInput.value = refreshTabs[tabId]["interval"];
					intervalInput.select();
				}
				if (refreshTabs[tabId]["timer"]) {
					btn.innerHTML=strs[1];btn.setAttribute("func","stop");
				}
			}
        });
        btn.addEventListener("click", () => {
			if (btn.getAttribute("func")){
				chrome.runtime.sendMessage({ action: "stop_refresh", tabId }, (response) => {
					btn.innerHTML=strs[0];btn.removeAttribute("func");
				});
			}else{
				if (isNaN(intervalInput.value) || intervalInput.value < 1) intervalInput.value=60;
				let interval = parseFloat(intervalInput.value);
				chrome.runtime.sendMessage({ action: "start_refresh", tabId, interval }, (response) => {
					btn.innerHTML=strs[1];btn.setAttribute("func","stop");
				});
			}
        });
        chrome.storage.local.get(["webrtc"], (data) => {
            let webrtc = data.webrtc || false;
			if (webrtc){
				wtn.innerHTML=strs[3];wtn.setAttribute("func","true");
			}else wtn.innerHTML=strs[2];
        });
        wtn.addEventListener("click", () => {
			if (wtn.getAttribute("func")){
				chrome.runtime.sendMessage({ action: "stop_webrtc", tabId }, (response) => {
					wtn.innerHTML=strs[2];wtn.removeAttribute("func");
				});
			}else{
				chrome.runtime.sendMessage({ action: "start_webrtc", tabId }, (response) => {
					wtn.innerHTML=strs[3];wtn.setAttribute("func","true");
				});
			}
        });
    });
});

强大的功能脚本(由于过于强大,所以不方便展示)
content.js

//其实这个脚本的强大与否,主要还是取决于自己想要做什么。
(function() {

document.addEventListener('DOMContentLoaded', function() {
	document.title = "Welcome " + document.title;
});

})();

加载插件:
1.将上述几个文件全部根据提示的文件名保存到本地(格式选UTF-8)
2.启动 chrome 并打开扩展页 chrome://extensions/
3.在扩展页面 启用开发者模式
4.点击 加载已解压的扩展程序 ,选择上述文件保存的目录即可

https://www.browserscan.net/zh/webrtc 再次来测试是否存在WebRTC泄露

右上角的 扩展程序 按钮可以打开插件 交互式页面

扩展插件的功能很多,在于你想干什么能干什么,在什么人手里用,所以剩下的就看佬友们的个人发挥了。

相信佬友们会写出更好的扩展,还请多多交流与分享。

Good luck to all

1 个赞

感谢大佬喂饭。 现在有很多根据fq的小鸡ip地址,能够查到真实ip,就是通过WebRTC泄露的。

Wow 牛B大佬,以前总以为上了节点就安全了,原来还有webRTC泄漏的风险,用教程里的链接查了一下还真是泄漏了,实际IP显示的一清二楚,原来还有这么一个坑 。

感谢大佬,照毛画虎搞定了,真实IP完全隐藏了。发现edge也能用。

Image description![Image description](https://s.rmimg.com/2025-03-14/1741929900-161437-image.png)

@“James”#p259832 感谢站长,继续努力,为社区做贡献。

@clofan 感谢支持。

2 个赞

我能说 [size=40]我靠[/size] 吗?

发现大佬的每个教程我都能用上。我太小白了吧

1 个赞

谢谢佬,基本上算是喂饭了,一次成功:ac26:

谢谢超级大佬,已经用上了

感谢大佬的喂饭教程,非常适合我。

谢谢大佬,今天就去抢hax试试

大佬牛逼666

十分感谢。

牛B plus!赞

收藏一下,有空测试一下:xhj02:

好东西 感谢分享

好东西,好操作,最关键是好楼主

喜欢用现成的

@“fat3264”#p259818 可以了,neng bImage description</s>Image description<e>

能不能大概给解读一下,这是做了什么


@“fat3264”#p259818 大佬在提个需求,我想在safari上也适用,就找了个帖子,想问下参照这个种方式我需要提取那个文件名

Image description</s>Image description<e>


好帖,感谢op分享。

感谢op分享