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 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 &lt; 120 MiB?}
116
+ B -- yes --> C{quota &lt; 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 **120 MiB** Chromium gives incognito tabs ~10 % of
132
- disk capped at 120 MiB, and Firefox / Safari private modes cap similarly.
133
- Devices with very small total storage can hit false positives; raise the
134
- threshold or lower-bound against `navigator.deviceMemory * 1 GiB` if that
135
- matters to you.
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 **120 MiB**. Raise it if you see false positives on small-disk
187
- devices.
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 = 120 * 1024 * 1024;
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 = [];
@@ -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 **120 MB** which matches the
55
- * Chromium / Firefox / Safari thresholds observed in 2023-2026.
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: 120 MB. Below this, every shipping browser we tested in 2024
168
- * is in a restricted (private) storage context. Above, it is normal.
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
- * Sources: detectIncognito.js research notes; cross-checked against Chrome
171
- * 120+, Firefox 121+, Safari 17+.
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 **120 MB** which matches the
55
- * Chromium / Firefox / Safari thresholds observed in 2023-2026.
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: 120 MB. Below this, every shipping browser we tested in 2024
168
- * is in a restricted (private) storage context. Above, it is normal.
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
- * Sources: detectIncognito.js research notes; cross-checked against Chrome
171
- * 120+, Firefox 121+, Safari 17+.
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 = 120 * 1024 * 1024;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "is-incognito-mode",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Reliably detect whether the user's browser is in private / incognito mode. Zero dependencies, dual ESM + CJS, fully typed.",
5
5
  "keywords": [
6
6
  "incognito",