UNPKG

detectincognitojs

Version:

detectIncognito.js can be used to detect incognito mode & other private browsing modes on most modern browsers.

289 lines (250 loc) 7.91 kB
/*! * * detectIncognito v1.6.2 * * https://github.com/Joe12387/detectIncognito * * MIT License * * Copyright (c) 2021 - 2025 Joe Rutkowski <Joe@dreggle.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Please keep this comment intact in order to properly abide by the MIT License. * **/ declare global { interface Window { detectIncognito: typeof detectIncognito; } } export async function detectIncognito(): Promise<{ isPrivate: boolean; browserName: string }> { return await new Promise(function (resolve, reject) { let browserName = 'Unknown' let callbackSettled = false function __callback(isPrivate: boolean): void { if (callbackSettled) { return } callbackSettled = true resolve({ isPrivate, browserName }) } function identifyChromium(): string { const ua = navigator.userAgent if (ua.match(/Chrome/)) { if ((navigator as any).brave !== undefined) { return 'Brave' } else if (ua.match(/Edg/)) { return 'Edge' } else if (ua.match(/OPR/)) { return 'Opera' } return 'Chrome' } else { return 'Chromium' } } function feid(): number { let toFixedEngineID = 0 let neg = parseInt("-1") try { neg.toFixed(neg) } catch (e) { toFixedEngineID = (e as Error).message.length } return toFixedEngineID } function isSafari(): boolean { return feid() === 44 || feid() === 43 } function isChrome(): boolean { return feid() === 51 } function isFirefox(): boolean { return feid() === 25 } function isMSIE(): boolean { return (navigator as any).msSaveBlob !== undefined } /** * Safari (Safari for iOS & macOS) **/ async function currentSafariTest() { try { await navigator.storage.getDirectory(); __callback(false) } catch (e) { let message = (e instanceof Error && typeof e.message === 'string') ? e.message : String(e) const matchesExpectedError = message.includes('unknown transient reason') __callback(matchesExpectedError) } } function safari13to18Test(): void { const tmp = String(Math.random()); try { const dbReq = indexedDB.open(tmp, 1); dbReq.onupgradeneeded = (ev) => { const db = (ev.target as IDBOpenDBRequest).result; const finish = (priv: boolean) => { __callback(priv); }; try { db.createObjectStore('t', { autoIncrement: true }).put(new Blob()); finish(false) } catch (err) { const message = (err instanceof Error && typeof err.message === 'string') ? err.message : String(err); if (message.includes('are not yet supported')) finish(true); else finish(false); } finally { db.close(); indexedDB.deleteDatabase(tmp); } }; dbReq.onerror = () => __callback(false) } catch { __callback(false) } } function oldSafariTest(): void { const openDB = (window as any).openDatabase const storage = window.localStorage try { openDB(null, null, null, null) } catch (e) { __callback(true); return } try { storage.setItem('test', '1') storage.removeItem('test') } catch (e) { __callback(true); return } __callback(false) } async function safariPrivateTest(): Promise<void> { if (typeof navigator.storage?.getDirectory === 'function') { await currentSafariTest() } else if (navigator.maxTouchPoints !== undefined) { safari13to18Test() } else { oldSafariTest() } } /** * Chrome **/ function getQuotaLimit(): number { const w = window as any return w?.performance?.memory?.jsHeapSizeLimit ?? 1073741824 } // >= 76 function storageQuotaChromePrivateTest(): void { (navigator as any).webkitTemporaryStorage.queryUsageAndQuota( function (_: number, quota: number) { const quotaInMib = Math.round(quota / (1024 * 1024)) const quotaLimitInMib = Math.round(getQuotaLimit() / (1024 * 1024)) * 2 __callback(quotaInMib < quotaLimitInMib) }, function (e: any) { reject( new Error( 'detectIncognito somehow failed to query storage quota: ' + e.message ) ) } ) } // 50 to 75 function oldChromePrivateTest(): void { const fs = (window as any).webkitRequestFileSystem const success = function () { __callback(false) } const error = function () { __callback(true) } fs(0, 1, success, error) } function chromePrivateTest(): void { if (self.Promise !== undefined && (self.Promise as any).allSettled !== undefined) { storageQuotaChromePrivateTest() } else { oldChromePrivateTest() } } /** * Firefox **/ async function firefoxPrivateTest(): Promise<void> { if (typeof navigator.storage?.getDirectory === 'function') { try { await navigator.storage.getDirectory() __callback(false) } catch (e) { let message = (e instanceof Error && typeof e.message === 'string') ? e.message : String(e) const matchesExpectedError = message.includes('Security error') __callback(matchesExpectedError); return } } else { const request = indexedDB.open('inPrivate'); request.onerror = (event) => { if (request.error && request.error.name === 'InvalidStateError') { event.preventDefault(); } __callback(true); }; request.onsuccess = () => { indexedDB.deleteDatabase('inPrivate'); __callback(false); }; } } /** * MSIE **/ function msiePrivateTest(): void { __callback(window.indexedDB === undefined) } async function main(): Promise<void> { if (isSafari()) { browserName = 'Safari' await safariPrivateTest() } else if (isChrome()) { browserName = identifyChromium() chromePrivateTest() } else if (isFirefox()) { browserName = 'Firefox' await firefoxPrivateTest() } else if (isMSIE()) { browserName = 'Internet Explorer' msiePrivateTest() } else { reject(new Error('detectIncognito cannot determine the browser')) } } main().catch(reject) }) } if (typeof window !== 'undefined') { window.detectIncognito = detectIncognito; } export default detectIncognito;