解决UMAMI统计脚本被广告拦截插件屏蔽的问题
上一篇文章讲到了如何在本机环境和docker中去安装umami,这一篇讲一下如何防止umami脚本被屏蔽。
默认的代码如下:
<script async defer data-website-id="6b43b895-17fb-42d5-aacc-ef0841ca34c5" src="http://ip:3000/umami.js"></script>
有两种方法,但是都需要修改umami的脚本名。第一种是通过反代、worker、修改配置实现,第二种是重新混淆js实现,殊途同归。
通过umami配置文件修改脚本名
该方法需要重启umami来使其生效
在上一篇中创建的.env文件中增加一行TRACKER_SCRIPT_NAME=新脚本名称
如果是docker的话,需要通过docker ps
定位到容器,然后执行docker attach 容器ID
进入并找到.env文件进行修改(实际可能并不能进入到终端),或者是创建的时候使用-v
参数将volume映射一份到本地目录。
修改完成后重启:pm2 restart umami
重启后效果如下:
但是dashboard中代码仍然显示的umami.js,我们换成自己定义的名称.js即可。
原因
在其umami/pages/_middleware.js
代码中通过
process.env.TRACKER_SCRIPT_NAME
获取变量从而决定track脚本的名称:
function customScriptName(req) {
const scriptName = process.env.TRACKER_SCRIPT_NAME;
if (scriptName) {
const url = req.nextUrl.clone();
const { pathname } = url;
const names = scriptName.split(',').map(name => (name + '.js').trim());
if (names.find(name => pathname.endsWith(name))) {
url.pathname = '/umami.js';
return NextResponse.rewrite(url);
}
}
}
通过反向代理重命名脚本名称
我们可以直接添加以下内容到伪静态
location /myjs {
proxy_pass http://127.0.0.1:3000/umami.js;
}
然后访问/myjs即可,同理上步。
通过cloudflare worker进行http代理
此处就不以cloudflare为例了,我们使用百度云加速:
worker代码:
const ScriptName = '/umami.js'; //自定义脚本名称
const Endpoint = '/foo/bar';
const UmamiUrl = 'https://uaxk.com'; //UMAMI服务域名地址。
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
'Access-Control-Max-Age': '86400',
};
const ScriptWithoutExtension = ScriptName.replace('.js', '')
addEventListener('fetch', event => {
event.passThroughOnException();
event.respondWith(handleRequest(event));
})
async function handleRequest(event) {
const pathname = new URL(event.request.url).pathname
const [baseUri, ...extensions] = pathname.split('.')
const clientIP = event.request.headers.get("CF-Connecting-IP")
if (baseUri.endsWith(ScriptWithoutExtension)) {
return getScript(event, extensions)
} else if (pathname.endsWith(Endpoint)) {
return postData(event)
}
return new Response(null, {status: 404})
}
async function getScript(event, extensions) {
let response = await caches.default.match(event.request);
if (!response) {
response = await fetch(UmamiUrl +"/umami.js");
var js = await response.text();
js = js.replace("/api/collect", Endpoint);
response = new Response(js, {
headers: {
...response.headers,
...corsHeaders,
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Request-Headers'),
},
})
event.waitUntil(caches.default.put(event.request, response.clone()));
}
return response;
}
async function postData(event) {
const request = new Request(event.request);
request.headers.delete('cookie');
response = await fetch(UmamiUrl +"/api/collect", request);
var js = await response.text();
response = new Response(js, {
headers: {
...response.headers,
...corsHeaders,
'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers'),
},
});
return response;
}
由于百度云加速的限制,暂时无法验证是否可以使用,但是可以参考另外一篇博文,里面做了cloudflare的worker演示。
混淆js实现隐私防护屏蔽umami等统计
对于国内的一些隐私防护插件可以根据umami等统计脚本的代码特征进行识别从而屏蔽。
我们可以修改umami脚本后进行混淆,达到不被拦截的效果,当然,前提也是不要用很明显的二级、一级域名告诉别人这是统计用的。
原来的统计代码:
!function(){"use strict";var t=function(t,e,n){var a=t[e];return function(){for(var e=[],i=arguments.length;i--;)e[i]=arguments[i];return n.apply(null,e),a.apply(t,e)}},e=function(){var t=window.doNotTrack,e=window.navigator,n=window.external,a="msTrackingProtectionEnabled",i=t||e.doNotTrack||e.msDoNotTrack||n&&a in n&&n[a]();return"1"==i||"yes"===i};!function(n){var a=n.screen,i=a.width,r=a.height,o=n.navigator.language,c=n.location,s=c.hostname,u=c.pathname,l=c.search,d=n.localStorage,f=n.document,v=n.history,p=f.querySelector("script[data-website-id]");if(p){var m,g,h=p.getAttribute.bind(p),y=h("data-website-id"),w=h("data-host-url"),b="false"!==h("data-auto-track"),S=h("data-do-not-track"),k="false"!==h("data-css-events"),E=h("data-domains")||"",N=E.split(",").map((function(t){return t.trim()})),T=/^umami--([a-z]+)--([\w]+[\w-]*)$/,q="[class*='umami--']",A=function(){return d&&d.getItem("umami.disabled")||S&&e()||E&&!N.includes(s)},O=w?(m=w)&&m.length>1&&m.endsWith("/")?m.slice(0,-1):m:p.src.split("/").slice(0,-1).join("/"),j=i+"x"+r,L={},_=""+u+l,x=f.referrer,H=function(){return{website:y,hostname:s,screen:j,language:o,url:_}},R=function(t,e){return Object.keys(e).forEach((function(n){t[n]=e[n]})),t},J=function(t,e){A()||function(t,e,n){var a=new XMLHttpRequest;a.open("POST",t,!0),a.setRequestHeader("Content-Type","application/json"),g&&a.setRequestHeader("x-umami-cache",g),a.onreadystatechange=function(){4===a.readyState&&n(a.response)},a.send(JSON.stringify(e))}(O+"/api/collect",{type:t,payload:e},(function(t){return g=t}))},M=function(t,e,n){void 0===t&&(t=_),void 0===e&&(e=x),void 0===n&&(n=y),J("pageview",R(H(),{website:n,url:t,referrer:e}))},P=function(t,e,n,a){void 0===e&&(e="custom"),void 0===n&&(n=_),void 0===a&&(a=y),J("event",R(H(),{website:a,url:n,event_type:e,event_value:t}))},z=function(t){var e=t.querySelectorAll(q);Array.prototype.forEach.call(e,B)},B=function(t){(t.getAttribute("class")||"").split(" ").forEach((function(e){if(T.test(e)){var n=e.split("--"),a=n[1],i=n[2],r=L[e]?L[e]:L[e]=function(){"A"===t.tagName?function(t,e){var n=H();n.event_type=e,n.event_value=t;var a=JSON.stringify({type:"event",payload:n});navigator.sendBeacon(O+"/api/collect",a)}(i,a):P(i,a)};t.addEventListener(a,r,!0)}}))},C=function(t,e,n){if(n){x=_;var a=n.toString();(_="http"===a.substring(0,4)?"/"+a.split("/").splice(3).join("/"):a)!==x&&M()}};if(!n.umami){var D=function(t){return P(t)};D.trackView=M,D.trackEvent=P,n.umami=D}if(b&&!A()){v.pushState=t(v,"pushState",C),v.replaceState=t(v,"replaceState",C);var I=function(){"complete"===f.readyState&&(M(),k&&(z(f),new MutationObserver((function(t){t.forEach((function(t){var e=t.target;B(e),z(e)}))})).observe(f,{childList:!0,subtree:!0})))};f.addEventListener("readystatechange",I,!0),I()}}}(window)}();
整理一下:
!(function () {
"use strict";
var t = function (t, e, n) {
var a = t[e];
return function () {
for (var e = [], i = arguments.length; i--; ) e[i] = arguments[i];
return n.apply(null, e), a.apply(t, e);
};
},
e = function () {
var t = window.doNotTrack,
e = window.navigator,
n = window.external,
a = "msTrackingProtectionEnabled",
i = t || e.doNotTrack || e.msDoNotTrack || (n && a in n && n[a]());
return "1" == i || "yes" === i;
};
!(function (n) {
var a = n.screen,
i = a.width,
r = a.height,
o = n.navigator.language,
c = n.location,
s = c.hostname,
u = c.pathname,
l = c.search,
d = n.localStorage,
f = n.document,
v = n.history,
p = f.querySelector("script[data-website-id]");
if (p) {
var m,
g,
h = p.getAttribute.bind(p),
y = h("data-website-id"),
w = h("data-host-url"),
b = "false" !== h("data-auto-track"),
S = h("data-do-not-track"),
k = "false" !== h("data-css-events"),
E = h("data-domains") || "",
N = E.split(",").map(function (t) {
return t.trim();
}),
T = /^umami--([a-z]+)--([\w]+[\w-]*)$/,
q = "[class*='umami--']",
A = function () {
return (
(d && d.getItem("umami.disabled")) ||
(S && e()) ||
(E && !N.includes(s))
);
},
O = w
? (m = w) && m.length > 1 && m.endsWith("/")
? m.slice(0, -1)
: m
: p.src.split("/").slice(0, -1).join("/"),
j = i + "x" + r,
L = {},
_ = "" + u + l,
x = f.referrer,
H = function () {
return { website: y, hostname: s, screen: j, language: o, url: _ };
},
R = function (t, e) {
return (
Object.keys(e).forEach(function (n) {
t[n] = e[n];
}),
t
);
},
J = function (t, e) {
A() ||
(function (t, e, n) {
var a = new XMLHttpRequest();
a.open("POST", t, !0),
a.setRequestHeader("Content-Type", "application/json"),
g && a.setRequestHeader("x-umami-cache", g),
(a.onreadystatechange = function () {
4 === a.readyState && n(a.response);
}),
a.send(JSON.stringify(e));
})(O + "/api/collect", { type: t, payload: e }, function (t) {
return (g = t);
});
},
M = function (t, e, n) {
void 0 === t && (t = _),
void 0 === e && (e = x),
void 0 === n && (n = y),
J("pageview", R(H(), { website: n, url: t, referrer: e }));
},
P = function (t, e, n, a) {
void 0 === e && (e = "custom"),
void 0 === n && (n = _),
void 0 === a && (a = y),
J(
"event",
R(H(), { website: a, url: n, event_type: e, event_value: t })
);
},
z = function (t) {
var e = t.querySelectorAll(q);
Array.prototype.forEach.call(e, B);
},
B = function (t) {
(t.getAttribute("class") || "").split(" ").forEach(function (e) {
if (T.test(e)) {
var n = e.split("--"),
a = n[1],
i = n[2],
r = L[e]
? L[e]
: (L[e] = function () {
"A" === t.tagName
? (function (t, e) {
var n = H();
(n.event_type = e), (n.event_value = t);
var a = JSON.stringify({
type: "event",
payload: n
});
navigator.sendBeacon(O + "/api/collect", a);
})(i, a)
: P(i, a);
});
t.addEventListener(a, r, !0);
}
});
},
C = function (t, e, n) {
if (n) {
x = _;
var a = n.toString();
(_ =
"http" === a.substring(0, 4)
? "/" + a.split("/").splice(3).join("/")
: a) !== x && M();
}
};
if (!n.umami) {
var D = function (t) {
return P(t);
};
(D.trackView = M), (D.trackEvent = P), (n.umami = D);
}
if (b && !A()) {
(v.pushState = t(v, "pushState", C)),
(v.replaceState = t(v, "replaceState", C));
var I = function () {
"complete" === f.readyState &&
(M(),
k &&
(z(f),
new MutationObserver(function (t) {
t.forEach(function (t) {
var e = t.target;
B(e), z(e);
});
}).observe(f, { childList: !0, subtree: !0 })));
};
f.addEventListener("readystatechange", I, !0), I();
}
}
})(window);
})();
我们可以看到,每次请求前都是O + '/api'
进行的URL拼接,所以我们找到变量O的位置进行修改即可。
O = w
? (m = w) && m.length > 1 && m.endsWith("/")
? m.slice(0, -1)
: m
: p.src.split("/").slice(0, -1).join("/"),
我们将这里的O改成我们自己的umami域名,成了这样:
!(function () {
"use strict";
var t = function (t, e, n) {
var a = t[e];
return function () {
for (var e = [], i = arguments.length; i--; ) e[i] = arguments[i];
return n.apply(null, e), a.apply(t, e);
};
},
e = function () {
var t = window.doNotTrack,
e = window.navigator,
n = window.external,
a = "msTrackingProtectionEnabled",
i = t || e.doNotTrack || e.msDoNotTrack || (n && a in n && n[a]());
return "1" == i || "yes" === i;
};
!(function (n) {
var a = n.screen,
i = a.width,
r = a.height,
o = n.navigator.language,
c = n.location,
s = c.hostname,
u = c.pathname,
l = c.search,
d = n.localStorage,
f = n.document,
v = n.history,
p = f.querySelector("script[data-website-id]");
if (p) {
var m,
g,
h = p.getAttribute.bind(p),
y = h("data-website-id"),
w = h("data-host-url"),
b = "false" !== h("data-auto-track"),
S = h("data-do-not-track"),
k = "false" !== h("data-css-events"),
E = h("data-domains") || "",
N = E.split(",").map(function (t) {
return t.trim();
}),
T = /^umami--([a-z]+)--([\w]+[\w-]*)$/,
q = "[class*='umami--']",
A = function () {
return (
(d && d.getItem("umami.disabled")) ||
(S && e()) ||
(E && !N.includes(s))
);
},
O = 'https://www.dwt.life',
j = i + "x" + r,
L = {},
_ = "" + u + l,
x = f.referrer,
H = function () {
return { website: y, hostname: s, screen: j, language: o, url: _ };
},
R = function (t, e) {
return (
Object.keys(e).forEach(function (n) {
t[n] = e[n];
}),
t
);
},
J = function (t, e) {
A() ||
(function (t, e, n) {
var a = new XMLHttpRequest();
a.open("POST", t, !0),
a.setRequestHeader("Content-Type", "application/json"),
g && a.setRequestHeader("x-umami-cache", g),
(a.onreadystatechange = function () {
4 === a.readyState && n(a.response);
}),
a.send(JSON.stringify(e));
})(O + "/api/collect", { type: t, payload: e }, function (t) {
return (g = t);
});
},
M = function (t, e, n) {
void 0 === t && (t = _),
void 0 === e && (e = x),
void 0 === n && (n = y),
J("pageview", R(H(), { website: n, url: t, referrer: e }));
},
P = function (t, e, n, a) {
void 0 === e && (e = "custom"),
void 0 === n && (n = _),
void 0 === a && (a = y),
J(
"event",
R(H(), { website: a, url: n, event_type: e, event_value: t })
);
},
z = function (t) {
var e = t.querySelectorAll(q);
Array.prototype.forEach.call(e, B);
},
B = function (t) {
(t.getAttribute("class") || "").split(" ").forEach(function (e) {
if (T.test(e)) {
var n = e.split("--"),
a = n[1],
i = n[2],
r = L[e]
? L[e]
: (L[e] = function () {
"A" === t.tagName
? (function (t, e) {
var n = H();
(n.event_type = e), (n.event_value = t);
var a = JSON.stringify({
type: "event",
payload: n
});
navigator.sendBeacon(O + "/api/collect", a);
})(i, a)
: P(i, a);
});
t.addEventListener(a, r, !0);
}
});
},
C = function (t, e, n) {
if (n) {
x = _;
var a = n.toString();
(_ =
"http" === a.substring(0, 4)
? "/" + a.split("/").splice(3).join("/")
: a) !== x && M();
}
};
if (!n.umami) {
var D = function (t) {
return P(t);
};
(D.trackView = M), (D.trackEvent = P), (n.umami = D);
}
if (b && !A()) {
(v.pushState = t(v, "pushState", C)),
(v.replaceState = t(v, "replaceState", C));
var I = function () {
"complete" === f.readyState &&
(M(),
k &&
(z(f),
new MutationObserver(function (t) {
t.forEach(function (t) {
var e = t.target;
B(e), z(e);
});
}).observe(f, { childList: !0, subtree: !0 })));
};
f.addEventListener("readystatechange", I, !0), I();
}
}
})(window);
})();
最后使用在线工具混淆一下:
- https://www.sojson.com/
- https://tool.lu/js
我们需要调试,所以选择tool.lu,
新建一个u.js
,保存加密的js
再创建一个页面引用js:
最后效果:
因为我用的博客域名,实际换成umami即可。