mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-06 03:26:51 +03:00
feat: Introduce fe modular build system
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
.idea
|
.idea
|
||||||
|
fe-app-podkop/node_modules
|
||||||
|
|||||||
8
fe-app-podkop/.prettierrc
Normal file
8
fe-app-podkop/.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true
|
||||||
|
}
|
||||||
18
fe-app-podkop/eslint.config.js
Normal file
18
fe-app-podkop/eslint.config.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// eslint.config.js
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
ignores: ['dist', 'node_modules'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'no-console': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prettier,
|
||||||
|
];
|
||||||
25
fe-app-podkop/package.json
Normal file
25
fe-app-podkop/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "fe-app-podkop",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write src",
|
||||||
|
"lint": "eslint src --ext .ts,.tsx",
|
||||||
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
||||||
|
"build": "tsup src/main.ts",
|
||||||
|
"dev": "tsup src/main.ts --watch",
|
||||||
|
"test": "vitest"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||||
|
"@typescript-eslint/parser": "8.45.0",
|
||||||
|
"eslint": "9.36.0",
|
||||||
|
"eslint-config-prettier": "10.1.8",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"tsup": "8.5.0",
|
||||||
|
"typescript": "5.9.3",
|
||||||
|
"typescript-eslint": "8.45.0",
|
||||||
|
"vitest": "3.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
fe-app-podkop/src/main.ts
Normal file
4
fe-app-podkop/src/main.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
'use strict';
|
||||||
|
'require baseclass';
|
||||||
|
|
||||||
|
export * from './validators';
|
||||||
4
fe-app-podkop/src/validators/index.ts
Normal file
4
fe-app-podkop/src/validators/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './validateIp';
|
||||||
|
export * from './validateDomain';
|
||||||
|
export * from './validateDns';
|
||||||
|
export * from './validateUrl';
|
||||||
24
fe-app-podkop/src/validators/tests/validateDns.test.js
Normal file
24
fe-app-podkop/src/validators/tests/validateDns.test.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { validateDNS } from '../validateDns.js';
|
||||||
|
import { invalidIPs, validIPs } from './validateIp.test';
|
||||||
|
import { invalidDomains, validDomains } from './validateDomain.test';
|
||||||
|
|
||||||
|
const validDns = [...validIPs, ...validDomains];
|
||||||
|
|
||||||
|
const invalidDns = [...invalidIPs, ...invalidDomains];
|
||||||
|
|
||||||
|
describe('validateDns', () => {
|
||||||
|
describe.each(validDns)('Valid dns: %s', (_desc, domain) => {
|
||||||
|
it(`returns valid=true for "${domain}"`, () => {
|
||||||
|
const res = validateDNS(domain);
|
||||||
|
expect(res.valid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(invalidDns)('Invalid dns: %s', (_desc, domain) => {
|
||||||
|
it(`returns valid=false for "${domain}"`, () => {
|
||||||
|
const res = validateDNS(domain);
|
||||||
|
expect(res.valid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
46
fe-app-podkop/src/validators/tests/validateDomain.test.js
Normal file
46
fe-app-podkop/src/validators/tests/validateDomain.test.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { validateDomain } from '../validateDomain';
|
||||||
|
|
||||||
|
export const validDomains = [
|
||||||
|
['Simple domain', 'example.com'],
|
||||||
|
['Subdomain', 'sub.example.com'],
|
||||||
|
['With dash', 'my-site.org'],
|
||||||
|
['With numbers', 'site123.net'],
|
||||||
|
['Deep subdomain', 'a.b.c.example.co.uk'],
|
||||||
|
['With path', 'example.com/path/to/resource'],
|
||||||
|
['Punycode RU', 'xn--d1acufc.xn--p1ai'],
|
||||||
|
['Adguard dns', 'dns.adguard-dns.com'],
|
||||||
|
['Nextdns dns', 'dns.nextdns.io/xxxxxxx'],
|
||||||
|
['Long domain (63 chars in label)', 'a'.repeat(63) + '.com'],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const invalidDomains = [
|
||||||
|
['No TLD', 'localhost'],
|
||||||
|
['Only TLD', '.com'],
|
||||||
|
['Double dot', 'example..com'],
|
||||||
|
['Illegal chars', 'exa!mple.com'],
|
||||||
|
['Space inside', 'exa mple.com'],
|
||||||
|
['Ending with dash', 'example-.com'],
|
||||||
|
['Starting with dash', '-example.com'],
|
||||||
|
['Trailing dot', 'example.com.'],
|
||||||
|
['Too short TLD', 'example.c'],
|
||||||
|
['With protocol (not allowed)', 'http://example.com'],
|
||||||
|
['Too long label (>63 chars)', 'a'.repeat(64) + '.com'],
|
||||||
|
['Too long domain (>253 chars)', Array(40).fill('abcdef').join('.') + '.com'],
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('validateDomain', () => {
|
||||||
|
describe.each(validDomains)('Valid domain: %s', (_desc, domain) => {
|
||||||
|
it(`returns valid=true for "${domain}"`, () => {
|
||||||
|
const res = validateDomain(domain);
|
||||||
|
expect(res.valid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(invalidDomains)('Invalid domain: %s', (_desc, domain) => {
|
||||||
|
it(`returns valid=false for "${domain}"`, () => {
|
||||||
|
const res = validateDomain(domain);
|
||||||
|
expect(res.valid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
38
fe-app-podkop/src/validators/tests/validateIp.test.js
Normal file
38
fe-app-podkop/src/validators/tests/validateIp.test.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { validateIPV4 } from '../validateIp';
|
||||||
|
|
||||||
|
export const validIPs = [
|
||||||
|
['Private LAN', '192.168.1.1'],
|
||||||
|
['All zeros', '0.0.0.0'],
|
||||||
|
['Broadcast', '255.255.255.255'],
|
||||||
|
['Simple', '1.2.3.4'],
|
||||||
|
['Loopback', '127.0.0.1'],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const invalidIPs = [
|
||||||
|
['Octet too large', '256.0.0.1'],
|
||||||
|
['Too few octets', '192.168.1'],
|
||||||
|
['Too many octets', '1.2.3.4.5'],
|
||||||
|
['Leading zero (1st octet)', '01.2.3.4'],
|
||||||
|
['Leading zero (2nd octet)', '1.02.3.4'],
|
||||||
|
['Leading zero (3rd octet)', '1.2.003.4'],
|
||||||
|
['Leading zero (4th octet)', '1.2.3.004'],
|
||||||
|
['Four digits in octet', '1.2.3.0004'],
|
||||||
|
['Trailing dot', '1.2.3.'],
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('validateIPV4', () => {
|
||||||
|
describe.each(validIPs)('Valid IP: %s', (_desc, ip) => {
|
||||||
|
it(`returns {valid:true for "${ip}"`, () => {
|
||||||
|
const res = validateIPV4(ip);
|
||||||
|
expect(res.valid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(invalidIPs)('Invalid IP: %s', (_desc, ip) => {
|
||||||
|
it(`returns {valid:false for "${ip}"`, () => {
|
||||||
|
const res = validateIPV4(ip);
|
||||||
|
expect(res.valid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
40
fe-app-podkop/src/validators/tests/validateUrl.test.js
Normal file
40
fe-app-podkop/src/validators/tests/validateUrl.test.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { validateUrl } from '../validateUrl';
|
||||||
|
|
||||||
|
const validUrls = [
|
||||||
|
['Simple HTTP', 'http://example.com'],
|
||||||
|
['Simple HTTPS', 'https://example.com'],
|
||||||
|
['With path', 'https://example.com/path/to/page'],
|
||||||
|
['With query', 'https://example.com/?q=test'],
|
||||||
|
['With port', 'http://example.com:8080'],
|
||||||
|
['With subdomain', 'https://sub.example.com'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const invalidUrls = [
|
||||||
|
['Invalid format', 'not a url'],
|
||||||
|
['Missing protocol', 'example.com'],
|
||||||
|
['Unsupported protocol (ftp)', 'ftp://example.com'],
|
||||||
|
['Unsupported protocol (ws)', 'ws://example.com'],
|
||||||
|
['Empty string', ''],
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('validateUrl', () => {
|
||||||
|
describe.each(validUrls)('Valid URL: %s', (_desc, url) => {
|
||||||
|
it(`returns valid=true for "${url}"`, () => {
|
||||||
|
const res = validateUrl(url);
|
||||||
|
expect(res.valid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(invalidUrls)('Invalid URL: %s', (_desc, url) => {
|
||||||
|
it(`returns valid=false for "${url}"`, () => {
|
||||||
|
const res = validateUrl(url);
|
||||||
|
expect(res.valid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows custom protocol list (ftp)', () => {
|
||||||
|
const res = validateUrl('ftp://example.com', ['ftp:']);
|
||||||
|
expect(res.valid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
4
fe-app-podkop/src/validators/types.ts
Normal file
4
fe-app-podkop/src/validators/types.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ValidationResult {
|
||||||
|
valid: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
23
fe-app-podkop/src/validators/validateDns.ts
Normal file
23
fe-app-podkop/src/validators/validateDns.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { validateDomain } from './validateDomain';
|
||||||
|
import { validateIPV4 } from './validateIp';
|
||||||
|
import { ValidationResult } from './types.js';
|
||||||
|
|
||||||
|
export function validateDNS(value: string): ValidationResult {
|
||||||
|
if (!value) {
|
||||||
|
return { valid: false, message: 'DNS server address cannot be empty' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateIPV4(value).valid) {
|
||||||
|
return { valid: true, message: 'Valid' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateDomain(value).valid) {
|
||||||
|
return { valid: true, message: 'Valid' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message:
|
||||||
|
'Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH',
|
||||||
|
};
|
||||||
|
}
|
||||||
21
fe-app-podkop/src/validators/validateDomain.ts
Normal file
21
fe-app-podkop/src/validators/validateDomain.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { ValidationResult } from './types.js';
|
||||||
|
|
||||||
|
export function validateDomain(domain: string): ValidationResult {
|
||||||
|
const domainRegex =
|
||||||
|
/^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/;
|
||||||
|
|
||||||
|
if (!domainRegex.test(domain)) {
|
||||||
|
return { valid: false, message: 'Invalid domain address' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostname = domain.split('/')[0];
|
||||||
|
const parts = hostname.split('.');
|
||||||
|
|
||||||
|
const atLeastOneInvalidPart = parts.some((part) => part.length > 63);
|
||||||
|
|
||||||
|
if (atLeastOneInvalidPart) {
|
||||||
|
return { valid: false, message: 'Invalid domain address' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true, message: 'Valid' };
|
||||||
|
}
|
||||||
12
fe-app-podkop/src/validators/validateIp.ts
Normal file
12
fe-app-podkop/src/validators/validateIp.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ValidationResult } from './types.js';
|
||||||
|
|
||||||
|
export function validateIPV4(ip: string): ValidationResult {
|
||||||
|
const ipRegex =
|
||||||
|
/^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;
|
||||||
|
|
||||||
|
if (ipRegex.test(ip)) {
|
||||||
|
return { valid: true, message: 'Valid' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: false, message: 'Invalid IP address' };
|
||||||
|
}
|
||||||
20
fe-app-podkop/src/validators/validateUrl.ts
Normal file
20
fe-app-podkop/src/validators/validateUrl.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ValidationResult } from './types.js';
|
||||||
|
|
||||||
|
export function validateUrl(
|
||||||
|
url: string,
|
||||||
|
protocols: string[] = ['http:', 'https:'],
|
||||||
|
): ValidationResult {
|
||||||
|
try {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
|
if (!protocols.includes(parsedUrl.protocol)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: `URL must use one of the following protocols: ${protocols.join(', ')}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { valid: true, message: 'Valid' };
|
||||||
|
} catch (e) {
|
||||||
|
return { valid: false, message: 'Invalid URL format' };
|
||||||
|
}
|
||||||
|
}
|
||||||
13
fe-app-podkop/tsconfig.json
Normal file
13
fe-app-podkop/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
35
fe-app-podkop/tsup.config.ts
Normal file
35
fe-app-podkop/tsup.config.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/main.ts'],
|
||||||
|
format: ['esm'], // пусть tsup генерит export {...}
|
||||||
|
outDir: '../luci-app-podkop/htdocs/luci-static/resources/view/podkop',
|
||||||
|
outExtension: () => ({ js: '.js' }),
|
||||||
|
dts: false,
|
||||||
|
clean: false,
|
||||||
|
sourcemap: false,
|
||||||
|
banner: {
|
||||||
|
js: `// This file is autogenerated, please don't change manually \n"use strict";`,
|
||||||
|
},
|
||||||
|
esbuildOptions(options) {
|
||||||
|
options.legalComments = 'none';
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
const outDir =
|
||||||
|
'../luci-app-podkop/htdocs/luci-static/resources/view/podkop';
|
||||||
|
const file = path.join(outDir, 'main.js');
|
||||||
|
let code = fs.readFileSync(file, 'utf8');
|
||||||
|
|
||||||
|
code = code.replace(
|
||||||
|
/export\s*{([\s\S]*?)}/,
|
||||||
|
(match, group) => {
|
||||||
|
return `return baseclass.extend({${group}})`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(file, code, 'utf8');
|
||||||
|
console.log(`✅ Patched LuCI build: ${file}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
8
fe-app-podkop/vitest.config.js
Normal file
8
fe-app-podkop/vitest.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'node',
|
||||||
|
},
|
||||||
|
});
|
||||||
1857
fe-app-podkop/yarn.lock
Normal file
1857
fe-app-podkop/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,9 @@
|
|||||||
'require ui';
|
'require ui';
|
||||||
'require network';
|
'require network';
|
||||||
'require view.podkop.constants as constants';
|
'require view.podkop.constants as constants';
|
||||||
|
'require view.podkop.main as main';
|
||||||
'require tools.widgets as widgets';
|
'require tools.widgets as widgets';
|
||||||
|
|
||||||
function validateUrl(url, protocols = ['http:', 'https:']) {
|
|
||||||
try {
|
|
||||||
const parsedUrl = new URL(url);
|
|
||||||
if (!protocols.includes(parsedUrl.protocol)) {
|
|
||||||
return _('URL must use one of the following protocols: ') + protocols.join(', ');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return _('Invalid URL format');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createConfigSection(section, map, network) {
|
function createConfigSection(section, map, network) {
|
||||||
const s = section;
|
const s = section;
|
||||||
@@ -438,8 +428,18 @@ function createConfigSection(section, map, network) {
|
|||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
o.validate = function (section_id, value) {
|
o.validate = function (section_id, value) {
|
||||||
if (!value || value.length === 0) return true;
|
// Optional
|
||||||
return validateUrl(value);
|
if (!value || value.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = main.validateUrl(url);
|
||||||
|
|
||||||
|
if (validation.valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _(validation.message)
|
||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption('basic', form.Flag, 'local_subnet_lists_enabled', _('Local Subnet Lists'), _('Use the list from the router filesystem'));
|
o = s.taboption('basic', form.Flag, 'local_subnet_lists_enabled', _('Local Subnet Lists'), _('Use the list from the router filesystem'));
|
||||||
@@ -562,8 +562,18 @@ function createConfigSection(section, map, network) {
|
|||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
o.validate = function (section_id, value) {
|
o.validate = function (section_id, value) {
|
||||||
if (!value || value.length === 0) return true;
|
// Optional
|
||||||
return validateUrl(value);
|
if (!value || value.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = main.validateUrl(url);
|
||||||
|
|
||||||
|
if (validation.valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _(validation.message)
|
||||||
};
|
};
|
||||||
|
|
||||||
o = s.taboption('basic', form.Flag, 'all_traffic_from_ip_enabled', _('IP for full redirection'), _('Specify local IP addresses whose traffic will always use the configured route'));
|
o = s.taboption('basic', form.Flag, 'all_traffic_from_ip_enabled', _('IP for full redirection'), _('Specify local IP addresses whose traffic will always use the configured route'));
|
||||||
@@ -591,4 +601,4 @@ function createConfigSection(section, map, network) {
|
|||||||
|
|
||||||
return baseclass.extend({
|
return baseclass.extend({
|
||||||
createConfigSection
|
createConfigSection
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// This file is autogenerated, please don't change manually
|
||||||
|
"use strict";
|
||||||
|
"require baseclass";
|
||||||
|
|
||||||
|
// src/validators/validateIp.ts
|
||||||
|
function validateIPV4(ip) {
|
||||||
|
const ipRegex = /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;
|
||||||
|
if (ipRegex.test(ip)) {
|
||||||
|
return { valid: true, message: "Valid" };
|
||||||
|
}
|
||||||
|
return { valid: false, message: "Invalid IP address" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/validators/validateDomain.ts
|
||||||
|
function validateDomain(domain) {
|
||||||
|
const domainRegex = /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/;
|
||||||
|
if (!domainRegex.test(domain)) {
|
||||||
|
return { valid: false, message: "Invalid domain address" };
|
||||||
|
}
|
||||||
|
const hostname = domain.split("/")[0];
|
||||||
|
const parts = hostname.split(".");
|
||||||
|
const atLeastOneInvalidPart = parts.some((part) => part.length > 63);
|
||||||
|
if (atLeastOneInvalidPart) {
|
||||||
|
return { valid: false, message: "Invalid domain address" };
|
||||||
|
}
|
||||||
|
return { valid: true, message: "Valid" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/validators/validateDns.ts
|
||||||
|
function validateDNS(value) {
|
||||||
|
if (!value) {
|
||||||
|
return { valid: false, message: "DNS server address cannot be empty" };
|
||||||
|
}
|
||||||
|
if (validateIPV4(value).valid) {
|
||||||
|
return { valid: true, message: "Valid" };
|
||||||
|
}
|
||||||
|
if (validateDomain(value).valid) {
|
||||||
|
return { valid: true, message: "Valid" };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/validators/validateUrl.ts
|
||||||
|
function validateUrl(url, protocols = ["http:", "https:"]) {
|
||||||
|
try {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
if (!protocols.includes(parsedUrl.protocol)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: `URL must use one of the following protocols: ${protocols.join(", ")}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { valid: true, message: "Valid" };
|
||||||
|
} catch (e) {
|
||||||
|
return { valid: false, message: "Invalid URL format" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baseclass.extend({
|
||||||
|
validateDNS,
|
||||||
|
validateDomain,
|
||||||
|
validateIPV4,
|
||||||
|
validateUrl
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user