detectincognitojs
Version:
detectIncognito.js can be used to detect incognito mode & other private browsing modes on most modern browsers as of 2024.
290 lines (253 loc) • 7.64 kB
text/typescript
/*!
*
* detectIncognito v1.4.1
*
* https://github.com/Joe12387/detectIncognito
*
* MIT License
*
* Copyright (c) 2021 - 2024 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'
function __callback (isPrivate: boolean): void {
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 assertEvalToString (value: number): boolean {
return value === eval.toString().length
}
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
}
function isChrome (): boolean {
return feid() === 51
}
function isFirefox (): boolean {
return feid() === 25
}
function isMSIE (): boolean {
return (
(navigator as any).msSaveBlob !== undefined && assertEvalToString(39)
)
}
/**
* Safari (Safari for iOS & macOS)
**/
function newSafariTestByStorageFallback (): void {
if (!navigator.storage?.estimate) {
__callback(false);
return;
}
navigator.storage
.estimate()
.then(({ usage, quota }) => {
// iOS 18.x/macOS Safari 18.x (normal): ~41GB
// iOS 18.x/macOS Safari 18.x (private): ~1GB
// If reported quota < 2 GB => likely private
if (quota && quota < 2_000_000_000) {
__callback(true);
} else {
__callback(false);
}
})
.catch(() => {
__callback(false);
});
}
function newSafariTest (): void {
const tmp_name = String(Math.random())
try {
const db = window.indexedDB.open(tmp_name, 1)
db.onupgradeneeded = function (i) {
const res = i.target?.result
try {
res.createObjectStore('test', {
autoIncrement: true
}).put(new Blob())
} catch (e) {
let message = e
if (e instanceof Error) {
message = e.message ?? e
}
if (typeof message !== 'string') {
__callback(false); return
}
const matchesExpectedError = message.includes('BlobURLs are not yet supported')
if (matchesExpectedError) {
__callback(true)
}
} finally {
res.close()
window.indexedDB.deleteDatabase(tmp_name)
// indexdb works on newer versions of safari so we need to check via storage fallback
newSafariTestByStorageFallback();
}
}
} catch (e) {
__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)
}
function safariPrivateTest (): void {
if (navigator.maxTouchPoints !== undefined) {
newSafariTest()
} else {
oldSafariTest()
}
}
/**
* Chrome
**/
function getQuotaLimit (): number {
const w = window as any
if (
w.performance !== undefined &&
w.performance.memory !== undefined &&
w.performance.memory.jsHeapSizeLimit !== undefined
) {
return (performance as any).memory.jsHeapSizeLimit
}
return 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
**/
function firefoxPrivateTest (): void {
__callback(navigator.serviceWorker === undefined)
}
/**
* MSIE
**/
function msiePrivateTest (): void {
__callback(window.indexedDB === undefined)
}
function main (): void {
if (isSafari()) {
browserName = 'Safari'
safariPrivateTest()
} else if (isChrome()) {
browserName = identifyChromium()
chromePrivateTest()
} else if (isFirefox()) {
browserName = 'Firefox'
firefoxPrivateTest()
} else if (isMSIE()) {
browserName = 'Internet Explorer'
msiePrivateTest()
} else {
reject(new Error('detectIncognito cannot determine the browser'))
}
}
main()
})
}
if (typeof window !== 'undefined') {
window.detectIncognito = detectIncognito;
}
export default detectIncognito;