1 import UAParser from 'ua-parser-js';
3 const uaParser = new UAParser();
4 const ua = uaParser.getResult();
6 export const hasModulesSupport = () => {
7 const script = document.createElement('script');
8 return 'noModule' in script;
11 export const isFileSaverSupported = () => !!new Blob();
13 export const textToClipboard = (text = '', target = document.body) => {
14 const oldActiveElement = document.activeElement as HTMLElement;
15 if (navigator.clipboard) {
16 void navigator.clipboard.writeText(text);
18 const dummy = document.createElement('textarea');
19 target.appendChild(dummy);
22 document.execCommand('copy');
23 target.removeChild(dummy);
25 oldActiveElement?.focus?.();
28 export const copyDomToClipboard = async (element: HTMLElement) => {
29 if (!element || !(element instanceof HTMLElement)) {
30 console.error('Invalid element provided');
34 /** Try to use the Clipboard API if available */
36 * Commenting the clipboard API solution for now because of 2 "issues"
37 * 1- The current solution is copying HTML only. However, we would need to copy plaintext too for editors that are not supporting HTML
38 * 2- When using the clipboard API, the content is sanitized, meaning that some parts of the content are dropped, such as classes
40 // if (navigator.clipboard && typeof navigator.clipboard.write === 'function') {
41 // const type = 'text/html';
42 // const blob = new Blob([element.innerHTML], { type });
43 // const data = [new ClipboardItem({ [type]: blob })];
44 // await navigator.clipboard.write(data);
46 const activeElement = document.activeElement;
48 // Create an off-screen container for the element's HTML content
49 const tempContainer = document.createElement('div');
50 tempContainer.style.position = 'absolute';
51 tempContainer.style.left = '-9999px';
52 tempContainer.innerHTML = element.innerHTML;
54 document.body.appendChild(tempContainer);
56 const selection = window.getSelection();
58 console.error('Failed to get selection');
59 document.body.removeChild(tempContainer);
63 // Select the contents of the temporary container
64 const range = document.createRange();
65 range.selectNodeContents(tempContainer);
67 selection.removeAllRanges();
68 selection.addRange(range);
70 // Copy the selected content to the clipboard
72 document.execCommand('copy');
74 console.error('Failed to copy content', err);
78 document.body.removeChild(tempContainer);
79 selection.removeAllRanges();
81 // Restore previous focus
82 if (activeElement instanceof HTMLElement) {
83 activeElement.focus();
88 export const getOS = () => {
89 const { name = 'other', version = '' } = ua.os;
90 return { name, version };
93 export const isIos11 = () => {
94 const { name, version } = getOS();
95 return name.toLowerCase() === 'ios' && parseInt(version, 10) === 11;
98 export const isAndroid = () => {
99 const { name } = getOS();
100 return name.toLowerCase().includes('android');
103 export const isStandaloneApp = () => window.matchMedia('(display-mode: standalone)').matches;
105 export const isDuckDuckGo = () => ua.browser.name === 'DuckDuckGo';
106 export const isSafari = () => ua.browser.name === 'Safari' || ua.browser.name === 'Mobile Safari';
107 export const isSafari11 = () => isSafari() && ua.browser.major === '11';
108 export const isMinimumSafariVersion = (version: number) => isSafari() && Number(ua.browser.version) >= version;
109 export const isSafariMobile = () => ua.browser.name === 'Mobile Safari';
110 export const isIE11 = () => ua.browser.name === 'IE' && ua.browser.major === '11';
111 export const isEdge = () => ua.browser.name === 'Edge';
112 export const isEdgeChromium = () => isEdge() && ua.engine.name === 'Blink';
113 export const isBrave = () => ua.browser.name === 'Brave';
114 export const isFirefox = () => ua.browser.name === 'Firefox';
115 export const isMaybeTorLessThan11 = () => {
118 /\.0$/.test(ua.browser.version || '') && // The Firefox minor revision is omitted.
119 Intl.DateTimeFormat().resolvedOptions().timeZone === 'UTC' && // The timezone is set to UTC
120 !Object.prototype.hasOwnProperty.call(window, 'Components') && // It strips out Components
121 navigator.plugins.length === 0; // 0 plugins are returned
122 // Starting from tor browser 11, tor is based on firefox 91
123 return isMaybeTor && !!ua.browser.major && +ua.browser.major < 91;
125 export const isChrome = () => ua.browser.name === 'Chrome';
126 export const isChromiumBased = () => 'chrome' in window;
127 export const isJSDom = () => navigator.userAgent.includes('jsdom');
128 export const isMac = () => ua.os.name === 'Mac OS';
129 export const isWindows = () => ua.os.name === 'Windows';
130 export const isLinux = () => ua.ua.match(/(L|l)inux/);
131 export const hasTouch = typeof document === 'undefined' ? false : 'ontouchstart' in document.documentElement;
132 export const hasCookie = () => navigator.cookieEnabled;
133 export const getOs = () => ua.os;
134 export const getBrowser = () => ua.browser;
135 export const getDevice = () => ua.device;
136 export const isMobile = () => {
137 const { type } = getDevice();
138 return type === 'mobile';
140 export const isDesktop = () => {
141 const { type } = getDevice();
144 export const getIsIframe = () => window.self !== window.top;
146 export const metaKey = isMac() ? '⌘' : 'Ctrl';
147 export const altKey = isMac() ? 'Option' : 'Alt';
148 export const shiftKey = 'Shift';
150 export const getActiveXObject = (name: string) => {
153 return new ActiveXObject(name);
154 } catch (error: any) {
159 export const isIos = () =>
160 // https://racase.com.np/javascript-how-to-detect-if-device-is-ios/
161 (/iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream) ||
162 ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) ||
163 // iPad on iOS 13 detection
164 (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
165 export const isIpad = () => isSafari() && navigator.maxTouchPoints && navigator.maxTouchPoints > 2;
166 export const hasAcrobatInstalled = () => !!(getActiveXObject('AcroPDF.PDF') || getActiveXObject('PDF.PdfCtrl'));
167 export const hasPDFSupport = () => {
168 // mimeTypes is deprecated in favor of pdfViewerEnabled.
170 (navigator.mimeTypes && 'application/pdf' in navigator.mimeTypes) ||
171 navigator.pdfViewerEnabled ||
172 (isFirefox() && isDesktop()) ||
174 hasAcrobatInstalled()
177 export const replaceUrl = (url = '') => document.location.replace(url);
178 export const redirectTo = (url = '') => replaceUrl(`${document.location.origin}${url}`);
181 * Detect browser requiring direct action
182 * Like opening a new tab
184 export const requireDirectAction = () => isSafari() || isFirefox() || isEdge();
187 * Open an URL inside a new tab/window and remove the referrer
188 * @links { https://mathiasbynens.github.io/rel-noopener/}
190 export const openNewTab = (url: string) => {
192 const otherWindow = window.open();
196 otherWindow.opener = null;
197 otherWindow.location.href = url;
200 const anchor = document.createElement('a');
202 anchor.setAttribute('rel', 'noreferrer nofollow noopener');
203 anchor.setAttribute('target', '_blank');
206 return anchor.click();
209 // On safari < 14 the Version cookie is sent for index.html file but
210 // not sent for asset requests due to https://bugs.webkit.org/show_bug.cgi?id=171566
211 export const doesNotSupportEarlyAccessVersion = () => isSafari() && Number(ua.browser.major) < 14;
213 export const getHasWebAuthnSupport = () => {
215 return !!navigator?.credentials?.create;
221 export const browserAPI = (globalThis as any)?.browser ?? (globalThis as any)?.chrome;