├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── detectIncognito.js ├── detectIncognito.min.js ├── dist ├── detectIncognito.d.ts ├── detectIncognito.d.ts.map ├── detectIncognito.js ├── detectIncognito.js.map └── detectIncognito.min.js ├── example.html ├── package-lock.json ├── package.json ├── src ├── @types │ └── lib.dom.d.ts └── detectIncognito.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Joe12387 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Joe Rutkowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note: The npm package is now [detectincognitojs](https://www.npmjs.com/package/detectincognitojs "detectincognitojs"). 2 | 3 | # detectIncognito.js 4 | 5 | detectIncognito.js can be used to detect incognito mode & other private browsing modes on most modern browsers as of 2022. 6 | 7 | - Detects Incognito mode on Google Chrome 8 | - Detects Private Windows on Safari for macOS 9 | - Detects Private Tabs on Safari for iOS 10 | - Detects Private Windows in Firefox 11 | - Detects InPrivate Windows on Microsoft Edge 12 | - Detects InPrivate Windows on Microsoft Internet Explorer 13 | - Detects Private Windows in Brave 14 | - Detects Private Windows in Opera 15 | 16 | DEMO: https://detectincognito.com/ 17 | 18 | # Usage 19 | 20 | Get script from CDN 21 | 22 | ```html 23 | 24 | ``` 25 | 26 | Or install from NPM 27 | 28 | ``` 29 | npm i detectincognitojs 30 | ``` 31 | 32 | ```javascript 33 | import { detectIncognito } from "detectincognitojs"; 34 | ``` 35 | 36 | Run the detect function 37 | 38 | ```javascript 39 | detectIncognito().then((result) => { 40 | console.log(result.browserName, result.isPrivate); 41 | }); 42 | ``` 43 | 44 | # Supported Browsers 45 | 46 | - Safari for iOS - 8 to 16 47 | - Safari for macOS <= 16 48 | - Chrome/Chromium - 50 to 105 49 | - Edge - 15 to 18; 79 to 105 50 | - Firefox - 44 to 104 51 | - Brave <= 1.43 52 | - MSIE == 11 (Promise polyfill required) 53 | 54 | Please note that although this script works on almost all modern browsers, detecting private modes in browsers is very much an arms race. As such, I cannot guarantee that this script will continue to work into the future. However, I will continue to actively maintain this script to support as many browsers as is possible. 55 | 56 | If you are aware of any modern browsers this script does not work with, please let me know by creating an issue. 57 | 58 | # Notes 59 | 60 | - There will be a false positive in certain browser configurations, as well as in Chrome's Guest mode. ([Issue #21](https://github.com/Joe12387/detectIncognito/issues/21)). 61 | - This script does not detect Container Tabs on Firefox as they work differently compared to private mode. 62 | - An error will be thrown if the browser cannot be identified. 63 | - The script only works remotely (i.e. on a web server). Running the script locally may produce a false result, or not run at all. 64 | 65 | # Similar Projects 66 | - [Overpowered Browser Fingerprinting Script](https://github.com/Joe12387/OP-Fingerprinting-Script "Overpowered Browser Fingerprinting Script") 67 | -------------------------------------------------------------------------------- /detectIncognito.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.detectIncognito = void 0; 4 | /** 5 | * 6 | * detectIncognito v1.1.2 - (c) 2022 Joe Rutkowski (https://github.com/Joe12387/detectIncognito) 7 | * 8 | **/ 9 | var detectIncognito = function () { 10 | return new Promise(function (resolve, reject) { 11 | var browserName = "Unknown"; 12 | function __callback(isPrivate) { 13 | resolve({ 14 | isPrivate: isPrivate, 15 | browserName: browserName, 16 | }); 17 | } 18 | function identifyChromium() { 19 | var ua = navigator.userAgent; 20 | if (ua.match(/Chrome/)) { 21 | if (navigator.brave !== undefined) { 22 | return "Brave"; 23 | } 24 | else if (ua.match(/Edg/)) { 25 | return "Edge"; 26 | } 27 | else if (ua.match(/OPR/)) { 28 | return "Opera"; 29 | } 30 | return "Chrome"; 31 | } 32 | else { 33 | return "Chromium"; 34 | } 35 | } 36 | function assertEvalToString(value) { 37 | return value === eval.toString().length; 38 | } 39 | function isSafari() { 40 | var v = navigator.vendor; 41 | return (v !== undefined && v.indexOf("Apple") === 0 && assertEvalToString(37)); 42 | } 43 | function isChrome() { 44 | var v = navigator.vendor; 45 | return (v !== undefined && v.indexOf("Google") === 0 && assertEvalToString(33)); 46 | } 47 | function isFirefox() { 48 | return (document.documentElement !== undefined && 49 | document.documentElement.style.MozAppearance !== undefined && 50 | assertEvalToString(37)); 51 | } 52 | function isMSIE() { 53 | return (navigator.msSaveBlob !== undefined && assertEvalToString(39)); 54 | } 55 | /** 56 | * Safari (Safari for iOS & macOS) 57 | **/ 58 | function newSafariTest() { 59 | var tmp_name = String(Math.random()); 60 | try { 61 | var db = window.indexedDB.open(tmp_name, 1); 62 | db.onupgradeneeded = function (i) { 63 | var _a, _b; 64 | var res = (_a = i.target) === null || _a === void 0 ? void 0 : _a.result; 65 | try { 66 | res.createObjectStore("test", { 67 | autoIncrement: true, 68 | }).put(new Blob); 69 | __callback(false); 70 | } 71 | catch (e) { 72 | var message = e; 73 | if (e instanceof Error) { 74 | message = (_b = e.message) !== null && _b !== void 0 ? _b : e; 75 | } 76 | if (typeof message !== 'string') { 77 | return __callback(false); 78 | } 79 | var matchesExpectedError = /BlobURLs are not yet supported/.test(message); 80 | return __callback(matchesExpectedError); 81 | } 82 | finally { 83 | res.close(); 84 | window.indexedDB.deleteDatabase(tmp_name); 85 | } 86 | }; 87 | } 88 | catch (e) { 89 | return __callback(false); 90 | } 91 | } 92 | function oldSafariTest() { 93 | var openDB = window.openDatabase; 94 | var storage = window.localStorage; 95 | try { 96 | openDB(null, null, null, null); 97 | } 98 | catch (e) { 99 | return __callback(true); 100 | } 101 | try { 102 | storage.setItem("test", "1"); 103 | storage.removeItem("test"); 104 | } 105 | catch (e) { 106 | return __callback(true); 107 | } 108 | return __callback(false); 109 | } 110 | function safariPrivateTest() { 111 | if (navigator.maxTouchPoints !== undefined) { 112 | newSafariTest(); 113 | } 114 | else { 115 | oldSafariTest(); 116 | } 117 | } 118 | /** 119 | * Chrome 120 | **/ 121 | function getQuotaLimit() { 122 | var w = window; 123 | if (w.performance !== undefined && 124 | w.performance.memory !== undefined && 125 | w.performance.memory.jsHeapSizeLimit !== undefined) { 126 | return performance.memory.jsHeapSizeLimit; 127 | } 128 | return 1073741824; 129 | } 130 | // >= 76 131 | function storageQuotaChromePrivateTest() { 132 | navigator.webkitTemporaryStorage.queryUsageAndQuota(function (usage, quota) { 133 | __callback(quota < getQuotaLimit()); 134 | }, function (e) { 135 | reject(new Error("detectIncognito somehow failed to query storage quota: " + 136 | e.message)); 137 | }); 138 | } 139 | // 50 to 75 140 | function oldChromePrivateTest() { 141 | var fs = window.webkitRequestFileSystem; 142 | var success = function () { 143 | __callback(false); 144 | }; 145 | var error = function () { 146 | __callback(true); 147 | }; 148 | fs(0, 1, success, error); 149 | } 150 | function chromePrivateTest() { 151 | if (self.Promise !== undefined && self.Promise.allSettled !== undefined) { 152 | storageQuotaChromePrivateTest(); 153 | } 154 | else { 155 | oldChromePrivateTest(); 156 | } 157 | } 158 | /** 159 | * Firefox 160 | **/ 161 | function firefoxPrivateTest() { 162 | __callback(navigator.serviceWorker === undefined); 163 | } 164 | /** 165 | * MSIE 166 | **/ 167 | function msiePrivateTest() { 168 | __callback(window.indexedDB === undefined); 169 | } 170 | function main() { 171 | if (isSafari()) { 172 | browserName = 'Safari'; 173 | safariPrivateTest(); 174 | } 175 | else if (isChrome()) { 176 | browserName = identifyChromium(); 177 | chromePrivateTest(); 178 | } 179 | else if (isFirefox()) { 180 | browserName = "Firefox"; 181 | firefoxPrivateTest(); 182 | } 183 | else if (isMSIE()) { 184 | browserName = "Internet Explorer"; 185 | msiePrivateTest(); 186 | } 187 | else { 188 | reject(new Error("detectIncognito cannot determine the browser")); 189 | } 190 | } 191 | main(); 192 | }); 193 | }; 194 | exports.detectIncognito = detectIncognito; 195 | //# sourceMappingURL=detectIncognito.js.map -------------------------------------------------------------------------------- /detectIncognito.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * detectIncognito v1.1.2 - (c) 2022 Joe Rutkowski (https://github.com/Joe12387/detectIncognito) 4 | * 5 | **/ 6 | var detectIncognito=function(){return new Promise(function(e,t){var o,n,r="Unknown";function i(t){e({isPrivate:t,browserName:r})}function a(e){return e===eval.toString().length}function d(){void 0!==navigator.maxTouchPoints?function(){var e=String(Math.random());try{window.indexedDB.open(e,1).onupgradeneeded=function(t){var o,n,r=null===(o=t.target)||void 0===o?void 0:o.result;try{r.createObjectStore("test",{autoIncrement:!0}).put(new Blob),i(!1)}catch(e){var a=e;return e instanceof Error&&(a=null!==(n=e.message)&&void 0!==n?n:e),i("string"==typeof a&&/BlobURLs are not yet supported/.test(a))}finally{r.close(),window.indexedDB.deleteDatabase(e)}}}catch(e){return i(!1)}}():function(){var e=window.openDatabase,t=window.localStorage;try{e(null,null,null,null)}catch(e){return i(!0)}try{t.setItem("test","1"),t.removeItem("test")}catch(e){return i(!0)}i(!1)}()}function c(){navigator.webkitTemporaryStorage.queryUsageAndQuota(function(e,t){var o;i(t<(void 0!==(o=window).performance&&void 0!==o.performance.memory&&void 0!==o.performance.memory.jsHeapSizeLimit?performance.memory.jsHeapSizeLimit:1073741824))},function(e){t(new Error("detectIncognito somehow failed to query storage quota: "+e.message))})}function u(){void 0!==self.Promise&&void 0!==self.Promise.allSettled?c():(0,window.webkitRequestFileSystem)(0,1,function(){i(!1)},function(){i(!0)})}void 0!==(n=navigator.vendor)&&0===n.indexOf("Apple")&&a(37)?(r="Safari",d()):function(){var e=navigator.vendor;return void 0!==e&&0===e.indexOf("Google")&&a(33)}()?(o=navigator.userAgent,r=o.match(/Chrome/)?void 0!==navigator.brave?"Brave":o.match(/Edg/)?"Edge":o.match(/OPR/)?"Opera":"Chrome":"Chromium",u()):void 0!==document.documentElement&&void 0!==document.documentElement.style.MozAppearance&&a(37)?(r="Firefox",i(void 0===navigator.serviceWorker)):void 0!==navigator.msSaveBlob&&a(39)?(r="Internet Explorer",i(void 0===window.indexedDB)):t(new Error("detectIncognito cannot determine the browser"))})}; 7 | -------------------------------------------------------------------------------- /dist/detectIncognito.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * detectIncognito v1.1.3 - (c) 2022 Joe Rutkowski (https://github.com/Joe12387/detectIncognito) 4 | * 5 | **/ 6 | declare const detectIncognito: () => Promise<{ 7 | isPrivate: boolean; 8 | browserName: string; 9 | }>; 10 | //# sourceMappingURL=detectIncognito.d.ts.map -------------------------------------------------------------------------------- /dist/detectIncognito.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"detectIncognito.d.ts","sourceRoot":"","sources":["../src/detectIncognito.ts"],"names":[],"mappings":"AAAA;;;;IAII;AACJ,QAAA,MAAM,eAAe,QAAgB,QAAQ;IAC3C,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAwNA,CAAC"} -------------------------------------------------------------------------------- /dist/detectIncognito.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * 4 | * detectIncognito v1.1.3 - (c) 2022 Joe Rutkowski (https://github.com/Joe12387/detectIncognito) 5 | * 6 | **/ 7 | var detectIncognito = function () { 8 | return new Promise(function (resolve, reject) { 9 | var browserName = "Unknown"; 10 | function __callback(isPrivate) { 11 | resolve({ 12 | isPrivate: isPrivate, 13 | browserName: browserName, 14 | }); 15 | } 16 | function identifyChromium() { 17 | var ua = navigator.userAgent; 18 | if (ua.match(/Chrome/)) { 19 | if (navigator.brave !== undefined) { 20 | return "Brave"; 21 | } 22 | else if (ua.match(/Edg/)) { 23 | return "Edge"; 24 | } 25 | else if (ua.match(/OPR/)) { 26 | return "Opera"; 27 | } 28 | return "Chrome"; 29 | } 30 | else { 31 | return "Chromium"; 32 | } 33 | } 34 | function assertEvalToString(value) { 35 | return value === eval.toString().length; 36 | } 37 | function isSafari() { 38 | var v = navigator.vendor; 39 | return (v !== undefined && v.indexOf("Apple") === 0 && assertEvalToString(37)); 40 | } 41 | function isChrome() { 42 | var v = navigator.vendor; 43 | return (v !== undefined && v.indexOf("Google") === 0 && assertEvalToString(33)); 44 | } 45 | function isFirefox() { 46 | return (document.documentElement !== undefined && 47 | document.documentElement.style.MozAppearance !== undefined && 48 | assertEvalToString(37)); 49 | } 50 | function isMSIE() { 51 | return (navigator.msSaveBlob !== undefined && assertEvalToString(39)); 52 | } 53 | /** 54 | * Safari (Safari for iOS & macOS) 55 | **/ 56 | function newSafariTest() { 57 | var tmp_name = String(Math.random()); 58 | try { 59 | var db = window.indexedDB.open(tmp_name, 1); 60 | db.onupgradeneeded = function (i) { 61 | var _a, _b; 62 | var res = (_a = i.target) === null || _a === void 0 ? void 0 : _a.result; 63 | try { 64 | res.createObjectStore("test", { 65 | autoIncrement: true, 66 | }).put(new Blob); 67 | __callback(false); 68 | } 69 | catch (e) { 70 | var message = e; 71 | if (e instanceof Error) { 72 | message = (_b = e.message) !== null && _b !== void 0 ? _b : e; 73 | } 74 | if (typeof message !== 'string') { 75 | return __callback(false); 76 | } 77 | var matchesExpectedError = /BlobURLs are not yet supported/.test(message); 78 | return __callback(matchesExpectedError); 79 | } 80 | finally { 81 | res.close(); 82 | window.indexedDB.deleteDatabase(tmp_name); 83 | } 84 | }; 85 | } 86 | catch (e) { 87 | return __callback(false); 88 | } 89 | } 90 | function oldSafariTest() { 91 | var openDB = window.openDatabase; 92 | var storage = window.localStorage; 93 | try { 94 | openDB(null, null, null, null); 95 | } 96 | catch (e) { 97 | return __callback(true); 98 | } 99 | try { 100 | storage.setItem("test", "1"); 101 | storage.removeItem("test"); 102 | } 103 | catch (e) { 104 | return __callback(true); 105 | } 106 | return __callback(false); 107 | } 108 | function safariPrivateTest() { 109 | if (navigator.maxTouchPoints !== undefined) { 110 | newSafariTest(); 111 | } 112 | else { 113 | oldSafariTest(); 114 | } 115 | } 116 | /** 117 | * Chrome 118 | **/ 119 | function getQuotaLimit() { 120 | var w = window; 121 | if (w.performance !== undefined && 122 | w.performance.memory !== undefined && 123 | w.performance.memory.jsHeapSizeLimit !== undefined) { 124 | return performance.memory.jsHeapSizeLimit; 125 | } 126 | return 1073741824; 127 | } 128 | // >= 76 129 | function storageQuotaChromePrivateTest() { 130 | navigator.webkitTemporaryStorage.queryUsageAndQuota(function (usage, quota) { 131 | __callback(quota < getQuotaLimit()); 132 | }, function (e) { 133 | reject(new Error("detectIncognito somehow failed to query storage quota: " + 134 | e.message)); 135 | }); 136 | } 137 | // 50 to 75 138 | function oldChromePrivateTest() { 139 | var fs = window.webkitRequestFileSystem; 140 | var success = function () { 141 | __callback(false); 142 | }; 143 | var error = function () { 144 | __callback(true); 145 | }; 146 | fs(0, 1, success, error); 147 | } 148 | function chromePrivateTest() { 149 | if (self.Promise !== undefined && self.Promise.allSettled !== undefined) { 150 | storageQuotaChromePrivateTest(); 151 | } 152 | else { 153 | oldChromePrivateTest(); 154 | } 155 | } 156 | /** 157 | * Firefox 158 | **/ 159 | function firefoxPrivateTest() { 160 | __callback(navigator.serviceWorker === undefined); 161 | } 162 | /** 163 | * MSIE 164 | **/ 165 | function msiePrivateTest() { 166 | __callback(window.indexedDB === undefined); 167 | } 168 | function main() { 169 | if (isSafari()) { 170 | browserName = 'Safari'; 171 | safariPrivateTest(); 172 | } 173 | else if (isChrome()) { 174 | browserName = identifyChromium(); 175 | chromePrivateTest(); 176 | } 177 | else if (isFirefox()) { 178 | browserName = "Firefox"; 179 | firefoxPrivateTest(); 180 | } 181 | else if (isMSIE()) { 182 | browserName = "Internet Explorer"; 183 | msiePrivateTest(); 184 | } 185 | else { 186 | reject(new Error("detectIncognito cannot determine the browser")); 187 | } 188 | } 189 | main(); 190 | }); 191 | }; 192 | //# sourceMappingURL=detectIncognito.js.map -------------------------------------------------------------------------------- /dist/detectIncognito.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"detectIncognito.js","sourceRoot":"","sources":["../src/detectIncognito.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/detectIncognito.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var detectIncognito=function(){return new Promise(function(resolve,reject){var browserName="Unknown";function __callback(isPrivate){resolve({isPrivate:isPrivate,browserName:browserName})}function identifyChromium(){var ua=navigator.userAgent;if(ua.match(/Chrome/)){if(navigator.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){return value===eval.toString().length}function isSafari(){var v=navigator.vendor;return v!==undefined&&v.indexOf("Apple")===0&&assertEvalToString(37)}function isChrome(){var v=navigator.vendor;return v!==undefined&&v.indexOf("Google")===0&&assertEvalToString(33)}function isFirefox(){return document.documentElement!==undefined&&document.documentElement.style.MozAppearance!==undefined&&assertEvalToString(37)}function isMSIE(){return navigator.msSaveBlob!==undefined&&assertEvalToString(39)}function newSafariTest(){var tmp_name=String(Math.random());try{var db=window.indexedDB.open(tmp_name,1);db.onupgradeneeded=function(i){var _a,_b;var res=(_a=i.target)===null||_a===void 0?void 0:_a.result;try{res.createObjectStore("test",{autoIncrement:true}).put(new Blob);__callback(false)}catch(e){var message=e;if(e instanceof Error){message=(_b=e.message)!==null&&_b!==void 0?_b:e}if(typeof message!=="string"){return __callback(false)}var matchesExpectedError=/BlobURLs are not yet supported/.test(message);return __callback(matchesExpectedError)}finally{res.close();window.indexedDB.deleteDatabase(tmp_name)}}}catch(e){return __callback(false)}}function oldSafariTest(){var openDB=window.openDatabase;var storage=window.localStorage;try{openDB(null,null,null,null)}catch(e){return __callback(true)}try{storage.setItem("test","1");storage.removeItem("test")}catch(e){return __callback(true)}return __callback(false)}function safariPrivateTest(){if(navigator.maxTouchPoints!==undefined){newSafariTest()}else{oldSafariTest()}}function getQuotaLimit(){var w=window;if(w.performance!==undefined&&w.performance.memory!==undefined&&w.performance.memory.jsHeapSizeLimit!==undefined){return performance.memory.jsHeapSizeLimit}return 1073741824}function storageQuotaChromePrivateTest(){navigator.webkitTemporaryStorage.queryUsageAndQuota(function(usage,quota){__callback(quota 2 | 3 | 4 | 5 | detectIncognito - JavaScript Private Browsing Detection 6 | 21 | 22 | 23 | 24 | 25 |
26 |

Is this a Private Browsing window?

27 |

28 |
29 |
30 |

Powered by detectIncognito.js.

31 |
32 |
33 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "detectincognitojs", 3 | "version": "1.1.3", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "detectincognitojs", 9 | "version": "1.1.3", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "typescript": "^4.6.4", 13 | "uglify-js": "^3.17.1" 14 | } 15 | }, 16 | "node_modules/typescript": { 17 | "version": "4.6.4", 18 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", 19 | "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", 20 | "dev": true, 21 | "bin": { 22 | "tsc": "bin/tsc", 23 | "tsserver": "bin/tsserver" 24 | }, 25 | "engines": { 26 | "node": ">=4.2.0" 27 | } 28 | }, 29 | "node_modules/uglify-js": { 30 | "version": "3.17.1", 31 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", 32 | "integrity": "sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==", 33 | "dev": true, 34 | "bin": { 35 | "uglifyjs": "bin/uglifyjs" 36 | }, 37 | "engines": { 38 | "node": ">=0.8.0" 39 | } 40 | } 41 | }, 42 | "dependencies": { 43 | "typescript": { 44 | "version": "4.6.4", 45 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", 46 | "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", 47 | "dev": true 48 | }, 49 | "uglify-js": { 50 | "version": "3.17.1", 51 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", 52 | "integrity": "sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==", 53 | "dev": true 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "detectincognitojs", 3 | "version": "1.1.3", 4 | "description": "detectIncognito.js can be used to detect incognito mode & other private browsing modes on most modern browsers as of September 2022.", 5 | "main": "dist/detectIncognito.js", 6 | "types": "dist", 7 | "typings": "dist/detectIncognito.d.ts", 8 | "scripts": { 9 | "build": "tsc -p . && npx uglify-js ./dist/detectIncognito.js -o ./dist/detectIncognito.min.js" 10 | }, 11 | "files": [ 12 | "dist", 13 | "src" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/Joe12387/detectIncognito.git" 18 | }, 19 | "keywords": [ 20 | "incognito" 21 | ], 22 | "author": "Joe12387", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "typescript": "^4.6.4", 26 | "uglify-js": "^3.17.1" 27 | }, 28 | "dependencies": {} 29 | } 30 | -------------------------------------------------------------------------------- /src/@types/lib.dom.d.ts: -------------------------------------------------------------------------------- 1 | interface IDBVersionChangeEvent { 2 | target: IDBRequest; 3 | } 4 | -------------------------------------------------------------------------------- /src/detectIncognito.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * detectIncognito v1.1.3 - (c) 2022 Joe Rutkowski (https://github.com/Joe12387/detectIncognito) 4 | * 5 | **/ 6 | const detectIncognito = function (): Promise<{ 7 | isPrivate: boolean; 8 | browserName: string; 9 | }> { 10 | return new Promise(function (resolve, reject) { 11 | var browserName = "Unknown"; 12 | 13 | function __callback(isPrivate: any) { 14 | resolve({ 15 | isPrivate: isPrivate, 16 | browserName: browserName, 17 | }); 18 | } 19 | 20 | function identifyChromium() { 21 | var ua = navigator.userAgent; 22 | if (ua.match(/Chrome/)) { 23 | if ((navigator as any).brave !== undefined) { 24 | return "Brave"; 25 | } else if (ua.match(/Edg/)) { 26 | return "Edge"; 27 | } else if (ua.match(/OPR/)) { 28 | return "Opera"; 29 | } 30 | return "Chrome"; 31 | } else { 32 | return "Chromium"; 33 | } 34 | } 35 | 36 | function assertEvalToString(value: any) { 37 | return value === eval.toString().length; 38 | } 39 | 40 | function isSafari() { 41 | var v = navigator.vendor; 42 | return ( 43 | v !== undefined && v.indexOf("Apple") === 0 && assertEvalToString(37) 44 | ); 45 | } 46 | 47 | function isChrome() { 48 | var v = navigator.vendor; 49 | return ( 50 | v !== undefined && v.indexOf("Google") === 0 && assertEvalToString(33) 51 | ); 52 | } 53 | 54 | function isFirefox() { 55 | return ( 56 | document.documentElement !== undefined && 57 | (document as any).documentElement.style.MozAppearance !== undefined && 58 | assertEvalToString(37) 59 | ); 60 | } 61 | 62 | function isMSIE() { 63 | return ( 64 | (navigator as any).msSaveBlob !== undefined && assertEvalToString(39) 65 | ); 66 | } 67 | 68 | /** 69 | * Safari (Safari for iOS & macOS) 70 | **/ 71 | 72 | function newSafariTest() { 73 | var tmp_name = String(Math.random()); 74 | 75 | try { 76 | var db = window.indexedDB.open(tmp_name, 1); 77 | 78 | db.onupgradeneeded = function (i) { 79 | var res = i.target?.result; 80 | 81 | try { 82 | res.createObjectStore("test", { 83 | autoIncrement: true, 84 | }).put(new Blob); 85 | 86 | __callback(false); 87 | } catch (e) { 88 | var message = e; 89 | 90 | if (e instanceof Error) { 91 | message = e.message ?? e; 92 | } 93 | 94 | if (typeof message !== 'string') { 95 | return __callback(false); 96 | } 97 | 98 | var matchesExpectedError = /BlobURLs are not yet supported/.test(message); 99 | 100 | return __callback(matchesExpectedError); 101 | } finally { 102 | res.close(); 103 | window.indexedDB.deleteDatabase(tmp_name); 104 | } 105 | } 106 | } catch (e) { 107 | return __callback(false); 108 | } 109 | } 110 | 111 | function oldSafariTest() { 112 | var openDB = (window as any).openDatabase; 113 | var storage = window.localStorage; 114 | try { 115 | openDB(null, null, null, null); 116 | } catch (e) { 117 | return __callback(true); 118 | } 119 | try { 120 | storage.setItem("test", "1"); 121 | storage.removeItem("test"); 122 | } catch (e) { 123 | return __callback(true); 124 | } 125 | return __callback(false); 126 | } 127 | 128 | function safariPrivateTest() { 129 | if (navigator.maxTouchPoints !== undefined) { 130 | newSafariTest(); 131 | } else { 132 | oldSafariTest(); 133 | } 134 | } 135 | 136 | /** 137 | * Chrome 138 | **/ 139 | 140 | function getQuotaLimit() { 141 | var w = window as any; 142 | if ( 143 | w.performance !== undefined && 144 | w.performance.memory !== undefined && 145 | w.performance.memory.jsHeapSizeLimit !== undefined 146 | ) { 147 | return (performance as any).memory.jsHeapSizeLimit; 148 | } 149 | return 1073741824; 150 | } 151 | 152 | // >= 76 153 | function storageQuotaChromePrivateTest() { 154 | (navigator as any).webkitTemporaryStorage.queryUsageAndQuota( 155 | function (usage: any, quota: any) { 156 | __callback(quota < getQuotaLimit()); 157 | }, 158 | function (e: any) { 159 | reject( 160 | new Error( 161 | "detectIncognito somehow failed to query storage quota: " + 162 | e.message 163 | ) 164 | ); 165 | } 166 | ); 167 | } 168 | 169 | // 50 to 75 170 | function oldChromePrivateTest() { 171 | var fs = (window as any).webkitRequestFileSystem; 172 | var success = function () { 173 | __callback(false); 174 | }; 175 | var error = function () { 176 | __callback(true); 177 | }; 178 | fs(0, 1, success, error); 179 | } 180 | 181 | function chromePrivateTest() { 182 | if (self.Promise !== undefined && (self.Promise as any).allSettled !== undefined) { 183 | storageQuotaChromePrivateTest(); 184 | } else { 185 | oldChromePrivateTest(); 186 | } 187 | } 188 | 189 | /** 190 | * Firefox 191 | **/ 192 | 193 | function firefoxPrivateTest() { 194 | __callback(navigator.serviceWorker === undefined); 195 | } 196 | 197 | /** 198 | * MSIE 199 | **/ 200 | 201 | function msiePrivateTest() { 202 | __callback(window.indexedDB === undefined); 203 | } 204 | 205 | function main() { 206 | if (isSafari()) { 207 | browserName = 'Safari'; 208 | safariPrivateTest(); 209 | } else if (isChrome()) { 210 | browserName = identifyChromium(); 211 | chromePrivateTest(); 212 | } else if (isFirefox()) { 213 | browserName = "Firefox"; 214 | firefoxPrivateTest(); 215 | } else if (isMSIE()) { 216 | browserName = "Internet Explorer"; 217 | msiePrivateTest(); 218 | } else { 219 | reject(new Error("detectIncognito cannot determine the browser")); 220 | } 221 | } 222 | 223 | main(); 224 | }); 225 | }; 226 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "none", 5 | "rootDir": "./src", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "outDir": "./dist", 10 | "removeComments": false, 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "skipLibCheck": true, 15 | "noImplicitAny": false, 16 | "lib": [ 17 | "es2016", 18 | "dom" 19 | ] 20 | } 21 | } 22 | --------------------------------------------------------------------------------