UNPKG

@mirawision/copily

Version:

A comprehensive clipboard manipulation library for TypeScript, providing functionalities for copying/pasting text, HTML, JSON, images, files, and smart content detection.

246 lines (245 loc) 7.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.requestClipboardPermission = exports.isSecureContext = exports.removeTemporaryElement = exports.createTemporaryElement = exports.imageToBlob = exports.isJSON = exports.isOTP = exports.isEmail = exports.isURL = exports.sanitizeHTML = exports.getClipboardFormats = exports.supportsFormat = exports.supportsClipboardAPI = void 0; const types_1 = require("./types"); /** * Check if the Clipboard API is supported in the current environment. * * @returns True if `navigator.clipboard` is available. */ function supportsClipboardAPI() { return typeof navigator !== 'undefined' && 'clipboard' in navigator && typeof navigator.clipboard === 'object'; } exports.supportsClipboardAPI = supportsClipboardAPI; /** * Check if the clipboard supports a specific MIME format. * * Note: The Clipboard API does not expose a formal capability query for * formats. This function validates against a curated list of common types. * * @param format MIME type (e.g. `text/html`). * @returns True when the format is recognized. */ function supportsFormat(format) { if (!supportsClipboardAPI()) { return false; } // Basic format validation const validFormats = [ 'text/plain', 'text/html', 'text/uri-list', 'image/png', 'image/jpeg', 'image/gif', 'image/webp', 'application/json' ]; return validFormats.includes(format); } exports.supportsFormat = supportsFormat; /** * Get available MIME formats currently present on the clipboard. * * @returns Resolves to an array of MIME types; returns an empty array when * formats cannot be read. * @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable. */ async function getClipboardFormats() { if (!supportsClipboardAPI()) { throw new types_1.ClipboardUnsupportedError(); } try { const items = await navigator.clipboard.read(); return items.map(item => Array.from(item.types)).flat(); } catch (error) { // If we can't read formats, return empty array return []; } } exports.getClipboardFormats = getClipboardFormats; /** * Validate and sanitize an HTML string. * * Removes script/iframe tags and common inline event handlers. For strict * sanitization in production, consider a dedicated sanitizer like DOMPurify. * * @param html HTML string. * @returns Sanitized HTML string. */ function sanitizeHTML(html) { // Basic HTML sanitization - in production, use a proper sanitizer like DOMPurify return html .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '') .replace(/javascript:/gi, '') .replace(/on\w+\s*=/gi, ''); } exports.sanitizeHTML = sanitizeHTML; /** * Detect if a string is a URL. * * Accepts http(s), ftp, mailto and tel protocols. * * @param text String to test. * @returns True when the input is a URL. */ function isURL(text) { try { const url = new URL(text); return ['http:', 'https:', 'ftp:', 'mailto:', 'tel:'].includes(url.protocol); } catch { return false; } } exports.isURL = isURL; /** * Detect if a string is an email address. * * @param text String to test. * @returns True when the input matches a simple email regex. */ function isEmail(text) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(text.trim()); } exports.isEmail = isEmail; /** * Detect if a string looks like a One-Time Passcode (OTP). * * Matches 4-8 digit codes or 6-character alphanumerics. * * @param text String to test. * @returns True when the input matches OTP patterns. */ function isOTP(text) { const trimmed = text.trim(); // OTP patterns: 4-8 digits, or 6 alphanumeric characters const otpRegex = /^(\d{4,8}|[A-Za-z0-9]{6})$/; return otpRegex.test(trimmed); } exports.isOTP = isOTP; /** * Detect if a string is valid JSON. * * @param text String to test. * @returns True when `JSON.parse` succeeds. */ function isJSON(text) { try { JSON.parse(text); return true; } catch { return false; } } exports.isJSON = isJSON; /** * Convert an image to a Blob. * * For a URL string, the image is fetched. For an `HTMLImageElement`, a canvas * conversion is performed. * * @param image URL string or `HTMLImageElement`. * @returns Resolves to a Blob of the image data. */ async function imageToBlob(image) { if (typeof image === 'string') { // Fetch image from URL const response = await fetch(image); if (!response.ok) { throw new Error('Failed to fetch image'); } return await response.blob(); } else { // Convert canvas to blob const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Failed to get canvas context'); } canvas.width = image.naturalWidth; canvas.height = image.naturalHeight; ctx.drawImage(image, 0, 0); return new Promise((resolve, reject) => { canvas.toBlob((blob) => { if (blob) { resolve(blob); } else { reject(new Error('Failed to convert image to blob')); } }); }); } } exports.imageToBlob = imageToBlob; /** * Create a hidden textarea for legacy copy operations. * * @returns The created textarea element. */ function createTemporaryElement() { const textArea = document.createElement('textarea'); textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; textArea.style.opacity = '0'; document.body.appendChild(textArea); return textArea; } exports.createTemporaryElement = createTemporaryElement; /** * Remove a temporary element from the DOM. * * @param element The element to remove. */ function removeTemporaryElement(element) { if (element.parentNode) { element.parentNode.removeChild(element); } } exports.removeTemporaryElement = removeTemporaryElement; /** * Check if the current page context is secure (HTTPS or otherwise). * * @returns True when in a secure context. */ function isSecureContext() { return typeof window !== 'undefined' && (window.isSecureContext || location.protocol === 'https:'); } exports.isSecureContext = isSecureContext; /** * Request clipboard permission by attempting a read. * * @returns Resolves true on success. * @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable. * @throws {ClipboardPermissionError} If the operation is not permitted. */ async function requestClipboardPermission() { if (!supportsClipboardAPI()) { throw new types_1.ClipboardUnsupportedError(); } if (!isSecureContext()) { throw new types_1.ClipboardPermissionError('Clipboard API requires secure context (HTTPS)'); } try { // Try to read clipboard to trigger permission request await navigator.clipboard.readText(); return true; } catch (error) { if (error instanceof types_1.ClipboardUnsupportedError || error instanceof types_1.ClipboardPermissionError) { throw error; } throw new types_1.ClipboardPermissionError(); } } exports.requestClipboardPermission = requestClipboardPermission;