From 95a563b22a8c90b022d8f811ab6c9dd83510fc76 Mon Sep 17 00:00:00 2001 From: iamsivin Date: Wed, 30 Jul 2025 08:57:33 +0530 Subject: [PATCH] feat: Add `isValidDomain` helper for validation --- package.json | 2 +- src/index.ts | 3 ++- src/url.ts | 15 +++++++++++++++ test/url.test.ts | 45 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2089379..f614f58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/utils", - "version": "0.0.48", + "version": "0.0.49", "description": "Chatwoot utils", "private": false, "license": "MIT", diff --git a/src/index.ts b/src/index.ts index 5cf5eb6..1f04d59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import { formatNumber, } from './helpers'; -import { toURL, isSameHost } from './url'; +import { toURL, isSameHost, isValidDomain } from './url'; import { getRecipients } from './email'; @@ -62,6 +62,7 @@ export { splitName, toURL, isSameHost, + isValidDomain, trimContent, downloadFile, getFileInfo, diff --git a/src/url.ts b/src/url.ts index ac9a116..91aa012 100644 --- a/src/url.ts +++ b/src/url.ts @@ -50,3 +50,18 @@ export const isSameHost = ( return false; } }; + +/** + * Check if a string is a valid domain name. + * An empty string is allowed and considered valid. + * + * @param domain Domain to validate. + * @returns Whether the domain matches the rules. + */ +export const isValidDomain = (domain: string): boolean => { + if (domain === '') return true; + + const domainRegex = /^(?!-)(?!.*--)[\p{L}0-9-]{1,63}(? { it('returns null for falsy inputs', () => { @@ -80,3 +80,46 @@ describe('isSameHost', () => { expect(isSameHost(url1, url3)).toBe(false); }); }); + +describe('#isValidDomain', () => { + it('should return true for valid domains', () => { + expect(isValidDomain('')).toBe(true); + expect(isValidDomain('google.com')).toBe(true); + expect(isValidDomain('example.org')).toBe(true); + expect(isValidDomain('test.net')).toBe(true); + expect(isValidDomain('www.google.com')).toBe(true); + expect(isValidDomain('api.example.org')).toBe(true); + expect(isValidDomain('deep.nested.subdomain.com')).toBe(true); + expect(isValidDomain('my-site.com')).toBe(true); + expect(isValidDomain('test-domain.co.uk')).toBe(true); + expect(isValidDomain('123domain.org')).toBe(true); + expect(isValidDomain('测试.网络')).toBe(true); + expect(isValidDomain('пример.рф')).toBe(true); + expect(isValidDomain('example.co.uk')).toBe(true); + }); + + it('should return false for invalid domains', () => { + const longDomain = 'a'.repeat(250) + '.com'; + expect(isValidDomain('localhost')).toBe(false); + expect(isValidDomain('example.com!')).toBe(false); + expect(isValidDomain('test@domain.com')).toBe(false); + expect(isValidDomain('domain with spaces.com')).toBe(false); + expect(isValidDomain('test_domain.com')).toBe(false); + expect(isValidDomain('example..com')).toBe(false); + expect(isValidDomain('..example.com')).toBe(false); + expect(isValidDomain('example.com..')).toBe(false); + expect(isValidDomain('-example-.com')).toBe(false); + expect(isValidDomain(longDomain)).toBe(false); + expect(isValidDomain('192.168.1.1')).toBe(false); + expect(isValidDomain('127.0.0.1')).toBe(false); + expect(isValidDomain('example.com/path/to/page')).toBe(false); + expect(isValidDomain('http://example.com')).toBe(false); + expect(isValidDomain('https://example.com')).toBe(false); + expect(isValidDomain((null as unknown) as string)).toBe(false); + expect(isValidDomain((undefined as unknown) as string)).toBe(false); + expect(isValidDomain((123 as unknown) as string)).toBe(false); + expect(isValidDomain(({} as unknown) as string)).toBe(false); + expect(isValidDomain(([] as unknown) as string)).toBe(false); + expect(isValidDomain(' ')).toBe(false); + }); +});