mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
0a001bebcc
- Introduced a new DomainRulesPage component for managing custom and global equivalent domains. - Updated AppMainRoutes to include a route for domain rules. - Added API functions to fetch and save domain rules. - Enhanced localization with new strings for domain rules in multiple languages. - Updated styles for the new domain rules interface and ensured responsiveness. - Added types for domain rules in the TypeScript definitions.
152 lines
2.9 KiB
TypeScript
152 lines
2.9 KiB
TypeScript
const MULTI_LABEL_PUBLIC_SUFFIXES = new Set([
|
|
'ac.cn',
|
|
'com.cn',
|
|
'edu.cn',
|
|
'gov.cn',
|
|
'net.cn',
|
|
'org.cn',
|
|
'ah.cn',
|
|
'bj.cn',
|
|
'cq.cn',
|
|
'fj.cn',
|
|
'gd.cn',
|
|
'gs.cn',
|
|
'gx.cn',
|
|
'gz.cn',
|
|
'ha.cn',
|
|
'hb.cn',
|
|
'he.cn',
|
|
'hi.cn',
|
|
'hk.cn',
|
|
'hl.cn',
|
|
'hn.cn',
|
|
'jl.cn',
|
|
'js.cn',
|
|
'jx.cn',
|
|
'ln.cn',
|
|
'mo.cn',
|
|
'nm.cn',
|
|
'nx.cn',
|
|
'qh.cn',
|
|
'sc.cn',
|
|
'sd.cn',
|
|
'sh.cn',
|
|
'sn.cn',
|
|
'sx.cn',
|
|
'tj.cn',
|
|
'tw.cn',
|
|
'xj.cn',
|
|
'xz.cn',
|
|
'yn.cn',
|
|
'zj.cn',
|
|
'co.uk',
|
|
'org.uk',
|
|
'net.uk',
|
|
'ac.uk',
|
|
'gov.uk',
|
|
'com.au',
|
|
'net.au',
|
|
'org.au',
|
|
'edu.au',
|
|
'gov.au',
|
|
'co.nz',
|
|
'org.nz',
|
|
'net.nz',
|
|
'com.br',
|
|
'com.mx',
|
|
'com.ar',
|
|
'com.tr',
|
|
'com.sg',
|
|
'com.my',
|
|
'com.hk',
|
|
'com.tw',
|
|
'co.jp',
|
|
'ne.jp',
|
|
'or.jp',
|
|
'co.kr',
|
|
'or.kr',
|
|
'co.in',
|
|
'firm.in',
|
|
'net.in',
|
|
'org.in',
|
|
'co.id',
|
|
'or.id',
|
|
'web.id',
|
|
'co.il',
|
|
'org.il',
|
|
'co.za',
|
|
'com.sa',
|
|
'com.ph',
|
|
'com.vn',
|
|
'com.pk',
|
|
'com.bd',
|
|
'com.ng',
|
|
'github.io',
|
|
'pages.dev',
|
|
'workers.dev',
|
|
'cloudflareaccess.com',
|
|
'vercel.app',
|
|
'netlify.app',
|
|
'web.app',
|
|
'firebaseapp.com',
|
|
'herokuapp.com',
|
|
'fly.dev',
|
|
'railway.app',
|
|
'render.com',
|
|
'onrender.com',
|
|
]);
|
|
|
|
function extractHost(input: string): string {
|
|
let raw = input.trim().toLowerCase();
|
|
if (!raw) return '';
|
|
raw = raw.replace(/\\/g, '/');
|
|
|
|
try {
|
|
const candidate = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
const parsed = new URL(candidate);
|
|
raw = parsed.hostname;
|
|
} catch {
|
|
raw = raw.split(/[/?#]/, 1)[0] || '';
|
|
const atIndex = raw.lastIndexOf('@');
|
|
if (atIndex >= 0) raw = raw.slice(atIndex + 1);
|
|
if (raw.startsWith('[')) return '';
|
|
const colonIndex = raw.lastIndexOf(':');
|
|
if (colonIndex > -1 && raw.indexOf(':') === colonIndex) raw = raw.slice(0, colonIndex);
|
|
}
|
|
|
|
return raw
|
|
.replace(/^\*+\./, '')
|
|
.replace(/^\.+/, '')
|
|
.replace(/\.+$/, '');
|
|
}
|
|
|
|
function isValidHost(host: string): boolean {
|
|
if (!host || host.length > 253 || !host.includes('.')) return false;
|
|
if (host.includes('..') || /[:/\s]/.test(host)) return false;
|
|
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) return false;
|
|
return host.split('.').every((label) => (
|
|
label.length > 0
|
|
&& label.length <= 63
|
|
&& /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label)
|
|
));
|
|
}
|
|
|
|
export function normalizeEquivalentDomain(value: unknown): string {
|
|
const host = extractHost(String(value || ''));
|
|
if (!isValidHost(host)) return '';
|
|
|
|
const labels = host.split('.');
|
|
for (let index = 0; index < labels.length; index += 1) {
|
|
const suffix = labels.slice(index).join('.');
|
|
if (!MULTI_LABEL_PUBLIC_SUFFIXES.has(suffix)) continue;
|
|
if (index === 0) return '';
|
|
return labels.slice(index - 1).join('.');
|
|
}
|
|
|
|
return labels.length >= 2 ? labels.slice(-2).join('.') : '';
|
|
}
|
|
|
|
export function isValidEquivalentDomain(value: unknown): boolean {
|
|
return !!normalizeEquivalentDomain(value);
|
|
}
|