/** * Normalizes a given URL by performing the following operations: * * 1. Ensures that the URL has a protocol by defaulting to 'wss://' if no protocol is provided. * 2. Creates a `URL` object to easily manipulate and normalize the URL components. * 3. Normalizes the pathname by: * - Replacing multiple consecutive slashes with a single slash. * - Removing the trailing slash if it exists. * 4. Removes the port number if it is the default port for the protocol: * - Port `80` for 'ws:' (WebSocket) protocol. * - Port `443` for 'wss:' (WebSocket Secure) protocol. * 5. Sorts the query parameters alphabetically. * 6. Clears any fragment (hash) identifier from the URL. * * @param urlString - The URL string to be normalized. * @returns A normalized URL string. */ export function normalizeWebSocketURL(urlString: string): string { // If the URL string does not contain a protocol (e.g., "http://", "https://"), // prepend "wss://" (WebSocket Secure) by default. if (urlString.indexOf('://') === -1) urlString = 'wss://' + urlString // Create a URL object from the provided URL string. const url = new URL(urlString) // Normalize the pathname by replacing multiple consecutive slashes with a single slash. url.pathname = url.pathname.replace(/\/+/g, '/') // Remove the trailing slash from the pathname if it exists. if (url.pathname.endsWith('/')) url.pathname = url.pathname.slice(0, -1) // Remove the port number if it is 80 for "ws:" protocol or 443 for "wss:" protocol, as these are default ports. if ( (url.port === '80' && url.protocol === 'ws:') || (url.port === '443' && url.protocol === 'wss:') ) url.port = '' // Sort the search parameters alphabetically. url.searchParams.sort() // Clear any hash fragment from the URL. url.hash = '' // Return the normalized URL as a string. return url.toString() } export const isValidUrl = (url: string) => { try { new URL(url) return true } catch (_) { return false } } export const isValidImageUrl = (url: string) => { const regex = /\.(jpeg|jpg|gif|png)$/ return regex.test(url) } export const isReachable = async (url: string) => { try { const response = await fetch(url, { method: 'HEAD' }) return response.ok } catch (error) { return false } } /** * Extracts a filename from a given URL. * * @param url - The URL from which to extract the filename. * @returns The filename extracted from the URL. If no filename can be extracted, a default name is provided. */ export const getFilenameFromUrl = (url: string): string => { // Create a URL object to parse the provided URL string const urlObj = new URL(url) // Extract the pathname from the URL object const pathname = urlObj.pathname // Extract the filename from the pathname. The filename is the last segment after the last '/' // If pathname is empty or does not end with a filename, use 'downloaded_file' as the default const filename = pathname.substring(pathname.lastIndexOf('/') + 1) || 'downloaded_file' // Return the extracted filename return filename } /** * Downloads a file from the given URL. * * @param url - The URL of the file to download. * @param filename - The name of the file to save as. */ export const downloadFile = (url: string, filename: string) => { // Create a temporary anchor element const a = document.createElement('a') // Set the href attribute to the file's URL a.href = url // Set the download attribute with the desired file name a.download = filename // Set target="_blank" to ensure that link opens in new tab a.setAttribute('target', '_blank') // Append the anchor to the body (not displayed) document.body.appendChild(a) // Programmatically trigger a click event on the anchor to start the download a.click() // Remove the anchor from the document document.body.removeChild(a) }