diff --git a/CHANGES.md b/CHANGES.md index 2e78e41..08d9171 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changelog +## 0.5.0 (2024-12-01) + +- Feat: added new tools: `isValidSecureUrl`, `prefetchImages`, `browserIsIE`, `browserIsSupported` and `isString` type helper. +- Fix: added checks for running in SSR mode. + ## 0.4.1 (2024-11-27) - Chore: typec-check in CI. diff --git a/README.md b/README.md index 486bca9..b815d55 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,17 @@ General utilities for Web development ### Browser -- `copyToClipboard(content: string): boolean` -- `getCookie(name: string): string | null` -- `storageAvailable(type: 'localStorage' | 'sessionStorage'): boolean` +- `browserIsIE()` +- `copyToClipboard(content: string)` +- `getCookie(name: string)` +- `isValidSecureUrl(url: string)` +- `prefetchImages(url: string|string[])` +- `storageAvailable(type: 'localStorage' | 'sessionStorage')` ### Vue -- `getNextPath(router?: Router): string` - returns the value of `?next` query param or `/` +- `getNextPath(router?: Router)` - returns the value of `?next` query param or `/` +- `isString(value: string | LocationQueryValue[])` ## Installation diff --git a/package.json b/package.json index bef3110..692e214 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@slipmatio/toolbelt", "type": "module", - "version": "0.4.1", + "version": "0.5.0", "main": "dist/toolbelt.js", "module": "dist/toolbelt.js", "exports": { diff --git a/src/browser.ts b/src/browser.ts index 2642a16..88d27a0 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,7 +1,31 @@ +export function isSSR() { + try { + return typeof window === 'undefined' && typeof document === 'undefined' + } catch { + return true + } +} + +export function hasTimeZoneSupport() { + if (isSSR()) { + return false + } + try { + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone + return Boolean(tz) + } catch { + return false + } +} + /** * Checks whether the given Storage is available and usable. */ -export function storageAvailable(type: 'localStorage' | 'sessionStorage'): boolean { +export function storageAvailable(type: 'localStorage' | 'sessionStorage') { + if (isSSR()) { + return false + } + let storage: Storage if (type === 'localStorage') { storage = window.localStorage @@ -36,6 +60,9 @@ export function storageAvailable(type: 'localStorage' | 'sessionStorage'): boole * Returns the cookie as a string or null if not found. */ export function getCookie(name: string) { + if (isSSR()) { + return null + } let cookieValue = '' if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';') @@ -60,6 +87,9 @@ export function getCookie(name: string) { * Simple clipboard copy for modern browsers. Returns true if successful. */ export function copyToClipboard(content: string) { + if (isSSR()) { + return false + } const d = document try { const el = document.createElement('input') @@ -74,3 +104,73 @@ export function copyToClipboard(content: string) { return false } } + +/** + * Basic URL validation that checks if string can be parsed as URL + * Input must start with https:// + */ +export function isValidSecureUrl(url: string) { + if (!url?.trim()) { + return false + } + + try { + const parsed = new URL(url) + return parsed.protocol === 'https:' + } catch { + return false + } +} + +/** + * Simple helper to prefetch images + * @param urls URL or array of URLs to prefetch + */ +export async function prefetchImages(urls: string | string[]) { + if (isSSR()) { + return [] + } + + const urlList = Array.isArray(urls) ? urls : [urls] + + return Promise.all( + urlList.map( + (url) => + new Promise<{ url: string; success: boolean }>((resolve) => { + const img = new Image() + + function cleanup() { + img.onload = null + img.onerror = null + } + + img.onload = () => { + cleanup() + resolve({ url, success: true }) + } + + img.onerror = () => { + cleanup() + resolve({ url, success: false }) + } + + img.src = url + }) + ) + ) +} + +export function browserIsIE() { + if (isSSR()) { + return false + } + const ua = window.navigator.userAgent + return ua.indexOf('MSIE ') > -1 || ua.indexOf('Trident/') > -1 +} + +export function browserIsSupported() { + if (isSSR()) { + return false + } + return !browserIsIE() && hasTimeZoneSupport() && storageAvailable('localStorage') +} diff --git a/src/vue/index.ts b/src/vue/index.ts index 3c65c0d..299fb04 100644 --- a/src/vue/index.ts +++ b/src/vue/index.ts @@ -20,3 +20,5 @@ export function getNextPath(router?: Router): string { } return next } + +export { isString }