is-incognito-mode 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +12 -8
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix: Chrome incognito false negative — raise default quota threshold from 120 MiB to 1 GiB.
|
|
8
|
+
|
|
9
|
+
Chrome 110+ raised the per-tab incognito storage ceiling: a private tab on a
|
|
10
|
+
desktop commonly reports `navigator.storage.estimate().quota` in the
|
|
11
|
+
500 MiB–1 GiB range. With the previous 120 MiB cutoff, every modern Chromium
|
|
12
|
+
incognito session was misclassified as normal.
|
|
13
|
+
|
|
14
|
+
`DEFAULT_PRIVATE_QUOTA_BYTES` is now `1024 × 1024 × 1024` (1 GiB), matching the
|
|
15
|
+
threshold used by the actively-maintained `detectIncognito` library and
|
|
16
|
+
covering Chromium, Firefox, and Safari private modes. Normal-mode quotas on
|
|
17
|
+
modern desktops are tens-to-hundreds of GiB so the safety margin is large.
|
|
18
|
+
|
|
19
|
+
A regression test pins the behaviour at a 800 MiB quota (the heart of the
|
|
20
|
+
modern Chromium incognito band).
|
|
21
|
+
|
|
22
|
+
If you previously depended on the 120 MiB cutoff, pass
|
|
23
|
+
`privateQuotaThresholdBytes: 120 * 1024 * 1024` to `detectIncognito()` to
|
|
24
|
+
restore the old behaviour.
|
|
25
|
+
|
|
26
|
+
## 2.0.1
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Docs and housekeeping pass following the v2.0.0 release.
|
|
31
|
+
- README leads with a 30-second tour before the "why" section so readers see
|
|
32
|
+
action first; the detection-flow Mermaid diagram is collapsible; use-cases
|
|
33
|
+
promoted to a scenario table; fixed a stale code-sample strategy name
|
|
34
|
+
(`storage-quota`, not `storage-estimate`).
|
|
35
|
+
- Removed unused `detectCurrentBrowser()` export (no production caller) and
|
|
36
|
+
the corresponding test. Only `detectBrowser()` taking explicit args is used.
|
|
37
|
+
- Removed the never-returned `'unknown'` member from `DetectionStrategyName`.
|
|
38
|
+
- `.gitignore` adds `.claude/`.
|
|
39
|
+
|
|
3
40
|
## 2.0.0
|
|
4
41
|
|
|
5
42
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ probes for older browsers. Click to expand:
|
|
|
113
113
|
```mermaid
|
|
114
114
|
flowchart TD
|
|
115
115
|
A[detectIncognito] --> B{navigator.storage<br/>.estimate available?}
|
|
116
|
-
B -- yes --> C{quota <
|
|
116
|
+
B -- yes --> C{quota < 1 GiB?}
|
|
117
117
|
C -- yes --> R1([private — high confidence])
|
|
118
118
|
C -- no --> R2([normal — high confidence])
|
|
119
119
|
B -- no --> D{which browser?}
|
|
@@ -128,11 +128,13 @@ flowchart TD
|
|
|
128
128
|
|
|
129
129
|
</details>
|
|
130
130
|
|
|
131
|
-
The default threshold is **
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
The default threshold is **1 GiB**. Modern Chromium incognito tabs report
|
|
132
|
+
quota in the **500 MiB–1 GiB** range (the cap was ~120 MiB pre-2022 but Chrome
|
|
133
|
+
raised it in 110+), and Firefox / Safari private modes are well below that.
|
|
134
|
+
Normal-mode quotas on a desktop are typically tens-to-hundreds of GiB, so the
|
|
135
|
+
margin is comfortable. Small-disk devices (low-end mobile, restricted ChromeOS
|
|
136
|
+
profiles) may produce false positives — override with
|
|
137
|
+
`privateQuotaThresholdBytes` if that matters to you.
|
|
136
138
|
|
|
137
139
|
---
|
|
138
140
|
|
|
@@ -183,8 +185,10 @@ const result = await detectIncognito({
|
|
|
183
185
|
});
|
|
184
186
|
```
|
|
185
187
|
|
|
186
|
-
Default is **
|
|
187
|
-
|
|
188
|
+
Default is **1 GiB** (matches the band where current Chrome / Firefox /
|
|
189
|
+
Safari incognito sessions report quota). Lower it if you trust a tighter
|
|
190
|
+
threshold for your audience, or raise it on devices with very little physical
|
|
191
|
+
storage where normal mode itself may report less than 1 GiB.
|
|
188
192
|
|
|
189
193
|
### Injecting globals (for testing)
|
|
190
194
|
|
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,7 @@ var IncognitoDetectionError = class extends Error {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
// src/strategies.ts
|
|
34
|
-
var DEFAULT_PRIVATE_QUOTA_BYTES =
|
|
34
|
+
var DEFAULT_PRIVATE_QUOTA_BYTES = 1024 * 1024 * 1024;
|
|
35
35
|
var PROBE_KEY = "__is_incognito_mode_probe_cd1394e6__";
|
|
36
36
|
async function runStrategies(browser, globals, options) {
|
|
37
37
|
const errors = [];
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/errors.ts","../src/strategies.ts","../src/detect.ts"],"names":["indexedDB"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Coarse browser-engine identifiers used to pick a detection strategy.\n *\n * We deliberately do not export brand names like \"Brave\" or \"Vivaldi\" — they\n * are all Chromium under the hood and the relevant private-mode behaviour\n * matches the engine, not the brand.\n */\nexport type BrowserName =\n | 'chromium'\n | 'firefox'\n | 'safari'\n | 'webkit'\n | 'edge-legacy'\n | 'ie'\n | 'unknown';\n\ninterface UserAgentSource {\n readonly userAgent: string;\n readonly vendor?: string;\n}\n\nconst CHROMIUM_HINTS = ['chrome/', 'crios/', 'chromium/', 'edg/', 'opr/'];\n\n/**\n * Classify the current navigator into a coarse browser engine.\n *\n * Pure function — accepts an optional `source` so it can be unit-tested\n * without polluting globals. In production we read from `navigator` directly.\n */\nexport function detectBrowser(source?: UserAgentSource): BrowserName {\n const ua = (source?.userAgent ?? '').toLowerCase();\n const vendor = (source?.vendor ?? '').toLowerCase();\n\n if (!ua) return 'unknown';\n\n if (ua.includes('msie ') || ua.includes('trident/')) return 'ie';\n if (ua.includes('edge/')) return 'edge-legacy';\n if (ua.includes('firefox/') || ua.includes('fxios/')) return 'firefox';\n\n const looksLikeChromium = CHROMIUM_HINTS.some((hint) => ua.includes(hint));\n if (looksLikeChromium) return 'chromium';\n\n // Safari sets `vendor === 'apple computer, inc.'`. iOS-Chrome/Firefox also\n // run on WebKit but advertise different UAs, so we reach this branch only\n // for \"real\" Safari and other WebKit shells.\n if (ua.includes('safari/') && (vendor.includes('apple') || !vendor)) {\n return 'safari';\n }\n if (ua.includes('applewebkit/')) return 'webkit';\n\n return 'unknown';\n}\n","/**\n * Stable identifiers for {@link IncognitoDetectionError} causes.\n *\n * Branching on `code` is safer than parsing `message` strings.\n */\nexport type IncognitoDetectionErrorCode =\n /** Invoked in a non-browser context (Node, worker without globals, etc.). */\n | 'NOT_A_BROWSER'\n /** Browser detected but no supported detection strategy applies. */\n | 'UNSUPPORTED_BROWSER'\n /** A probe threw before it could produce a definitive result. */\n | 'PROBE_FAILED';\n\n/**\n * Thrown when private-mode cannot be determined.\n *\n * @example\n * ```ts\n * import { isIncognito, IncognitoDetectionError } from 'is-incognito-mode';\n *\n * try {\n * await isIncognito();\n * } catch (error) {\n * if (error instanceof IncognitoDetectionError) {\n * console.warn(error.code, error.message);\n * }\n * }\n * ```\n */\nexport class IncognitoDetectionError extends Error {\n override readonly name = 'IncognitoDetectionError';\n readonly code: IncognitoDetectionErrorCode;\n\n constructor(\n code: IncognitoDetectionErrorCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.code = code;\n }\n}\n","import type { BrowserName } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Default cutoff: 120 MB. Below this, every shipping browser we tested in 2024\n * is in a restricted (private) storage context. Above, it is normal.\n *\n * Sources: detectIncognito.js research notes; cross-checked against Chrome\n * 120+, Firefox 121+, Safari 17+.\n */\nexport const DEFAULT_PRIVATE_QUOTA_BYTES = 120 * 1024 * 1024;\n\n/**\n * Sentinel key used by the legacy localStorage probe. Random enough that\n * collisions with real user keys are impossible.\n */\nconst PROBE_KEY = '__is_incognito_mode_probe_cd1394e6__';\n\n/**\n * Try strategies in priority order, returning the first one that produces a\n * definitive answer. Throws {@link IncognitoDetectionError} if every strategy\n * declines.\n */\nexport async function runStrategies(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult> {\n const errors: unknown[] = [];\n\n // 1. navigator.storage.estimate — the modern, vendor-blessed-ish signal.\n if (canUseStorageEstimate(globals)) {\n try {\n const result = await detectViaStorageQuota(browser, globals, options);\n if (result !== null) return result;\n } catch (error) {\n errors.push(error);\n }\n }\n\n // 2. Per-engine fallbacks for older versions / restricted runtimes.\n switch (browser) {\n case 'safari':\n case 'webkit': {\n const result = detectViaSafariStorage(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'firefox': {\n const result = await detectViaFirefoxIndexedDB(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'edge-legacy':\n case 'ie': {\n const result = detectViaLegacyEdge(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'chromium':\n case 'unknown': {\n break;\n }\n }\n\n throw new IncognitoDetectionError(\n browser === 'unknown' ? 'UNSUPPORTED_BROWSER' : 'PROBE_FAILED',\n `No detection strategy produced a verdict for browser \"${browser}\".`,\n errors.length > 0 ? { cause: errors[0] } : undefined,\n );\n}\n\nfunction canUseStorageEstimate(globals: DetectionGlobals): boolean {\n return typeof globals.navigator?.storage?.estimate === 'function';\n}\n\nasync function detectViaStorageQuota(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult | null> {\n const storage = globals.navigator?.storage;\n if (!storage?.estimate) return null;\n\n const estimate = await storage.estimate();\n const quota = typeof estimate.quota === 'number' ? estimate.quota : null;\n if (quota === null) return null;\n\n const isPrivate = quota < options.privateQuotaThresholdBytes;\n return {\n isPrivate,\n browser,\n confidence: 'high',\n quota,\n strategy: 'storage-quota',\n };\n}\n\nfunction detectViaSafariStorage(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const storage = globals.window?.localStorage;\n if (!storage) {\n // No localStorage at all is itself a strong signal on Safari < 11 private.\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n try {\n storage.setItem(PROBE_KEY, '1');\n storage.removeItem(PROBE_KEY);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'medium',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n const win = globals.window;\n if (typeof win.openDatabase === 'function') {\n try {\n win.openDatabase(null, null, null, null);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n }\n\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n}\n\nasync function detectViaFirefoxIndexedDB(\n browser: BrowserName,\n globals: DetectionGlobals,\n): Promise<DetectionResult | null> {\n const indexedDB = globals.indexedDB ?? globals.window?.indexedDB;\n if (!indexedDB) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n };\n }\n\n return new Promise<DetectionResult>((resolve) => {\n let request: IDBOpenDBRequest;\n try {\n request = indexedDB.open(PROBE_KEY);\n } catch {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n return;\n }\n\n request.addEventListener('error', () => {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n request.addEventListener('success', () => {\n try {\n request.result.close();\n indexedDB.deleteDatabase(PROBE_KEY);\n } catch {\n /* best-effort cleanup */\n }\n resolve({\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n });\n}\n\nfunction detectViaLegacyEdge(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const win = globals.window;\n if (!win) return null;\n const hasIndexedDB = Boolean(win.indexedDB ?? globals.indexedDB);\n const hasPointer = Boolean(win.PointerEvent ?? win.MSPointerEvent);\n if (!hasIndexedDB && hasPointer) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n }\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n}\n","import { detectBrowser } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport { DEFAULT_PRIVATE_QUOTA_BYTES, runStrategies } from './strategies.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Detect whether the current browser is in private / incognito mode.\n *\n * Returns a rich {@link DetectionResult}. Most callers want the thinner\n * {@link isIncognito} convenience instead.\n *\n * @throws {IncognitoDetectionError}\n * - `NOT_A_BROWSER` when invoked outside a browser-like environment.\n * - `UNSUPPORTED_BROWSER` when the browser was classified as `unknown` and\n * no fallback strategy applied.\n * - `PROBE_FAILED` when every applicable strategy threw before producing a\n * definitive answer.\n *\n * @example\n * ```ts\n * import { detectIncognito } from 'is-incognito-mode';\n *\n * const { isPrivate, browser, confidence, quota } = await detectIncognito();\n * console.log(`${browser} (${confidence} confidence, quota: ${quota ?? '?'})`);\n * ```\n */\nexport async function detectIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<DetectionResult> {\n const globals = resolveGlobals(options.globals);\n if (!isBrowserLike(globals)) {\n throw new IncognitoDetectionError(\n 'NOT_A_BROWSER',\n 'is-incognito-mode can only run in a browser-like environment.',\n );\n }\n\n const browser = detectBrowser({\n userAgent: globals.navigator.userAgent,\n ...(globals.navigator.vendor === undefined\n ? {}\n : { vendor: globals.navigator.vendor }),\n });\n\n const threshold =\n options.privateQuotaThresholdBytes ?? DEFAULT_PRIVATE_QUOTA_BYTES;\n\n return runStrategies(browser, globals, {\n privateQuotaThresholdBytes: threshold,\n });\n}\n\n/**\n * Convenience wrapper around {@link detectIncognito} that resolves to the\n * boolean verdict only. Drop-in compatible with v1's default export.\n *\n * @example\n * ```ts\n * import { isIncognito } from 'is-incognito-mode';\n *\n * if (await isIncognito()) {\n * showPaywall();\n * }\n * ```\n *\n * @throws {IncognitoDetectionError} See {@link detectIncognito}.\n */\nexport async function isIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<boolean> {\n const result = await detectIncognito(options);\n return result.isPrivate;\n}\n\nfunction resolveGlobals(\n overrides: DetectionGlobals | undefined,\n): DetectionGlobals {\n if (overrides) return overrides;\n return {\n navigator: typeof navigator === 'undefined' ? undefined : navigator,\n window: typeof window === 'undefined' ? undefined : window,\n indexedDB: typeof indexedDB === 'undefined' ? undefined : indexedDB,\n };\n}\n\nfunction isBrowserLike(\n globals: DetectionGlobals,\n): globals is DetectionGlobals & {\n navigator: NonNullable<DetectionGlobals['navigator']>;\n} {\n return Boolean(globals.navigator?.userAgent);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/errors.ts","../src/strategies.ts","../src/detect.ts"],"names":["indexedDB"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Coarse browser-engine identifiers used to pick a detection strategy.\n *\n * We deliberately do not export brand names like \"Brave\" or \"Vivaldi\" — they\n * are all Chromium under the hood and the relevant private-mode behaviour\n * matches the engine, not the brand.\n */\nexport type BrowserName =\n | 'chromium'\n | 'firefox'\n | 'safari'\n | 'webkit'\n | 'edge-legacy'\n | 'ie'\n | 'unknown';\n\ninterface UserAgentSource {\n readonly userAgent: string;\n readonly vendor?: string;\n}\n\nconst CHROMIUM_HINTS = ['chrome/', 'crios/', 'chromium/', 'edg/', 'opr/'];\n\n/**\n * Classify the current navigator into a coarse browser engine.\n *\n * Pure function — accepts an optional `source` so it can be unit-tested\n * without polluting globals. In production we read from `navigator` directly.\n */\nexport function detectBrowser(source?: UserAgentSource): BrowserName {\n const ua = (source?.userAgent ?? '').toLowerCase();\n const vendor = (source?.vendor ?? '').toLowerCase();\n\n if (!ua) return 'unknown';\n\n if (ua.includes('msie ') || ua.includes('trident/')) return 'ie';\n if (ua.includes('edge/')) return 'edge-legacy';\n if (ua.includes('firefox/') || ua.includes('fxios/')) return 'firefox';\n\n const looksLikeChromium = CHROMIUM_HINTS.some((hint) => ua.includes(hint));\n if (looksLikeChromium) return 'chromium';\n\n // Safari sets `vendor === 'apple computer, inc.'`. iOS-Chrome/Firefox also\n // run on WebKit but advertise different UAs, so we reach this branch only\n // for \"real\" Safari and other WebKit shells.\n if (ua.includes('safari/') && (vendor.includes('apple') || !vendor)) {\n return 'safari';\n }\n if (ua.includes('applewebkit/')) return 'webkit';\n\n return 'unknown';\n}\n","/**\n * Stable identifiers for {@link IncognitoDetectionError} causes.\n *\n * Branching on `code` is safer than parsing `message` strings.\n */\nexport type IncognitoDetectionErrorCode =\n /** Invoked in a non-browser context (Node, worker without globals, etc.). */\n | 'NOT_A_BROWSER'\n /** Browser detected but no supported detection strategy applies. */\n | 'UNSUPPORTED_BROWSER'\n /** A probe threw before it could produce a definitive result. */\n | 'PROBE_FAILED';\n\n/**\n * Thrown when private-mode cannot be determined.\n *\n * @example\n * ```ts\n * import { isIncognito, IncognitoDetectionError } from 'is-incognito-mode';\n *\n * try {\n * await isIncognito();\n * } catch (error) {\n * if (error instanceof IncognitoDetectionError) {\n * console.warn(error.code, error.message);\n * }\n * }\n * ```\n */\nexport class IncognitoDetectionError extends Error {\n override readonly name = 'IncognitoDetectionError';\n readonly code: IncognitoDetectionErrorCode;\n\n constructor(\n code: IncognitoDetectionErrorCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.code = code;\n }\n}\n","import type { BrowserName } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Default cutoff: 1 GiB. Below this, every shipping browser we tested is in a\n * restricted (private) storage context; above, it is normal.\n *\n * History: pre-2022 Chrome capped incognito at ~120 MiB and that was the\n * canonical threshold. Chrome 110+ raised the incognito ceiling and modern\n * versions can report 500 MiB–1 GiB for an incognito tab on a desktop, so the\n * old 120 MiB threshold misses Chromium-family private mode. 1 GiB\n * (1024 × 1024 × 1024) is the value the actively maintained `detectIncognito`\n * library settled on; we match it.\n *\n * On normal-mode desktops the quota is typically 10–80 % of free disk —\n * tens or hundreds of GiB — so this threshold has substantial margin.\n *\n * Devices with very small total storage (low-end mobiles, restricted ChromeOS\n * profiles) may produce false positives. Override with\n * `privateQuotaThresholdBytes` if that matters to you.\n */\nexport const DEFAULT_PRIVATE_QUOTA_BYTES = 1024 * 1024 * 1024;\n\n/**\n * Sentinel key used by the legacy localStorage probe. Random enough that\n * collisions with real user keys are impossible.\n */\nconst PROBE_KEY = '__is_incognito_mode_probe_cd1394e6__';\n\n/**\n * Try strategies in priority order, returning the first one that produces a\n * definitive answer. Throws {@link IncognitoDetectionError} if every strategy\n * declines.\n */\nexport async function runStrategies(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult> {\n const errors: unknown[] = [];\n\n // 1. navigator.storage.estimate — the modern, vendor-blessed-ish signal.\n if (canUseStorageEstimate(globals)) {\n try {\n const result = await detectViaStorageQuota(browser, globals, options);\n if (result !== null) return result;\n } catch (error) {\n errors.push(error);\n }\n }\n\n // 2. Per-engine fallbacks for older versions / restricted runtimes.\n switch (browser) {\n case 'safari':\n case 'webkit': {\n const result = detectViaSafariStorage(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'firefox': {\n const result = await detectViaFirefoxIndexedDB(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'edge-legacy':\n case 'ie': {\n const result = detectViaLegacyEdge(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'chromium':\n case 'unknown': {\n break;\n }\n }\n\n throw new IncognitoDetectionError(\n browser === 'unknown' ? 'UNSUPPORTED_BROWSER' : 'PROBE_FAILED',\n `No detection strategy produced a verdict for browser \"${browser}\".`,\n errors.length > 0 ? { cause: errors[0] } : undefined,\n );\n}\n\nfunction canUseStorageEstimate(globals: DetectionGlobals): boolean {\n return typeof globals.navigator?.storage?.estimate === 'function';\n}\n\nasync function detectViaStorageQuota(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult | null> {\n const storage = globals.navigator?.storage;\n if (!storage?.estimate) return null;\n\n const estimate = await storage.estimate();\n const quota = typeof estimate.quota === 'number' ? estimate.quota : null;\n if (quota === null) return null;\n\n const isPrivate = quota < options.privateQuotaThresholdBytes;\n return {\n isPrivate,\n browser,\n confidence: 'high',\n quota,\n strategy: 'storage-quota',\n };\n}\n\nfunction detectViaSafariStorage(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const storage = globals.window?.localStorage;\n if (!storage) {\n // No localStorage at all is itself a strong signal on Safari < 11 private.\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n try {\n storage.setItem(PROBE_KEY, '1');\n storage.removeItem(PROBE_KEY);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'medium',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n const win = globals.window;\n if (typeof win.openDatabase === 'function') {\n try {\n win.openDatabase(null, null, null, null);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n }\n\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n}\n\nasync function detectViaFirefoxIndexedDB(\n browser: BrowserName,\n globals: DetectionGlobals,\n): Promise<DetectionResult | null> {\n const indexedDB = globals.indexedDB ?? globals.window?.indexedDB;\n if (!indexedDB) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n };\n }\n\n return new Promise<DetectionResult>((resolve) => {\n let request: IDBOpenDBRequest;\n try {\n request = indexedDB.open(PROBE_KEY);\n } catch {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n return;\n }\n\n request.addEventListener('error', () => {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n request.addEventListener('success', () => {\n try {\n request.result.close();\n indexedDB.deleteDatabase(PROBE_KEY);\n } catch {\n /* best-effort cleanup */\n }\n resolve({\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n });\n}\n\nfunction detectViaLegacyEdge(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const win = globals.window;\n if (!win) return null;\n const hasIndexedDB = Boolean(win.indexedDB ?? globals.indexedDB);\n const hasPointer = Boolean(win.PointerEvent ?? win.MSPointerEvent);\n if (!hasIndexedDB && hasPointer) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n }\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n}\n","import { detectBrowser } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport { DEFAULT_PRIVATE_QUOTA_BYTES, runStrategies } from './strategies.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Detect whether the current browser is in private / incognito mode.\n *\n * Returns a rich {@link DetectionResult}. Most callers want the thinner\n * {@link isIncognito} convenience instead.\n *\n * @throws {IncognitoDetectionError}\n * - `NOT_A_BROWSER` when invoked outside a browser-like environment.\n * - `UNSUPPORTED_BROWSER` when the browser was classified as `unknown` and\n * no fallback strategy applied.\n * - `PROBE_FAILED` when every applicable strategy threw before producing a\n * definitive answer.\n *\n * @example\n * ```ts\n * import { detectIncognito } from 'is-incognito-mode';\n *\n * const { isPrivate, browser, confidence, quota } = await detectIncognito();\n * console.log(`${browser} (${confidence} confidence, quota: ${quota ?? '?'})`);\n * ```\n */\nexport async function detectIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<DetectionResult> {\n const globals = resolveGlobals(options.globals);\n if (!isBrowserLike(globals)) {\n throw new IncognitoDetectionError(\n 'NOT_A_BROWSER',\n 'is-incognito-mode can only run in a browser-like environment.',\n );\n }\n\n const browser = detectBrowser({\n userAgent: globals.navigator.userAgent,\n ...(globals.navigator.vendor === undefined\n ? {}\n : { vendor: globals.navigator.vendor }),\n });\n\n const threshold =\n options.privateQuotaThresholdBytes ?? DEFAULT_PRIVATE_QUOTA_BYTES;\n\n return runStrategies(browser, globals, {\n privateQuotaThresholdBytes: threshold,\n });\n}\n\n/**\n * Convenience wrapper around {@link detectIncognito} that resolves to the\n * boolean verdict only. Drop-in compatible with v1's default export.\n *\n * @example\n * ```ts\n * import { isIncognito } from 'is-incognito-mode';\n *\n * if (await isIncognito()) {\n * showPaywall();\n * }\n * ```\n *\n * @throws {IncognitoDetectionError} See {@link detectIncognito}.\n */\nexport async function isIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<boolean> {\n const result = await detectIncognito(options);\n return result.isPrivate;\n}\n\nfunction resolveGlobals(\n overrides: DetectionGlobals | undefined,\n): DetectionGlobals {\n if (overrides) return overrides;\n return {\n navigator: typeof navigator === 'undefined' ? undefined : navigator,\n window: typeof window === 'undefined' ? undefined : window,\n indexedDB: typeof indexedDB === 'undefined' ? undefined : indexedDB,\n };\n}\n\nfunction isBrowserLike(\n globals: DetectionGlobals,\n): globals is DetectionGlobals & {\n navigator: NonNullable<DetectionGlobals['navigator']>;\n} {\n return Boolean(globals.navigator?.userAgent);\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -51,8 +51,9 @@ interface DetectIncognitoOptions {
|
|
|
51
51
|
readonly globals?: DetectionGlobals;
|
|
52
52
|
/**
|
|
53
53
|
* Override the quota cutoff (bytes) below which `storage-quota` classifies
|
|
54
|
-
* the browser as private. Defaults to **
|
|
55
|
-
*
|
|
54
|
+
* the browser as private. Defaults to **1 GiB** — the threshold that
|
|
55
|
+
* separates incognito (≤ ~1 GiB on current Chrome / Firefox / Safari) from
|
|
56
|
+
* normal browsing (typically tens-to-hundreds of GiB).
|
|
56
57
|
*/
|
|
57
58
|
readonly privateQuotaThresholdBytes?: number;
|
|
58
59
|
}
|
|
@@ -164,11 +165,22 @@ declare class IncognitoDetectionError extends Error {
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
/**
|
|
167
|
-
* Default cutoff:
|
|
168
|
-
*
|
|
168
|
+
* Default cutoff: 1 GiB. Below this, every shipping browser we tested is in a
|
|
169
|
+
* restricted (private) storage context; above, it is normal.
|
|
169
170
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
171
|
+
* History: pre-2022 Chrome capped incognito at ~120 MiB and that was the
|
|
172
|
+
* canonical threshold. Chrome 110+ raised the incognito ceiling and modern
|
|
173
|
+
* versions can report 500 MiB–1 GiB for an incognito tab on a desktop, so the
|
|
174
|
+
* old 120 MiB threshold misses Chromium-family private mode. 1 GiB
|
|
175
|
+
* (1024 × 1024 × 1024) is the value the actively maintained `detectIncognito`
|
|
176
|
+
* library settled on; we match it.
|
|
177
|
+
*
|
|
178
|
+
* On normal-mode desktops the quota is typically 10–80 % of free disk —
|
|
179
|
+
* tens or hundreds of GiB — so this threshold has substantial margin.
|
|
180
|
+
*
|
|
181
|
+
* Devices with very small total storage (low-end mobiles, restricted ChromeOS
|
|
182
|
+
* profiles) may produce false positives. Override with
|
|
183
|
+
* `privateQuotaThresholdBytes` if that matters to you.
|
|
172
184
|
*/
|
|
173
185
|
declare const DEFAULT_PRIVATE_QUOTA_BYTES: number;
|
|
174
186
|
|
package/dist/index.d.ts
CHANGED
|
@@ -51,8 +51,9 @@ interface DetectIncognitoOptions {
|
|
|
51
51
|
readonly globals?: DetectionGlobals;
|
|
52
52
|
/**
|
|
53
53
|
* Override the quota cutoff (bytes) below which `storage-quota` classifies
|
|
54
|
-
* the browser as private. Defaults to **
|
|
55
|
-
*
|
|
54
|
+
* the browser as private. Defaults to **1 GiB** — the threshold that
|
|
55
|
+
* separates incognito (≤ ~1 GiB on current Chrome / Firefox / Safari) from
|
|
56
|
+
* normal browsing (typically tens-to-hundreds of GiB).
|
|
56
57
|
*/
|
|
57
58
|
readonly privateQuotaThresholdBytes?: number;
|
|
58
59
|
}
|
|
@@ -164,11 +165,22 @@ declare class IncognitoDetectionError extends Error {
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
/**
|
|
167
|
-
* Default cutoff:
|
|
168
|
-
*
|
|
168
|
+
* Default cutoff: 1 GiB. Below this, every shipping browser we tested is in a
|
|
169
|
+
* restricted (private) storage context; above, it is normal.
|
|
169
170
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
171
|
+
* History: pre-2022 Chrome capped incognito at ~120 MiB and that was the
|
|
172
|
+
* canonical threshold. Chrome 110+ raised the incognito ceiling and modern
|
|
173
|
+
* versions can report 500 MiB–1 GiB for an incognito tab on a desktop, so the
|
|
174
|
+
* old 120 MiB threshold misses Chromium-family private mode. 1 GiB
|
|
175
|
+
* (1024 × 1024 × 1024) is the value the actively maintained `detectIncognito`
|
|
176
|
+
* library settled on; we match it.
|
|
177
|
+
*
|
|
178
|
+
* On normal-mode desktops the quota is typically 10–80 % of free disk —
|
|
179
|
+
* tens or hundreds of GiB — so this threshold has substantial margin.
|
|
180
|
+
*
|
|
181
|
+
* Devices with very small total storage (low-end mobiles, restricted ChromeOS
|
|
182
|
+
* profiles) may produce false positives. Override with
|
|
183
|
+
* `privateQuotaThresholdBytes` if that matters to you.
|
|
172
184
|
*/
|
|
173
185
|
declare const DEFAULT_PRIVATE_QUOTA_BYTES: number;
|
|
174
186
|
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var IncognitoDetectionError = class extends Error {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// src/strategies.ts
|
|
30
|
-
var DEFAULT_PRIVATE_QUOTA_BYTES =
|
|
30
|
+
var DEFAULT_PRIVATE_QUOTA_BYTES = 1024 * 1024 * 1024;
|
|
31
31
|
var PROBE_KEY = "__is_incognito_mode_probe_cd1394e6__";
|
|
32
32
|
async function runStrategies(browser, globals, options) {
|
|
33
33
|
const errors = [];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/errors.ts","../src/strategies.ts","../src/detect.ts"],"names":["indexedDB"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Coarse browser-engine identifiers used to pick a detection strategy.\n *\n * We deliberately do not export brand names like \"Brave\" or \"Vivaldi\" — they\n * are all Chromium under the hood and the relevant private-mode behaviour\n * matches the engine, not the brand.\n */\nexport type BrowserName =\n | 'chromium'\n | 'firefox'\n | 'safari'\n | 'webkit'\n | 'edge-legacy'\n | 'ie'\n | 'unknown';\n\ninterface UserAgentSource {\n readonly userAgent: string;\n readonly vendor?: string;\n}\n\nconst CHROMIUM_HINTS = ['chrome/', 'crios/', 'chromium/', 'edg/', 'opr/'];\n\n/**\n * Classify the current navigator into a coarse browser engine.\n *\n * Pure function — accepts an optional `source` so it can be unit-tested\n * without polluting globals. In production we read from `navigator` directly.\n */\nexport function detectBrowser(source?: UserAgentSource): BrowserName {\n const ua = (source?.userAgent ?? '').toLowerCase();\n const vendor = (source?.vendor ?? '').toLowerCase();\n\n if (!ua) return 'unknown';\n\n if (ua.includes('msie ') || ua.includes('trident/')) return 'ie';\n if (ua.includes('edge/')) return 'edge-legacy';\n if (ua.includes('firefox/') || ua.includes('fxios/')) return 'firefox';\n\n const looksLikeChromium = CHROMIUM_HINTS.some((hint) => ua.includes(hint));\n if (looksLikeChromium) return 'chromium';\n\n // Safari sets `vendor === 'apple computer, inc.'`. iOS-Chrome/Firefox also\n // run on WebKit but advertise different UAs, so we reach this branch only\n // for \"real\" Safari and other WebKit shells.\n if (ua.includes('safari/') && (vendor.includes('apple') || !vendor)) {\n return 'safari';\n }\n if (ua.includes('applewebkit/')) return 'webkit';\n\n return 'unknown';\n}\n","/**\n * Stable identifiers for {@link IncognitoDetectionError} causes.\n *\n * Branching on `code` is safer than parsing `message` strings.\n */\nexport type IncognitoDetectionErrorCode =\n /** Invoked in a non-browser context (Node, worker without globals, etc.). */\n | 'NOT_A_BROWSER'\n /** Browser detected but no supported detection strategy applies. */\n | 'UNSUPPORTED_BROWSER'\n /** A probe threw before it could produce a definitive result. */\n | 'PROBE_FAILED';\n\n/**\n * Thrown when private-mode cannot be determined.\n *\n * @example\n * ```ts\n * import { isIncognito, IncognitoDetectionError } from 'is-incognito-mode';\n *\n * try {\n * await isIncognito();\n * } catch (error) {\n * if (error instanceof IncognitoDetectionError) {\n * console.warn(error.code, error.message);\n * }\n * }\n * ```\n */\nexport class IncognitoDetectionError extends Error {\n override readonly name = 'IncognitoDetectionError';\n readonly code: IncognitoDetectionErrorCode;\n\n constructor(\n code: IncognitoDetectionErrorCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.code = code;\n }\n}\n","import type { BrowserName } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Default cutoff: 120 MB. Below this, every shipping browser we tested in 2024\n * is in a restricted (private) storage context. Above, it is normal.\n *\n * Sources: detectIncognito.js research notes; cross-checked against Chrome\n * 120+, Firefox 121+, Safari 17+.\n */\nexport const DEFAULT_PRIVATE_QUOTA_BYTES = 120 * 1024 * 1024;\n\n/**\n * Sentinel key used by the legacy localStorage probe. Random enough that\n * collisions with real user keys are impossible.\n */\nconst PROBE_KEY = '__is_incognito_mode_probe_cd1394e6__';\n\n/**\n * Try strategies in priority order, returning the first one that produces a\n * definitive answer. Throws {@link IncognitoDetectionError} if every strategy\n * declines.\n */\nexport async function runStrategies(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult> {\n const errors: unknown[] = [];\n\n // 1. navigator.storage.estimate — the modern, vendor-blessed-ish signal.\n if (canUseStorageEstimate(globals)) {\n try {\n const result = await detectViaStorageQuota(browser, globals, options);\n if (result !== null) return result;\n } catch (error) {\n errors.push(error);\n }\n }\n\n // 2. Per-engine fallbacks for older versions / restricted runtimes.\n switch (browser) {\n case 'safari':\n case 'webkit': {\n const result = detectViaSafariStorage(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'firefox': {\n const result = await detectViaFirefoxIndexedDB(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'edge-legacy':\n case 'ie': {\n const result = detectViaLegacyEdge(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'chromium':\n case 'unknown': {\n break;\n }\n }\n\n throw new IncognitoDetectionError(\n browser === 'unknown' ? 'UNSUPPORTED_BROWSER' : 'PROBE_FAILED',\n `No detection strategy produced a verdict for browser \"${browser}\".`,\n errors.length > 0 ? { cause: errors[0] } : undefined,\n );\n}\n\nfunction canUseStorageEstimate(globals: DetectionGlobals): boolean {\n return typeof globals.navigator?.storage?.estimate === 'function';\n}\n\nasync function detectViaStorageQuota(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult | null> {\n const storage = globals.navigator?.storage;\n if (!storage?.estimate) return null;\n\n const estimate = await storage.estimate();\n const quota = typeof estimate.quota === 'number' ? estimate.quota : null;\n if (quota === null) return null;\n\n const isPrivate = quota < options.privateQuotaThresholdBytes;\n return {\n isPrivate,\n browser,\n confidence: 'high',\n quota,\n strategy: 'storage-quota',\n };\n}\n\nfunction detectViaSafariStorage(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const storage = globals.window?.localStorage;\n if (!storage) {\n // No localStorage at all is itself a strong signal on Safari < 11 private.\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n try {\n storage.setItem(PROBE_KEY, '1');\n storage.removeItem(PROBE_KEY);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'medium',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n const win = globals.window;\n if (typeof win.openDatabase === 'function') {\n try {\n win.openDatabase(null, null, null, null);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n }\n\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n}\n\nasync function detectViaFirefoxIndexedDB(\n browser: BrowserName,\n globals: DetectionGlobals,\n): Promise<DetectionResult | null> {\n const indexedDB = globals.indexedDB ?? globals.window?.indexedDB;\n if (!indexedDB) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n };\n }\n\n return new Promise<DetectionResult>((resolve) => {\n let request: IDBOpenDBRequest;\n try {\n request = indexedDB.open(PROBE_KEY);\n } catch {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n return;\n }\n\n request.addEventListener('error', () => {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n request.addEventListener('success', () => {\n try {\n request.result.close();\n indexedDB.deleteDatabase(PROBE_KEY);\n } catch {\n /* best-effort cleanup */\n }\n resolve({\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n });\n}\n\nfunction detectViaLegacyEdge(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const win = globals.window;\n if (!win) return null;\n const hasIndexedDB = Boolean(win.indexedDB ?? globals.indexedDB);\n const hasPointer = Boolean(win.PointerEvent ?? win.MSPointerEvent);\n if (!hasIndexedDB && hasPointer) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n }\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n}\n","import { detectBrowser } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport { DEFAULT_PRIVATE_QUOTA_BYTES, runStrategies } from './strategies.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Detect whether the current browser is in private / incognito mode.\n *\n * Returns a rich {@link DetectionResult}. Most callers want the thinner\n * {@link isIncognito} convenience instead.\n *\n * @throws {IncognitoDetectionError}\n * - `NOT_A_BROWSER` when invoked outside a browser-like environment.\n * - `UNSUPPORTED_BROWSER` when the browser was classified as `unknown` and\n * no fallback strategy applied.\n * - `PROBE_FAILED` when every applicable strategy threw before producing a\n * definitive answer.\n *\n * @example\n * ```ts\n * import { detectIncognito } from 'is-incognito-mode';\n *\n * const { isPrivate, browser, confidence, quota } = await detectIncognito();\n * console.log(`${browser} (${confidence} confidence, quota: ${quota ?? '?'})`);\n * ```\n */\nexport async function detectIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<DetectionResult> {\n const globals = resolveGlobals(options.globals);\n if (!isBrowserLike(globals)) {\n throw new IncognitoDetectionError(\n 'NOT_A_BROWSER',\n 'is-incognito-mode can only run in a browser-like environment.',\n );\n }\n\n const browser = detectBrowser({\n userAgent: globals.navigator.userAgent,\n ...(globals.navigator.vendor === undefined\n ? {}\n : { vendor: globals.navigator.vendor }),\n });\n\n const threshold =\n options.privateQuotaThresholdBytes ?? DEFAULT_PRIVATE_QUOTA_BYTES;\n\n return runStrategies(browser, globals, {\n privateQuotaThresholdBytes: threshold,\n });\n}\n\n/**\n * Convenience wrapper around {@link detectIncognito} that resolves to the\n * boolean verdict only. Drop-in compatible with v1's default export.\n *\n * @example\n * ```ts\n * import { isIncognito } from 'is-incognito-mode';\n *\n * if (await isIncognito()) {\n * showPaywall();\n * }\n * ```\n *\n * @throws {IncognitoDetectionError} See {@link detectIncognito}.\n */\nexport async function isIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<boolean> {\n const result = await detectIncognito(options);\n return result.isPrivate;\n}\n\nfunction resolveGlobals(\n overrides: DetectionGlobals | undefined,\n): DetectionGlobals {\n if (overrides) return overrides;\n return {\n navigator: typeof navigator === 'undefined' ? undefined : navigator,\n window: typeof window === 'undefined' ? undefined : window,\n indexedDB: typeof indexedDB === 'undefined' ? undefined : indexedDB,\n };\n}\n\nfunction isBrowserLike(\n globals: DetectionGlobals,\n): globals is DetectionGlobals & {\n navigator: NonNullable<DetectionGlobals['navigator']>;\n} {\n return Boolean(globals.navigator?.userAgent);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/errors.ts","../src/strategies.ts","../src/detect.ts"],"names":["indexedDB"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Coarse browser-engine identifiers used to pick a detection strategy.\n *\n * We deliberately do not export brand names like \"Brave\" or \"Vivaldi\" — they\n * are all Chromium under the hood and the relevant private-mode behaviour\n * matches the engine, not the brand.\n */\nexport type BrowserName =\n | 'chromium'\n | 'firefox'\n | 'safari'\n | 'webkit'\n | 'edge-legacy'\n | 'ie'\n | 'unknown';\n\ninterface UserAgentSource {\n readonly userAgent: string;\n readonly vendor?: string;\n}\n\nconst CHROMIUM_HINTS = ['chrome/', 'crios/', 'chromium/', 'edg/', 'opr/'];\n\n/**\n * Classify the current navigator into a coarse browser engine.\n *\n * Pure function — accepts an optional `source` so it can be unit-tested\n * without polluting globals. In production we read from `navigator` directly.\n */\nexport function detectBrowser(source?: UserAgentSource): BrowserName {\n const ua = (source?.userAgent ?? '').toLowerCase();\n const vendor = (source?.vendor ?? '').toLowerCase();\n\n if (!ua) return 'unknown';\n\n if (ua.includes('msie ') || ua.includes('trident/')) return 'ie';\n if (ua.includes('edge/')) return 'edge-legacy';\n if (ua.includes('firefox/') || ua.includes('fxios/')) return 'firefox';\n\n const looksLikeChromium = CHROMIUM_HINTS.some((hint) => ua.includes(hint));\n if (looksLikeChromium) return 'chromium';\n\n // Safari sets `vendor === 'apple computer, inc.'`. iOS-Chrome/Firefox also\n // run on WebKit but advertise different UAs, so we reach this branch only\n // for \"real\" Safari and other WebKit shells.\n if (ua.includes('safari/') && (vendor.includes('apple') || !vendor)) {\n return 'safari';\n }\n if (ua.includes('applewebkit/')) return 'webkit';\n\n return 'unknown';\n}\n","/**\n * Stable identifiers for {@link IncognitoDetectionError} causes.\n *\n * Branching on `code` is safer than parsing `message` strings.\n */\nexport type IncognitoDetectionErrorCode =\n /** Invoked in a non-browser context (Node, worker without globals, etc.). */\n | 'NOT_A_BROWSER'\n /** Browser detected but no supported detection strategy applies. */\n | 'UNSUPPORTED_BROWSER'\n /** A probe threw before it could produce a definitive result. */\n | 'PROBE_FAILED';\n\n/**\n * Thrown when private-mode cannot be determined.\n *\n * @example\n * ```ts\n * import { isIncognito, IncognitoDetectionError } from 'is-incognito-mode';\n *\n * try {\n * await isIncognito();\n * } catch (error) {\n * if (error instanceof IncognitoDetectionError) {\n * console.warn(error.code, error.message);\n * }\n * }\n * ```\n */\nexport class IncognitoDetectionError extends Error {\n override readonly name = 'IncognitoDetectionError';\n readonly code: IncognitoDetectionErrorCode;\n\n constructor(\n code: IncognitoDetectionErrorCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.code = code;\n }\n}\n","import type { BrowserName } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Default cutoff: 1 GiB. Below this, every shipping browser we tested is in a\n * restricted (private) storage context; above, it is normal.\n *\n * History: pre-2022 Chrome capped incognito at ~120 MiB and that was the\n * canonical threshold. Chrome 110+ raised the incognito ceiling and modern\n * versions can report 500 MiB–1 GiB for an incognito tab on a desktop, so the\n * old 120 MiB threshold misses Chromium-family private mode. 1 GiB\n * (1024 × 1024 × 1024) is the value the actively maintained `detectIncognito`\n * library settled on; we match it.\n *\n * On normal-mode desktops the quota is typically 10–80 % of free disk —\n * tens or hundreds of GiB — so this threshold has substantial margin.\n *\n * Devices with very small total storage (low-end mobiles, restricted ChromeOS\n * profiles) may produce false positives. Override with\n * `privateQuotaThresholdBytes` if that matters to you.\n */\nexport const DEFAULT_PRIVATE_QUOTA_BYTES = 1024 * 1024 * 1024;\n\n/**\n * Sentinel key used by the legacy localStorage probe. Random enough that\n * collisions with real user keys are impossible.\n */\nconst PROBE_KEY = '__is_incognito_mode_probe_cd1394e6__';\n\n/**\n * Try strategies in priority order, returning the first one that produces a\n * definitive answer. Throws {@link IncognitoDetectionError} if every strategy\n * declines.\n */\nexport async function runStrategies(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult> {\n const errors: unknown[] = [];\n\n // 1. navigator.storage.estimate — the modern, vendor-blessed-ish signal.\n if (canUseStorageEstimate(globals)) {\n try {\n const result = await detectViaStorageQuota(browser, globals, options);\n if (result !== null) return result;\n } catch (error) {\n errors.push(error);\n }\n }\n\n // 2. Per-engine fallbacks for older versions / restricted runtimes.\n switch (browser) {\n case 'safari':\n case 'webkit': {\n const result = detectViaSafariStorage(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'firefox': {\n const result = await detectViaFirefoxIndexedDB(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'edge-legacy':\n case 'ie': {\n const result = detectViaLegacyEdge(browser, globals);\n if (result !== null) return result;\n break;\n }\n case 'chromium':\n case 'unknown': {\n break;\n }\n }\n\n throw new IncognitoDetectionError(\n browser === 'unknown' ? 'UNSUPPORTED_BROWSER' : 'PROBE_FAILED',\n `No detection strategy produced a verdict for browser \"${browser}\".`,\n errors.length > 0 ? { cause: errors[0] } : undefined,\n );\n}\n\nfunction canUseStorageEstimate(globals: DetectionGlobals): boolean {\n return typeof globals.navigator?.storage?.estimate === 'function';\n}\n\nasync function detectViaStorageQuota(\n browser: BrowserName,\n globals: DetectionGlobals,\n options: Required<Pick<DetectIncognitoOptions, 'privateQuotaThresholdBytes'>>,\n): Promise<DetectionResult | null> {\n const storage = globals.navigator?.storage;\n if (!storage?.estimate) return null;\n\n const estimate = await storage.estimate();\n const quota = typeof estimate.quota === 'number' ? estimate.quota : null;\n if (quota === null) return null;\n\n const isPrivate = quota < options.privateQuotaThresholdBytes;\n return {\n isPrivate,\n browser,\n confidence: 'high',\n quota,\n strategy: 'storage-quota',\n };\n}\n\nfunction detectViaSafariStorage(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const storage = globals.window?.localStorage;\n if (!storage) {\n // No localStorage at all is itself a strong signal on Safari < 11 private.\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n try {\n storage.setItem(PROBE_KEY, '1');\n storage.removeItem(PROBE_KEY);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'medium',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n\n const win = globals.window;\n if (typeof win.openDatabase === 'function') {\n try {\n win.openDatabase(null, null, null, null);\n } catch {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n }\n }\n\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'safari-storage',\n };\n}\n\nasync function detectViaFirefoxIndexedDB(\n browser: BrowserName,\n globals: DetectionGlobals,\n): Promise<DetectionResult | null> {\n const indexedDB = globals.indexedDB ?? globals.window?.indexedDB;\n if (!indexedDB) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n };\n }\n\n return new Promise<DetectionResult>((resolve) => {\n let request: IDBOpenDBRequest;\n try {\n request = indexedDB.open(PROBE_KEY);\n } catch {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n return;\n }\n\n request.addEventListener('error', () => {\n resolve({\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n request.addEventListener('success', () => {\n try {\n request.result.close();\n indexedDB.deleteDatabase(PROBE_KEY);\n } catch {\n /* best-effort cleanup */\n }\n resolve({\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'firefox-indexeddb',\n });\n });\n });\n}\n\nfunction detectViaLegacyEdge(\n browser: BrowserName,\n globals: DetectionGlobals,\n): DetectionResult | null {\n const win = globals.window;\n if (!win) return null;\n const hasIndexedDB = Boolean(win.indexedDB ?? globals.indexedDB);\n const hasPointer = Boolean(win.PointerEvent ?? win.MSPointerEvent);\n if (!hasIndexedDB && hasPointer) {\n return {\n isPrivate: true,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n }\n return {\n isPrivate: false,\n browser,\n confidence: 'low',\n quota: null,\n strategy: 'legacy-edge',\n };\n}\n","import { detectBrowser } from './browser.ts';\nimport { IncognitoDetectionError } from './errors.ts';\nimport { DEFAULT_PRIVATE_QUOTA_BYTES, runStrategies } from './strategies.ts';\nimport type {\n DetectIncognitoOptions,\n DetectionGlobals,\n DetectionResult,\n} from './types.ts';\n\n/**\n * Detect whether the current browser is in private / incognito mode.\n *\n * Returns a rich {@link DetectionResult}. Most callers want the thinner\n * {@link isIncognito} convenience instead.\n *\n * @throws {IncognitoDetectionError}\n * - `NOT_A_BROWSER` when invoked outside a browser-like environment.\n * - `UNSUPPORTED_BROWSER` when the browser was classified as `unknown` and\n * no fallback strategy applied.\n * - `PROBE_FAILED` when every applicable strategy threw before producing a\n * definitive answer.\n *\n * @example\n * ```ts\n * import { detectIncognito } from 'is-incognito-mode';\n *\n * const { isPrivate, browser, confidence, quota } = await detectIncognito();\n * console.log(`${browser} (${confidence} confidence, quota: ${quota ?? '?'})`);\n * ```\n */\nexport async function detectIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<DetectionResult> {\n const globals = resolveGlobals(options.globals);\n if (!isBrowserLike(globals)) {\n throw new IncognitoDetectionError(\n 'NOT_A_BROWSER',\n 'is-incognito-mode can only run in a browser-like environment.',\n );\n }\n\n const browser = detectBrowser({\n userAgent: globals.navigator.userAgent,\n ...(globals.navigator.vendor === undefined\n ? {}\n : { vendor: globals.navigator.vendor }),\n });\n\n const threshold =\n options.privateQuotaThresholdBytes ?? DEFAULT_PRIVATE_QUOTA_BYTES;\n\n return runStrategies(browser, globals, {\n privateQuotaThresholdBytes: threshold,\n });\n}\n\n/**\n * Convenience wrapper around {@link detectIncognito} that resolves to the\n * boolean verdict only. Drop-in compatible with v1's default export.\n *\n * @example\n * ```ts\n * import { isIncognito } from 'is-incognito-mode';\n *\n * if (await isIncognito()) {\n * showPaywall();\n * }\n * ```\n *\n * @throws {IncognitoDetectionError} See {@link detectIncognito}.\n */\nexport async function isIncognito(\n options: DetectIncognitoOptions = {},\n): Promise<boolean> {\n const result = await detectIncognito(options);\n return result.isPrivate;\n}\n\nfunction resolveGlobals(\n overrides: DetectionGlobals | undefined,\n): DetectionGlobals {\n if (overrides) return overrides;\n return {\n navigator: typeof navigator === 'undefined' ? undefined : navigator,\n window: typeof window === 'undefined' ? undefined : window,\n indexedDB: typeof indexedDB === 'undefined' ? undefined : indexedDB,\n };\n}\n\nfunction isBrowserLike(\n globals: DetectionGlobals,\n): globals is DetectionGlobals & {\n navigator: NonNullable<DetectionGlobals['navigator']>;\n} {\n return Boolean(globals.navigator?.userAgent);\n}\n"]}
|
package/package.json
CHANGED