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
text/typescript
/*!
*
* 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;