url.ts raw

   1  export function isWebsocketUrl(url: string): boolean {
   2    try {
   3      const protocol = new URL(url).protocol
   4      return protocol === 'ws:' || protocol === 'wss:'
   5    } catch {
   6      return false
   7    }
   8  }
   9  
  10  export function isOnionUrl(url: string): boolean {
  11    try {
  12      const hostname = new URL(url).hostname
  13      return hostname.endsWith('.onion')
  14    } catch {
  15      return false
  16    }
  17  }
  18  
  19  // copy from nostr-tools/utils
  20  export function normalizeUrl(url: string): string {
  21    try {
  22      if (url.indexOf('://') === -1) {
  23        if (
  24          url.startsWith('localhost:') ||
  25          url.startsWith('localhost/') ||
  26          url.startsWith('127.') ||
  27          url.startsWith('192.168.')
  28        ) {
  29          url = 'ws://' + url
  30        } else {
  31          url = 'wss://' + url
  32        }
  33      }
  34      const p = new URL(url)
  35      p.pathname = p.pathname.replace(/\/+/g, '/')
  36      if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)
  37      if (p.protocol === 'https:') {
  38        p.protocol = 'wss:'
  39      } else if (p.protocol === 'http:') {
  40        p.protocol = 'ws:'
  41      }
  42      if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) {
  43        p.port = ''
  44      }
  45      p.searchParams.sort()
  46      p.hash = ''
  47      return p.toString()
  48    } catch {
  49      console.error('Invalid URL:', url)
  50      return ''
  51    }
  52  }
  53  
  54  export function normalizeHttpUrl(url: string): string {
  55    try {
  56      if (url.indexOf('://') === -1) url = 'https://' + url
  57      const p = new URL(url)
  58      p.pathname = p.pathname.replace(/\/+/g, '/')
  59      if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)
  60      if (p.protocol === 'wss:') {
  61        p.protocol = 'https:'
  62      } else if (p.protocol === 'ws:') {
  63        p.protocol = 'http:'
  64      }
  65      if (
  66        (p.port === '80' && p.protocol === 'http:') ||
  67        (p.port === '443' && p.protocol === 'https:')
  68      ) {
  69        p.port = ''
  70      }
  71      p.searchParams.sort()
  72      p.hash = ''
  73      return p.toString()
  74    } catch {
  75      console.error('Invalid URL:', url)
  76      return ''
  77    }
  78  }
  79  
  80  export function simplifyUrl(url: string): string {
  81    return url
  82      .replace('wss://', '')
  83      .replace('ws://', '')
  84      .replace('https://', '')
  85      .replace('http://', '')
  86      .replace(/\/$/, '')
  87  }
  88  
  89  export function isLocalNetworkUrl(urlString: string): boolean {
  90    try {
  91      const url = new URL(urlString)
  92      const hostname = url.hostname
  93  
  94      // Check if it's localhost
  95      if (hostname === 'localhost' || hostname === '::1') {
  96        return true
  97      }
  98  
  99      // Check if it's an IPv4 local network address
 100      const ipv4Match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)
 101      if (ipv4Match) {
 102        const [, a, b, c, d] = ipv4Match.map(Number)
 103        return (
 104          a === 10 ||
 105          (a === 172 && b >= 16 && b <= 31) ||
 106          (a === 192 && b === 168) ||
 107          (a === 127 && b === 0 && c === 0 && d === 1)
 108        )
 109      }
 110  
 111      // Check if it's an IPv6 address
 112      if (hostname.includes(':')) {
 113        if (hostname === '::1') {
 114          return true // IPv6 loopback address
 115        }
 116        if (hostname.startsWith('fe80:')) {
 117          return true // Link-local address
 118        }
 119        if (hostname.startsWith('fc') || hostname.startsWith('fd')) {
 120          return true // Unique local address (ULA)
 121        }
 122      }
 123  
 124      return false
 125    } catch {
 126      return false // Return false for invalid URLs
 127    }
 128  }
 129  
 130  export function isImage(url: string) {
 131    try {
 132      const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.heic', '.svg']
 133      return imageExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
 134    } catch {
 135      return false
 136    }
 137  }
 138  
 139  export function isMedia(url: string) {
 140    try {
 141      const mediaExtensions = [
 142        '.mp4',
 143        '.webm',
 144        '.ogg',
 145        '.mov',
 146        '.mp3',
 147        '.wav',
 148        '.flac',
 149        '.aac',
 150        '.m4a',
 151        '.opus',
 152        '.wma',
 153        '.3gp'
 154      ]
 155      return mediaExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
 156    } catch {
 157      return false
 158    }
 159  }
 160  
 161  export const truncateUrl = (url: string, maxLength: number = 40) => {
 162    try {
 163      const urlObj = new URL(url)
 164      let domain = urlObj.hostname
 165      let path = urlObj.pathname
 166  
 167      if (domain.startsWith('www.')) {
 168        domain = domain.slice(4)
 169      }
 170  
 171      if (!path || path === '/') {
 172        return domain
 173      }
 174  
 175      if (path.endsWith('/')) {
 176        path = path.slice(0, -1)
 177      }
 178  
 179      const u = domain + path
 180  
 181      if (u.length > maxLength) {
 182        return domain + path.slice(0, maxLength - domain.length - 3) + '...'
 183      }
 184  
 185      return u
 186    } catch {
 187      // invalid URL
 188      let truncated = url
 189      if (truncated.startsWith('https://')) {
 190        truncated = truncated.slice(8)
 191      } else if (truncated.startsWith('http://')) {
 192        truncated = truncated.slice(7)
 193      }
 194      if (truncated.startsWith('www.')) {
 195        truncated = truncated.slice(4)
 196      }
 197      if (truncated.length > maxLength) {
 198        return truncated.slice(0, maxLength - 3) + '...'
 199      }
 200      return truncated
 201    }
 202  }
 203