Skip to content

V0.9.5#78

Open
IntegerAlex wants to merge 7 commits intomainfrom
v0.9.5
Open

V0.9.5#78
IntegerAlex wants to merge 7 commits intomainfrom
v0.9.5

Conversation

@IntegerAlex
Copy link
Owner

No description provided.

IntegerAlex and others added 2 commits February 19, 2026 18:37
Implement a structured validation and normalization layer for user-provided
configurations to ensure runtime stability. This update introduces a new
FingerprintError class

Co-authored-by: gitbot <gitbot@gossorg.in>
Update the project changelog and roadmap component to reflect the
upcoming 0.9.5 release focused on robustness

Co-authored-by: gitbot <gitbot@gossorg.in>
@cursor
Copy link

cursor bot commented Feb 19, 2026

You have run out of free Bugbot PR reviews for this billing cycle. This will reset on March 7.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@netlify
Copy link

netlify bot commented Feb 19, 2026

Deploy Preview for clever-starlight-3034ea failed. Why did it fail? →

Name Link
🔨 Latest commit 80b6845
🔍 Latest deploy log https://app.netlify.com/projects/clever-starlight-3034ea/deploys/699b1c694693880008501437

@netlify
Copy link

netlify bot commented Feb 19, 2026

Deploy Preview for fingerprint-oss failed. Why did it fail? →

Name Link
🔨 Latest commit 80b6845
🔍 Latest deploy log https://app.netlify.com/projects/fingerprint-oss/deploys/699b1c699a42e40008f0c864

@greptile-apps
Copy link

greptile-apps bot commented Feb 19, 2026

Greptile Summary

This PR introduces v0.9.5 focused on robustness and developer experience improvements. The changes add comprehensive configuration validation, structured error handling, configurable geolocation timeouts, and a minimal preset mode for reduced client compute.

Key Changes:

  • Introduced FingerprintError class with typed error codes (GEO_TIMEOUT, GEO_FAILED, CONFIG_INVALID, SYSTEM_INFO_FAILED, HASH_FAILED, UNKNOWN) for better error handling
  • Added validateUserInfoConfig() with normalization, clamping, and validation warnings/errors; supports strictConfig mode
  • Implemented configurable geoTimeout with AbortController-based timeout handling that gracefully falls back to mock data
  • Added preset: 'minimal' mode that skips heavy collectors (canvas, audio, WebGL fingerprints, full font detection) to reduce client-side compute
  • Integrated warnings surface that returns optional warnings array in userInfo() output
  • Enhanced telemetry to track structured error codes in spans and metrics

Implementation Quality:
The implementation is well-structured with proper error handling, fallback mechanisms, and backwards compatibility. The timeout implementation using AbortController is efficient and follows best practices.

Confidence Score: 4/5

  • This PR is safe to merge with minor considerations around the always-set geoTimeout default behavior
  • The implementation is solid with proper error handling, fallback mechanisms, and well-structured code. Score is 4/5 due to the config validation always setting geoTimeout to default (6000ms) even when undefined, which may have subtle implications for existing code that checks for undefined values. The timeout implementation, structured errors, and preset system are all implemented correctly with appropriate safeguards.
  • Pay close attention to src/config.ts validation logic and src/geo-ip.ts timeout implementation

Important Files Changed

Filename Overview
src/errors.ts New file adding structured error handling with FingerprintError class and typed error codes
src/config.ts Added config validation with geoTimeout, minimal preset support, and strictConfig mode; includes validation function with warnings/errors
src/geo-ip.ts Added timeout support using AbortController with configurable timeoutMs and warning callbacks; proper error handling for timeout scenarios
src/helper.ts Added config-based skipping for audio, WebGL, canvas fingerprinting and reduced font detection when minimal preset is active
src/index.ts Integrated config validation, structured errors, and warnings surface; updated telemetry to include error codes
src/systemInfo.ts Applied minimal preset logic to skip heavy collectors and use simpler hardwareConcurrency detection

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[userInfo called with config] --> B[validateUserInfoConfig]
    B --> C{Has validation errors?}
    C -->|Yes| D[Throw FingerprintError CONFIG_INVALID]
    C -->|No| E[Initialize config with normalized values]
    E --> F{preset === 'minimal'?}
    F -->|Yes| G[Set skip flags for heavy collectors]
    F -->|No| H[Use default collectors]
    G --> I[Fetch system info & geo data in parallel]
    H --> I
    I --> J{Geo timeout exceeded?}
    J -->|Yes| K[AbortController aborts fetch]
    K --> L[Create FingerprintError GEO_TIMEOUT]
    L --> M[Add warning to runtimeWarnings]
    M --> N[Return mock geo data]
    J -->|No| O[Process geo response]
    O --> P{Other errors?}
    P -->|Yes| Q[toFingerprintError conversion]
    Q --> R[Log to telemetry with error.code]
    P -->|No| S[Generate result JSON]
    N --> S
    S --> T{Has warnings?}
    T -->|Yes| U[Return result with warnings array]
    T -->|No| V[Return result]
    R --> W[Return fallback data]
Loading

Last reviewed commit: 5fb305f

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Disable sourcemap generation across all Rollup output formats including
CommonJS, ES Modules, and UMD bundles. This architectural decision

Co-authored-by: gitbot <gitbot@gossorg.in>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Configuration validation with warnings and strict-mode errors
    • Minimal preset to reduce data collection and compute
    • Geolocation timeout with graceful fallback and warnings
    • Structured fingerprint error codes exposed to consumers
    • Warnings array surfaced in results
  • Chores

    • Disabled sourcemaps for distribution builds
    • Demo site: dynamic sitemap, AI discovery endpoint (/llms.txt), and updated site metadata and footer links

Walkthrough

Adds config validation and a minimal preset, structured FingerprintError types and conversion, geolocation fetch with abort/timeout and warning callbacks, runtime flags to skip/reduce expensive collectors, telemetry error.code enrichment, rollup sourcemap disabled, .npmignore, demo sitemap/robots/llms additions, and e2e tests for v0.9.5 config behavior.

Changes

Cohort / File(s) Summary
Configuration & Types
src/config.ts, src/types.ts
Add UserInfoConfigInput and UserInfoValidationResult, validateUserInfoConfig, applyPresetMinimal, clampNumber; extend FingerprintConfig/UserInfoConfig with geoTimeout, preset, strictConfig and skip/reduce flags; initialize defaults (geoTimeout, preset, skip flags).
Errors
src/errors.ts, src/index.ts
Add FingerprintErrorCode union and FingerprintError class; add toFingerprintError helper; re-export error types/helpers from src/index.ts.
Geolocation
src/geo-ip.ts
Add GeoFetchOptions (timeoutMs, onWarning); implement AbortController timeout, richer error normalization via toFingerprintError, warnings propagation, and mock-data fallbacks.
Fingerprint Collection & Helpers
src/helper.ts, src/systemInfo.ts
Use getConfig() to short-circuit or return placeholders for audio, WebGL, canvas, and font detection when skip/reduce flags or minimal preset are set; adjust hardwareConcurrency in minimal mode.
Core Flow & Telemetry
src/index.ts, src/telemetry.ts
Validate/normalize userInfo input, collect runtime warnings, throw FingerprintError on invalid config, propagate geo timeout/onWarning, wrap errors with codes, and include error.code in telemetry spans/counters; export error helpers.
Build & Packaging
rollup.config.js, .npmignore
Disable sourcemaps for outputs; add .npmignore patterns to exclude maps and a leftover LICENSE.md.save file.
Demo site & Routes
fingerprint-oss-demo/lib/site.ts, fingerprint-oss-demo/app/sitemap.ts, fingerprint-oss-demo/app/robots.ts, fingerprint-oss-demo/app/api/llms/route.ts, fingerprint-oss-demo/next.config.mjs, fingerprint-oss-demo/components/footer.tsx, fingerprint-oss-demo/components/roadmap.tsx
Add site config (siteBaseUrl/siteRoutes/siteInfo); generate sitemap from siteRoutes; use siteBaseUrl in robots; add /api/llms route and rewrite for /llms.txt; update footer GitHub link and roadmap label.
Tests & Changelog
test/e2e/cypress/e2e/unit/config-v095.cy.js, CHANGELOG.md
Add Cypress e2e tests covering minimal preset, geoTimeout, strict/non-strict validation, clamping, and warnings; update changelog with Unreleased entries describing new features and structured errors.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Validator as Config Validator
    participant Preset as Preset Engine
    participant Geo as Geo Fetcher
    participant Errors as Error Handler
    participant Telemetry

    Client->>Validator: userInfo(config)
    Validator->>Validator: validateUserInfoConfig() / normalize / collect warnings
    alt Validation fails
        Validator-->>Errors: throw FingerprintError(CONFIG_INVALID)
        Errors->>Telemetry: record error (include error.code)
    else Validation passes
        Validator->>Preset: normalizedConfig
        Preset->>Preset: apply minimal preset? set skip flags & geoTimeout
        Preset->>Geo: fetchGeolocationInfo({timeoutMs, onWarning})
        Geo->>Geo: start AbortController with timeoutMs
        alt Geo timeout
            Geo->>Validator: onWarning("GEO_TIMEOUT"), return mock geo
        else Fetch error / non-OK
            Geo->>Errors: toFingerprintError(...)
            Errors->>Telemetry: record error (include error.code)
            Geo->>Validator: onWarning("GEO_FAILED"), return mock geo
        end
        Validator-->>Client: userInfo result (warnings[], systemInfo, hash)
        Client->>Telemetry: record spans/metrics (include error.code if present)
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Poem

🐰✨ I hopped through configs, neat and bright,
Timed geos gently and softened the bite,
Skipped heavy collectors to save a jog,
Tagged errors with codes for a tidy log,
A nibble of warnings, and off in a jog.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to assess relevance to the changeset. Add a detailed description explaining the purpose of this release, key features added (config validation, error handling, presets, timeouts), and any breaking changes or migration notes.
Title check ❓ Inconclusive The title 'V0.9.5' is vague and generic; it references only a version number without describing what the changeset actually accomplishes. Use a descriptive title that summarizes the main feature or change, such as 'Add config validation, geolocation timeout, and minimal preset support for v0.9.5' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v0.9.5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
fingerprint-oss-demo/components/roadmap.tsx (1)

42-48: ⚠️ Potential issue | 🟡 Minor

Pre-existing date inversion in RELEASE_HISTORY makes v0.9.0 appear after v0.9.2–0.9.4.

v0.9.0 is dated "2025-07-01" (July) while v0.9.2, 0.9.3, and 0.9.4 are all dated January 2025. Since the component renders these in declaration order and the file claims factual accuracy per CHANGELOG.md, this inverted date likely misleads readers. Worth correcting the date (or the ordering) in a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fingerprint-oss-demo/components/roadmap.tsx` around lines 42 - 48, The
RELEASE_HISTORY entry for version "0.9.0" in the roadmap component is dated
"2025-07-01", which places it after 0.9.2–0.9.4; update the RELEASE_HISTORY data
so chronology is correct by either fixing the "0.9.0" date to the actual release
date (make it earlier than the January 2025 entries) or reorder the array so
versions are in true chronological order; locate the RELEASE_HISTORY constant in
the roadmap component (the entry with version "0.9.0") and adjust the date or
array order accordingly to match CHANGELOG.md.
src/telemetry.ts (1)

46-46: ⚠️ Potential issue | 🟡 Minor

Stale serviceVersion in telemetry defaults.

The default serviceVersion is '0.9.3' but this PR is for v0.9.5. Telemetry spans will report the wrong version.

Fix
-    serviceVersion: '0.9.3',
+    serviceVersion: '0.9.5',

Consider deriving this from package.json or a shared constant to avoid manual updates each release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/telemetry.ts` at line 46, The telemetry default currently hardcodes
serviceVersion ('serviceVersion: \'0.9.3\') and must be updated; replace the
hardcoded string with a dynamic source (e.g., import the package version or a
shared VERSION constant) and set serviceVersion to that value so spans reflect
v0.9.5 automatically; locate the serviceVersion entry in src/telemetry.ts and
either (a) import { version } from your package.json and assign serviceVersion:
version, or (b) import a shared constant (e.g., APP_VERSION or VERSION) and
assign serviceVersion: APP_VERSION, ensuring the package version is kept in one
place to avoid future staleness.
🧹 Nitpick comments (9)
src/config.ts (2)

277-285: Telemetry sampleRate is silently clamped without a warning, unlike geoTimeout.

geoTimeout emits a warning when clamped (line 273–275), but sampleRate clamping on line 280 does not. For consistency and discoverability, consider adding a similar warning here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config.ts` around lines 277 - 285, The telemetry.sampleRate is being
silently clamped; update the branch that handles config.telemetry (the telemetry
object created from config.telemetry and assigned to normalized.telemetry) to
detect when telemetry.sampleRate is out of bounds and push a warning into
warnings similar to geoTimeout’s handling; use the same
clampNumber(telemetry.sampleRate, 0, 1, 0.1) call but before/after clamping
compare the original value and, if changed, push a descriptive message into
warnings indicating sampleRate was clamped and what the allowed range is.

215-292: Validation has a few notable behaviors worth confirming.

  1. Line 259: geoTimeout always gets a value (fallback 6000) even if the user didn't provide one. This silently injects a default into normalizedConfig, which could surprise callers who check normalizedConfig.geoTimeout != null to determine if the user explicitly set it.

  2. Line 260: Any preset value other than 'minimal' (including undefined) is normalized to 'default'. Same concern — the distinction between "user explicitly chose default" and "user didn't specify" is lost.

  3. Lines 264-275: Warning messages are generated after normalization, which is correct. However, line 273 warns when geoTimeout was clamped but not when it was replaced from a non-number type (e.g., geoTimeout: "fast" silently becomes 6000 with no warning).

Add a warning for non-numeric geoTimeout
+    if (config.geoTimeout != null && typeof config.geoTimeout !== 'number') {
+        warnings.push('Invalid "geoTimeout" type; expected number');
+    }
     if (typeof config.geoTimeout === 'number' && config.geoTimeout !== normalized.geoTimeout) {
         warnings.push(`"geoTimeout" clamped to ${normalized.geoTimeout}ms`);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config.ts` around lines 215 - 292, The validation currently always
injects defaults: change normalized.geoTimeout to only set when user provided a
number (use clampNumber only when typeof config.geoTimeout === 'number') and add
a warning when config.geoTimeout is present but not a number; also stop forcing
preset to 'default' by making normalized.preset reflect only explicit values
(set to 'minimal' if config.preset === 'minimal', to 'default' only if
config.preset === 'default', otherwise leave undefined) and keep
applyPresetMinimal only when normalized.preset === 'minimal'; update
validateUserInfoConfig to implement these changes and reference
normalized.geoTimeout, normalized.preset, clampNumber, applyPresetMinimal, and
the geoTimeout warning logic.
src/helper.ts (1)

36-38: Skip guards in helper.ts duplicate the checks in systemInfo.ts.

Each helper function (e.g., getAudioFingerprint, getWebGLInfo, getCanvasFingerprint, getFontPreferences) now short-circuits based on config flags. However, systemInfo.ts (lines 205–237) also checks these same flags and substitutes placeholder values before calling the helpers.

Having guards in both places is safe but redundant. The helper-level guards are the better location (self-contained functions protecting themselves), so consider removing the duplicate checks from systemInfo.ts to avoid maintaining two sets of skip logic and skip-return-value literals.

Also applies to: 145-147, 282-284, 388-390

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/helper.ts` around lines 36 - 38, Helpers like getAudioFingerprint,
getWebGLInfo, getCanvasFingerprint, and getFontPreferences already short-circuit
based on getConfig().skip* flags, so remove the redundant config checks and
placeholder substitutions from systemInfo.ts (the blocks that pre-check
skipAudioFingerprint/skipWebGL/skipCanvas/skipFontPreferences and return
literals) and let the helpers be the single source of truth; keep the
helper-level guards as-is and update systemInfo.ts to call the helpers directly,
deleting the duplicate conditional branches (also remove the similar duplicated
checks at the other noted locations).
src/systemInfo.ts (1)

205-237: Skip-guard return values are consistent with their helper counterparts — but see note on duplicate logic.

The placeholder objects here (e.g., { vendor: 'skipped', renderer: 'skipped', imageHash: 'minimal_preset' }) match exactly what getWebGLInfo() returns when skipWebGLFingerprint is true (helper.ts line 146). Same for canvas and fonts. This is fine but fragile — if a placeholder changes in one place, it must change in the other. As noted in the helper.ts review, consolidating to one location would eliminate this risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/systemInfo.ts` around lines 205 - 237, The current code duplicates
placeholder return objects for skipped fingerprints (e.g., the inline object
used when runtimeConfig.skipWebGLFingerprint vs the object returned by
getWebGLInfo(), and similarly for getCanvasFingerprint()/canvas and
getFontPreferences()/fontPreferences), which is fragile; fix this by removing
inline placeholder objects and delegating the skip logic to the helper functions
so callers always call getWebGLInfo(), getCanvasFingerprint(), and
getFontPreferences() (or equivalent helpers) and those helpers return the
appropriate placeholder when runtimeConfig indicates skipping, ensuring a single
source of truth for placeholder values.
src/types.ts (1)

179-184: UserInfoConfig vs UserInfoConfigInput divergence.

UserInfoConfig here lacks the telemetry field that UserInfoConfigInput in config.ts (line 61–68) includes. If these are meant to represent the same user-facing config shape, the divergence will confuse consumers. If they serve different purposes, a clarifying doc comment on each would help.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types.ts` around lines 179 - 184, UserInfoConfig in src/types.ts is
missing the telemetry field present on UserInfoConfigInput; either add a
telemetry property to UserInfoConfig with the same shape/type as
UserInfoConfigInput.telemetry (so both types are aligned), or if the divergence
is intentional, add a clarifying doc comment above UserInfoConfig explaining how
it differs from UserInfoConfigInput and why telemetry is omitted; reference the
two types by name (UserInfoConfig and UserInfoConfigInput) when making the
change so the shapes remain consistent and consumers aren’t confused.
src/errors.ts (1)

18-24: Pass cause to the Error constructor to enable the native error chain.

Error supports native cause chaining since ES2022 via the constructor's options object. Currently super(message) ignores the cause, so the built-in Error.cause property is not populated. This breaks compatibility with tools that walk standard error chains (e.g., OpenTelemetry, structured loggers). The explicit this.cause assignment on line 23 shadows the inherited property but doesn't activate the native mechanism.

Proposed fix
-    constructor(code: FingerprintErrorCode, message: string, options?: { details?: unknown; cause?: unknown }) {
-        super(message);
+    constructor(code: FingerprintErrorCode, message: string, options?: { details?: unknown; cause?: unknown }) {
+        super(message, options?.cause != null ? { cause: options.cause } : undefined);
         this.name = 'FingerprintError';
         this.code = code;
         this.details = options?.details;
-        this.cause = options?.cause;
     }

You can also remove the explicit cause property declaration on line 16 and rely on the inherited Error.cause instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/errors.ts` around lines 18 - 24, The constructor of FingerprintError
currently calls super(message) and then assigns this.cause, which prevents using
native Error.cause chaining; update the FingerprintError constructor to pass the
cause into the Error constructor (i.e., call super(message, { cause:
options?.cause })) and remove the explicit this.cause assignment (and the
separate cause property declaration if present) so the built-in Error.cause is
used; keep the existing assignments for this.name, this.code and this.details
(options?.details) unchanged.
CHANGELOG.md (1)

8-8: Should the header read [0.9.5] with a date?

The PR title and branch are v0.9.5, but the CHANGELOG uses [Unreleased]. Per Keep a Changelog convention, [Unreleased] is replaced with the version and date at release time. If this PR is the release, consider updating the header to ## [0.9.5] - 2026-02-19 to keep the log accurate upon merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 8, Update the CHANGELOG header currently reading "##
[Unreleased]" to the released version and date by replacing it with "## [0.9.5]
- 2026-02-19" so the file reflects this PR's release; modify the header line
(the line containing "## [Unreleased]") accordingly.
src/geo-ip.ts (1)

230-243: Unnecessary normalizedError allocation in the abort branch.

normalizedError (Line 231) is created for every fetch error, but the AbortError branch (Lines 232–239) never uses it — it constructs its own timeoutError instead. Moving the allocation below the abort check avoids the throwaway object.

♻️ Suggested reorder
             } catch (fetchError) {
-                const normalizedError = toFingerprintError(fetchError, 'GEO_FAILED', 'Geolocation fetch failed');
                 if ((fetchError as any)?.name === 'AbortError') {
                     const timeoutError = new FingerprintError('GEO_TIMEOUT', `Geolocation request timed out after ${timeoutMs}ms`, {
                         cause: fetchError
                     });
                     onWarning?.(timeoutError.message);
                     StructuredLogger.warn('fetchGeolocationInfo', `${timeoutError.message}, using mock data`, timeoutError);
                     return getMockGeolocationData();
                 }

+                const normalizedError = toFingerprintError(fetchError, 'GEO_FAILED', 'Geolocation fetch failed');
                 onWarning?.('Geolocation request failed, using mock data');
                 StructuredLogger.warn('fetchGeolocationInfo', 'Error fetching geolocation information, using mock data', normalizedError);
                 return getMockGeolocationData();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/geo-ip.ts` around lines 230 - 243, The code allocates normalizedError via
toFingerprintError(fetchError, 'GEO_FAILED', ...) before checking for an
AbortError, but normalizedError is unused in the AbortError branch; move the
toFingerprintError call so it only runs in the non-abort path: first check if
(fetchError as any)?.name === 'AbortError' and handle by creating the
FingerprintError('GEO_TIMEOUT', ...) and returning getMockGeolocationData(),
then after that branch call toFingerprintError(...) to produce normalizedError
for the remaining error handling and
StructuredLogger.warn('fetchGeolocationInfo', ..., normalizedError); keep use of
timeoutMs, onWarning, getMockGeolocationData, FingerprintError and
StructuredLogger.warn as in the original logic.
src/index.ts (1)

219-221: Consider always returning warnings (even if empty) for a stable return shape.

Currently warnings is conditionally present, producing an inconsistent return type. Consumers using TypeScript will see a union which makes destructuring awkward. Returning warnings: [] unconditionally simplifies the contract and avoids a shape-check at every call site.

♻️ Suggested change (both success and fallback paths)
-            if (runtimeWarnings.length > 0) {
-                return { ...result, warnings: runtimeWarnings };
-            }
-            return result;
+            return { ...result, warnings: runtimeWarnings };

Apply the same pattern to the fallback path (Lines 257–259).

Also applies to: 257-259

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.ts` around lines 219 - 221, The return shape is unstable because
`warnings` is only added when `runtimeWarnings.length > 0`; update both the
success return that spreads `result` and the fallback return to always include a
`warnings` array (e.g., `warnings: runtimeWarnings` or `warnings: []` when
null/undefined) so callers always receive `warnings` (even if empty); locate the
returns that use `result` and the fallback return and ensure they always set
`warnings: runtimeWarnings || []`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/types.ts`:
- Around line 159-165: There are duplicate declarations of the FingerprintConfig
type (one used by the public API and one used by internal config code) which can
drift; remove the duplicate by keeping a single FingerprintConfig definition in
types.ts and have config.ts import and use that exported type instead of
redeclaring it, updating any references (e.g., where FingerprintConfig is used
in config parsing/validation functions) to the imported symbol so the project
has one source of truth.

---

Outside diff comments:
In `@fingerprint-oss-demo/components/roadmap.tsx`:
- Around line 42-48: The RELEASE_HISTORY entry for version "0.9.0" in the
roadmap component is dated "2025-07-01", which places it after 0.9.2–0.9.4;
update the RELEASE_HISTORY data so chronology is correct by either fixing the
"0.9.0" date to the actual release date (make it earlier than the January 2025
entries) or reorder the array so versions are in true chronological order;
locate the RELEASE_HISTORY constant in the roadmap component (the entry with
version "0.9.0") and adjust the date or array order accordingly to match
CHANGELOG.md.

In `@src/telemetry.ts`:
- Line 46: The telemetry default currently hardcodes serviceVersion
('serviceVersion: \'0.9.3\') and must be updated; replace the hardcoded string
with a dynamic source (e.g., import the package version or a shared VERSION
constant) and set serviceVersion to that value so spans reflect v0.9.5
automatically; locate the serviceVersion entry in src/telemetry.ts and either
(a) import { version } from your package.json and assign serviceVersion:
version, or (b) import a shared constant (e.g., APP_VERSION or VERSION) and
assign serviceVersion: APP_VERSION, ensuring the package version is kept in one
place to avoid future staleness.

---

Nitpick comments:
In `@CHANGELOG.md`:
- Line 8: Update the CHANGELOG header currently reading "## [Unreleased]" to the
released version and date by replacing it with "## [0.9.5] - 2026-02-19" so the
file reflects this PR's release; modify the header line (the line containing "##
[Unreleased]") accordingly.

In `@src/config.ts`:
- Around line 277-285: The telemetry.sampleRate is being silently clamped;
update the branch that handles config.telemetry (the telemetry object created
from config.telemetry and assigned to normalized.telemetry) to detect when
telemetry.sampleRate is out of bounds and push a warning into warnings similar
to geoTimeout’s handling; use the same clampNumber(telemetry.sampleRate, 0, 1,
0.1) call but before/after clamping compare the original value and, if changed,
push a descriptive message into warnings indicating sampleRate was clamped and
what the allowed range is.
- Around line 215-292: The validation currently always injects defaults: change
normalized.geoTimeout to only set when user provided a number (use clampNumber
only when typeof config.geoTimeout === 'number') and add a warning when
config.geoTimeout is present but not a number; also stop forcing preset to
'default' by making normalized.preset reflect only explicit values (set to
'minimal' if config.preset === 'minimal', to 'default' only if config.preset ===
'default', otherwise leave undefined) and keep applyPresetMinimal only when
normalized.preset === 'minimal'; update validateUserInfoConfig to implement
these changes and reference normalized.geoTimeout, normalized.preset,
clampNumber, applyPresetMinimal, and the geoTimeout warning logic.

In `@src/errors.ts`:
- Around line 18-24: The constructor of FingerprintError currently calls
super(message) and then assigns this.cause, which prevents using native
Error.cause chaining; update the FingerprintError constructor to pass the cause
into the Error constructor (i.e., call super(message, { cause: options?.cause
})) and remove the explicit this.cause assignment (and the separate cause
property declaration if present) so the built-in Error.cause is used; keep the
existing assignments for this.name, this.code and this.details
(options?.details) unchanged.

In `@src/geo-ip.ts`:
- Around line 230-243: The code allocates normalizedError via
toFingerprintError(fetchError, 'GEO_FAILED', ...) before checking for an
AbortError, but normalizedError is unused in the AbortError branch; move the
toFingerprintError call so it only runs in the non-abort path: first check if
(fetchError as any)?.name === 'AbortError' and handle by creating the
FingerprintError('GEO_TIMEOUT', ...) and returning getMockGeolocationData(),
then after that branch call toFingerprintError(...) to produce normalizedError
for the remaining error handling and
StructuredLogger.warn('fetchGeolocationInfo', ..., normalizedError); keep use of
timeoutMs, onWarning, getMockGeolocationData, FingerprintError and
StructuredLogger.warn as in the original logic.

In `@src/helper.ts`:
- Around line 36-38: Helpers like getAudioFingerprint, getWebGLInfo,
getCanvasFingerprint, and getFontPreferences already short-circuit based on
getConfig().skip* flags, so remove the redundant config checks and placeholder
substitutions from systemInfo.ts (the blocks that pre-check
skipAudioFingerprint/skipWebGL/skipCanvas/skipFontPreferences and return
literals) and let the helpers be the single source of truth; keep the
helper-level guards as-is and update systemInfo.ts to call the helpers directly,
deleting the duplicate conditional branches (also remove the similar duplicated
checks at the other noted locations).

In `@src/index.ts`:
- Around line 219-221: The return shape is unstable because `warnings` is only
added when `runtimeWarnings.length > 0`; update both the success return that
spreads `result` and the fallback return to always include a `warnings` array
(e.g., `warnings: runtimeWarnings` or `warnings: []` when null/undefined) so
callers always receive `warnings` (even if empty); locate the returns that use
`result` and the fallback return and ensure they always set `warnings:
runtimeWarnings || []`.

In `@src/systemInfo.ts`:
- Around line 205-237: The current code duplicates placeholder return objects
for skipped fingerprints (e.g., the inline object used when
runtimeConfig.skipWebGLFingerprint vs the object returned by getWebGLInfo(), and
similarly for getCanvasFingerprint()/canvas and
getFontPreferences()/fontPreferences), which is fragile; fix this by removing
inline placeholder objects and delegating the skip logic to the helper functions
so callers always call getWebGLInfo(), getCanvasFingerprint(), and
getFontPreferences() (or equivalent helpers) and those helpers return the
appropriate placeholder when runtimeConfig indicates skipping, ensuring a single
source of truth for placeholder values.

In `@src/types.ts`:
- Around line 179-184: UserInfoConfig in src/types.ts is missing the telemetry
field present on UserInfoConfigInput; either add a telemetry property to
UserInfoConfig with the same shape/type as UserInfoConfigInput.telemetry (so
both types are aligned), or if the divergence is intentional, add a clarifying
doc comment above UserInfoConfig explaining how it differs from
UserInfoConfigInput and why telemetry is omitted; reference the two types by
name (UserInfoConfig and UserInfoConfigInput) when making the change so the
shapes remain consistent and consumers aren’t confused.

Comment on lines +159 to +165
geoTimeout?: number;
preset?: 'default' | 'minimal';
skipCanvasFingerprint?: boolean;
skipAudioFingerprint?: boolean;
skipWebGLFingerprint?: boolean;
reduceFontDetection?: boolean;
skipGeolocation?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find FingerprintConfig definitions and their context
rg -n 'interface FingerprintConfig|type FingerprintConfig|export.*FingerprintConfig' --type=ts -A5 -B2 src/types.ts src/config.ts

Repository: IntegerAlex/fingerprint-oss

Length of output: 2057


🏁 Script executed:

# Check imports in config.ts to see if it imports from types.ts
head -30 src/config.ts | grep -E "import|from"

Repository: IntegerAlex/fingerprint-oss

Length of output: 53


🏁 Script executed:

# Count occurrences and context of FingerprintConfig across codebase
rg -n 'FingerprintConfig' --type=ts | head -40

Repository: IntegerAlex/fingerprint-oss

Length of output: 654


🏁 Script executed:

# Read the full FingerprintConfig definition in types.ts
sed -n '151,175p' src/types.ts

Repository: IntegerAlex/fingerprint-oss

Length of output: 804


🏁 Script executed:

# Read the full FingerprintConfig definition in config.ts along with imports
sed -n '1,80p' src/config.ts

Repository: IntegerAlex/fingerprint-oss

Length of output: 2311


🏁 Script executed:

# Check what is imported in index.ts
rg -n 'import.*FingerprintConfig' src/index.ts -B5 -A2

Repository: IntegerAlex/fingerprint-oss

Length of output: 53


🏁 Script executed:

# Check full imports section of config.ts (first 100 lines)
head -100 src/config.ts | head -20

Repository: IntegerAlex/fingerprint-oss

Length of output: 573


🏁 Script executed:

# Check if config.ts imports anything from types.ts
rg 'import.*from.*types' src/config.ts

Repository: IntegerAlex/fingerprint-oss

Length of output: 53


🏁 Script executed:

# Check what index.ts imports from config.ts and types.ts
rg 'import.*from.*[./]*(types|config)' src/index.ts | head -20

Repository: IntegerAlex/fingerprint-oss

Length of output: 117


🏁 Script executed:

# Check all imports of FingerprintConfig across the codebase
rg 'import.*FingerprintConfig.*from' --type=ts

Repository: IntegerAlex/fingerprint-oss

Length of output: 53


🏁 Script executed:

# Check the exports from index.ts to see what public API is exposed
rg 'export.*FingerprintConfig|export.*from.*config|export.*from.*types' src/index.ts | head -20

Repository: IntegerAlex/fingerprint-oss

Length of output: 53


🏁 Script executed:

# Search for all FingerprintConfig references to understand usage pattern
rg 'FingerprintConfig' --type=ts src/

Repository: IntegerAlex/fingerprint-oss

Length of output: 624


🏁 Script executed:

# Check if index.ts re-exports from config.ts or types.ts
cat src/index.ts | grep -A2 'export'

Repository: IntegerAlex/fingerprint-oss

Length of output: 1455


🏁 Script executed:

# Find the specific import statement for FingerprintConfig in index.ts
grep -n 'import.*FingerprintConfig' src/index.ts

Repository: IntegerAlex/fingerprint-oss

Length of output: 53


🏁 Script executed:

# Get the complete export statement with context to see which module provides FingerprintConfig
sed -n '1,50p' src/index.ts | grep -A10 -B10 FingerprintConfig

Repository: IntegerAlex/fingerprint-oss

Length of output: 53


🏁 Script executed:

# List all imports at the top of index.ts
head -50 src/index.ts

Repository: IntegerAlex/fingerprint-oss

Length of output: 1883


🏁 Script executed:

# Search for the export type statement that includes FingerprintConfig
rg -n 'export type' src/index.ts -A5 -B5

Repository: IntegerAlex/fingerprint-oss

Length of output: 1207


Consolidate duplicate FingerprintConfig definition.

FingerprintConfig is independently declared in both types.ts (lines 151–165) and config.ts (lines 20–45) with identical fields. Since config.ts does not import from types.ts, the two definitions can diverge if either is modified independently. The public API re-exports from types.ts, but internal config functions use the config.ts version, creating a potential mismatch.

Establish a single source of truth by defining FingerprintConfig in one module and importing it in the other.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types.ts` around lines 159 - 165, There are duplicate declarations of the
FingerprintConfig type (one used by the public API and one used by internal
config code) which can drift; remove the duplicate by keeping a single
FingerprintConfig definition in types.ts and have config.ts import and use that
exported type instead of redeclaring it, updating any references (e.g., where
FingerprintConfig is used in config parsing/validation functions) to the
imported symbol so the project has one source of truth.

IntegerAlex and others added 2 commits February 19, 2026 19:02
Introduce a comprehensive Cypress E2E test suite targeting the
configuration logic introduced in version 0.9.

Co-authored-by: gitbot <gitbot@gossorg.in>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
test/e2e/cypress/e2e/unit/config-v095.cy.js (2)

1-179: DRY opportunity: extract repeated cy.loadFingerprintOSS() wrapper into beforeEach

All seven tests repeat the identical cy.loadFingerprintOSS().then((fp) => { ... }) wrapping pattern. Hoisting the load into a beforeEach hook reduces boilerplate, makes individual tests easier to read, and centralises any future changes to the load mechanism.

♻️ Proposed refactor skeleton
 describe('v0.9.5 configuration features', () => {
+  let fp;
+
+  beforeEach(() => {
+    cy.loadFingerprintOSS().then((loaded) => {
+      fp = loaded;
+    });
+  });
+
   it('minimal preset skips heavy collectors and generates warnings', () => {
-    cy.loadFingerprintOSS().then((fp) => {
-      return fp({ preset: 'minimal' }).then((result) => {
-        // ...
-      });
+    cy.wrap(fp({ preset: 'minimal' })).then((result) => {
+      // ...
     });
   });
   // repeat pattern for remaining tests
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/cypress/e2e/unit/config-v095.cy.js` around lines 1 - 179, Tests
repeatedly call cy.loadFingerprintOSS() inside each it block; extract that call
into a beforeEach to DRY up the suite. Add a beforeEach that calls
cy.loadFingerprintOSS().then(fp => { this.fp = fp }) or uses a locally-scoped
variable via let fp; assign the returned factory there, and update each it to
use that fp instead of calling cy.loadFingerprintOSS() again; ensure references
to the factory in tests still use the same symbol (fp) and retain existing
promise handling/returns.

173-175: Misleading comment — transparency message is never asserted

The comment // Should still have transparency message implies an assertion about the transparency config, but the following lines only check result.systemInfo and result.hash. Either add an assertion that the transparency-related content is present in the result, or remove/update the comment to reflect what is actually being validated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/cypress/e2e/unit/config-v095.cy.js` around lines 173 - 175, The
comment "Should still have transparency message" is misleading because the test
only asserts result.systemInfo and result.hash; update the test to either (A)
add an explicit assertion for the transparency content (e.g., check
result.systemInfo.transparency or that result.systemInfo contains the expected
transparency string) using the same expect(...) style as result.systemInfo and
result.hash, or (B) change the comment to accurately describe the existing
assertions (e.g., "Should have system info and hash"); locate the assertion
block around result.systemInfo and result.hash and implement one of these two
fixes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/e2e/cypress/e2e/unit/config-v095.cy.js`:
- Line 149: The test currently hardcodes the pipe-separated enum string in the
assertion (expect(envWarnings.some(w => w.includes('TEST|DEV|STAGING|PROD')))),
which couples the test to formatting; update the assertion to instead verify the
presence of each allowed environment token individually (e.g., check that at
least one warning contains 'TEST', one contains 'DEV', etc.) or assert for a
stable substring like 'environment' plus individual value checks using the
existing envWarnings array so reordering or separator changes in src/config.ts
won't break the test.
- Around line 113-132: The geoTimeout test uses geoTimeout: 50000 which is
clamped to 20000ms and can stall CI; update the tests that only assert the
clamping warning (the "geoTimeout clamping generates warnings" test and the
"strictConfig accepts valid configurations" case using geoTimeout: 5000) to use
a very small timeout (e.g., 100) or mock the geo-IP network call so fp()
resolves immediately; locate the fp() invocation in the tests
(cy.loadFingerprintOSS().then(fp => fp(configWithClampedTimeout) / strictConfig
test) and either change the geoTimeout values to a short number or add a
cy.intercept() for the geo-IP endpoint before calling fp() so the assertion on
result.warnings still runs without waiting for the clamped abort delay.
- Around line 66-88: The test "strictConfig accepts valid configurations"
currently guards the config-errors assertion with if (result.warnings &&
result.warnings.length > 0), which can silently skip the check; change it to
unconditionally assert that result.warnings is an array
(expect(result.warnings).to.be.an('array')) and then compute configErrors from
result.warnings and assert expect(configErrors).to.have.length(0) so the
validation runs every time; also reduce or remove the long geoTimeout in the
validConfig object (the geoTimeout property on validConfig) to a much smaller
value or drop it to avoid CI hangs when geo-IP is unreachable.
- Around line 22-34: The current assertion in the test uses
result.warnings.some(...) with an OR that accepts any warning containing 'geo',
which lets the clamping warning pass; update the check on the hasTimeoutWarning
variable to only detect actual timeout messages (e.g., look for 'timed out' or
'timeout' alone) and exclude warnings that contain 'geo' (or match the clamping
message). Locate the test using fp({ geoTimeout: 1 }) and the hasTimeoutWarning
computation and replace the loose condition with a stricter includes check (or a
regex) that only matches real timeout wording before asserting
expect(hasTimeoutWarning).to.be.true.

---

Nitpick comments:
In `@test/e2e/cypress/e2e/unit/config-v095.cy.js`:
- Around line 1-179: Tests repeatedly call cy.loadFingerprintOSS() inside each
it block; extract that call into a beforeEach to DRY up the suite. Add a
beforeEach that calls cy.loadFingerprintOSS().then(fp => { this.fp = fp }) or
uses a locally-scoped variable via let fp; assign the returned factory there,
and update each it to use that fp instead of calling cy.loadFingerprintOSS()
again; ensure references to the factory in tests still use the same symbol (fp)
and retain existing promise handling/returns.
- Around line 173-175: The comment "Should still have transparency message" is
misleading because the test only asserts result.systemInfo and result.hash;
update the test to either (A) add an explicit assertion for the transparency
content (e.g., check result.systemInfo.transparency or that result.systemInfo
contains the expected transparency string) using the same expect(...) style as
result.systemInfo and result.hash, or (B) change the comment to accurately
describe the existing assertions (e.g., "Should have system info and hash");
locate the assertion block around result.systemInfo and result.hash and
implement one of these two fixes.

Comment on lines +22 to +34
it('geoTimeout generates warnings when fetch times out', () => {
cy.loadFingerprintOSS().then((fp) => {
// Very short timeout to force timeout behavior
return fp({ geoTimeout: 1 }).then((result) => {
expect(result).to.be.ok;
expect(result).to.have.property('warnings');
expect(result.warnings).to.be.an('array');

// Should include a timeout-related warning
const hasTimeoutWarning = result.warnings.some(warning =>
warning.includes('timeout') || warning.includes('geo')
);
expect(hasTimeoutWarning).to.be.true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for geoTimeout configuration and clamping logic
rg -i "geoTimeout" --type ts --type js -B 3 -A 5 | head -200

Repository: IntegerAlex/fingerprint-oss

Length of output: 10016


Test assertion is loose enough to pass on a clamping warning rather than a timeout warning

The assertion on lines 31–33 accepts any warning containing 'timeout' or 'geo'. When geoTimeout: 1 is passed, the implementation clamps it to the minimum of 1000ms and emits the warning: "geoTimeout" clamped to 1000ms". This warning contains the substring 'geo' and will satisfy the condition without a real timeout ever occurring. The test will pass based on the clamping warning alone, making the test name ("generates warnings when fetch times out") misleading.

Replace the loose || logic with a check for an actual timeout indicator (e.g., 'timed out', 'timeout' alone, or check result.warnings.some(w => w.includes('timed out'))). Note that the clamping behavior is already tested explicitly in the "geoTimeout clamping generates warnings" test (lines 113–132).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/cypress/e2e/unit/config-v095.cy.js` around lines 22 - 34, The
current assertion in the test uses result.warnings.some(...) with an OR that
accepts any warning containing 'geo', which lets the clamping warning pass;
update the check on the hasTimeoutWarning variable to only detect actual timeout
messages (e.g., look for 'timed out' or 'timeout' alone) and exclude warnings
that contain 'geo' (or match the clamping message). Locate the test using fp({
geoTimeout: 1 }) and the hasTimeoutWarning computation and replace the loose
condition with a stricter includes check (or a regex) that only matches real
timeout wording before asserting expect(hasTimeoutWarning).to.be.true.

Comment on lines +66 to +88
it('strictConfig accepts valid configurations', () => {
cy.loadFingerprintOSS().then((fp) => {
const validConfig = {
strictConfig: true,
transparency: true,
message: 'test message',
preset: 'minimal',
geoTimeout: 5000
};

return fp(validConfig).then((result) => {
expect(result).to.be.ok;
expect(result).to.have.property('hash');
// Should not have config validation errors
if (result.warnings && result.warnings.length > 0) {
const configErrors = result.warnings.filter(w =>
w.includes('Unknown config key') || w.includes('Invalid configuration')
);
expect(configErrors).to.have.length(0);
}
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Conditional warning assertion silently skips the config-error check when warnings is absent or empty

Line 80 wraps the configErrors assertion in if (result.warnings && result.warnings.length > 0). If result.warnings is undefined or an empty array, the assertion is never executed and the test passes trivially. Every other test in this file unconditionally asserts expect(result.warnings).to.be.an('array'). The guard here is inconsistent and could mask a regression where warnings is unexpectedly absent.

Additionally, geoTimeout: 5000 means this test can hang for up to 5 seconds in CI environments where the geo-IP service is unreachable (see also the more severe instance below at lines 113–132).

🛠️ Proposed fix
       return fp(validConfig).then((result) => {
         expect(result).to.be.ok;
         expect(result).to.have.property('hash');
-        // Should not have config validation errors
-        if (result.warnings && result.warnings.length > 0) {
-          const configErrors = result.warnings.filter(w =>
-            w.includes('Unknown config key') || w.includes('Invalid configuration')
-          );
-          expect(configErrors).to.have.length(0);
-        }
+        expect(result).to.have.property('warnings');
+        expect(result.warnings).to.be.an('array');
+        const configErrors = result.warnings.filter(w =>
+          w.includes('Unknown config key') || w.includes('Invalid configuration')
+        );
+        expect(configErrors).to.have.length(0);
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/cypress/e2e/unit/config-v095.cy.js` around lines 66 - 88, The test
"strictConfig accepts valid configurations" currently guards the config-errors
assertion with if (result.warnings && result.warnings.length > 0), which can
silently skip the check; change it to unconditionally assert that
result.warnings is an array (expect(result.warnings).to.be.an('array')) and then
compute configErrors from result.warnings and assert
expect(configErrors).to.have.length(0) so the validation runs every time; also
reduce or remove the long geoTimeout in the validConfig object (the geoTimeout
property on validConfig) to a much smaller value or drop it to avoid CI hangs
when geo-IP is unreachable.

Comment on lines +113 to +132
it('geoTimeout clamping generates warnings', () => {
cy.loadFingerprintOSS().then((fp) => {
// Test with out-of-range geoTimeout values
const configWithClampedTimeout = {
geoTimeout: 50000 // Above max (20000)
};

return fp(configWithClampedTimeout).then((result) => {
expect(result).to.be.ok;
expect(result.warnings).to.be.an('array');

// Should include clamping warning
const clampingWarnings = result.warnings.filter(w =>
w.includes('geoTimeout') && w.includes('clamped')
);
expect(clampingWarnings).to.have.length(1);
expect(clampingWarnings[0]).to.include('20000ms'); // Max value
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

geoTimeout: 50000 clamped to 20 000 ms can stall the entire test suite for up to 20 seconds in CI

Line 117 passes geoTimeout: 50000, which the implementation clamps to 20000 ms. The geo-IP fetch then runs with the clamped 20-second abort timer. Since fp() resolves gracefully on geo timeout (as implied by the AI summary and the optional geoInfo pattern elsewhere), the promise will not resolve until the abort fires — up to 20 seconds later when geo services are unreachable in a typical CI environment.

The same issue applies at a smaller scale to the geoTimeout: 5000 value in the strictConfig accepts valid configurations test (line 73).

Recommended fix: use a short geoTimeout (e.g., 100 ms) in tests that only need to verify the clamping warning message, since the clamping itself is a synchronous/pre-fetch operation:

⚡ Proposed fix
       // Test with out-of-range geoTimeout values
       const configWithClampedTimeout = {
-        geoTimeout: 50000  // Above max (20000)
+        geoTimeout: 50000, // Above max (20000) – verify clamping warning only
+        // Pair with a short effective timeout so the actual fetch aborts quickly.
+        // The clamp warning is emitted before the fetch, so 1 ms is sufficient here.
       };

Or alternatively, mock the geo-IP fetch in a cy.intercept() so it returns immediately and the geoTimeout value has no real latency cost.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('geoTimeout clamping generates warnings', () => {
cy.loadFingerprintOSS().then((fp) => {
// Test with out-of-range geoTimeout values
const configWithClampedTimeout = {
geoTimeout: 50000 // Above max (20000)
};
return fp(configWithClampedTimeout).then((result) => {
expect(result).to.be.ok;
expect(result.warnings).to.be.an('array');
// Should include clamping warning
const clampingWarnings = result.warnings.filter(w =>
w.includes('geoTimeout') && w.includes('clamped')
);
expect(clampingWarnings).to.have.length(1);
expect(clampingWarnings[0]).to.include('20000ms'); // Max value
});
});
});
it('geoTimeout clamping generates warnings', () => {
cy.loadFingerprintOSS().then((fp) => {
// Test with out-of-range geoTimeout values
const configWithClampedTimeout = {
geoTimeout: 100 // Short timeout to verify clamping warning without stalling
};
return fp(configWithClampedTimeout).then((result) => {
expect(result).to.be.ok;
expect(result.warnings).to.be.an('array');
// Should include clamping warning
const clampingWarnings = result.warnings.filter(w =>
w.includes('geoTimeout') && w.includes('clamped')
);
expect(clampingWarnings).to.have.length(1);
expect(clampingWarnings[0]).to.include('20000ms'); // Max value
});
});
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/cypress/e2e/unit/config-v095.cy.js` around lines 113 - 132, The
geoTimeout test uses geoTimeout: 50000 which is clamped to 20000ms and can stall
CI; update the tests that only assert the clamping warning (the "geoTimeout
clamping generates warnings" test and the "strictConfig accepts valid
configurations" case using geoTimeout: 5000) to use a very small timeout (e.g.,
100) or mock the geo-IP network call so fp() resolves immediately; locate the
fp() invocation in the tests (cy.loadFingerprintOSS().then(fp =>
fp(configWithClampedTimeout) / strictConfig test) and either change the
geoTimeout values to a short number or add a cy.intercept() for the geo-IP
endpoint before calling fp() so the assertion on result.warnings still runs
without waiting for the clamped abort delay.

w.includes('environment')
);
expect(envWarnings).to.have.length.greaterThan(0);
expect(envWarnings.some(w => w.includes('TEST|DEV|STAGING|PROD'))).to.be.true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded pipe-separated enum string couples the test to the exact implementation format

w.includes('TEST|DEV|STAGING|PROD') encodes both the allowed values and their separator verbatim. Reordering the values, switching to a comma/space separator, or adding a new environment (e.g., PREPROD) in src/config.ts will silently break this assertion with no obvious connection to the change.

Prefer a looser check that asserts each allowed value is present individually, or assert on a stable part of the message (e.g., w.includes('environment') plus w.includes('TEST')).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/cypress/e2e/unit/config-v095.cy.js` at line 149, The test currently
hardcodes the pipe-separated enum string in the assertion
(expect(envWarnings.some(w => w.includes('TEST|DEV|STAGING|PROD')))), which
couples the test to formatting; update the assertion to instead verify the
presence of each allowed environment token individually (e.g., check that at
least one warning contains 'TEST', one contains 'DEV', etc.) or assert for a
stable substring like 'environment' plus individual value checks using the
existing envWarnings array so reordering or separator changes in src/config.ts
won't break the test.

Introduce a centralized site configuration in lib/site.ts to manage
base URLs, route definitions, and project metadata. This architecture
enables dynamic generation of sitemap.xml and robots.txt while
ensuring consistency across the application. The update includes a
new /llms.txt endpoint, implemented via a Next.js API route with
force-static for build-time optimization and edge caching. This
endpoint follows the AI discovery specification to provide LLMs with
structured context about the fingerprinting library and its
documentation. Rewrites in next.config.

Co-authored-by: gitbot <gitbot@gossorg.in>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
fingerprint-oss-demo/components/footer.tsx (1)

45-51: Add rel="noopener noreferrer" to external <Link target="_blank"> elements.

When using target="_blank", you should always include rel="noopener noreferrer" for security reasons. While in 2024 rel="noopener" is implicit for target="_blank" in modern browsers, noreferrer is not, and the <a> tag at line 28 already sets it correctly. The <Link> components wrapping external URLs (lines 19, 22, 25, 45, 49) should be consistent.

🔒 Proposed fix
-            <Link href="https://github.com/IntegerAlex" target="_blank" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
+            <Link href="https://github.com/IntegerAlex" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
               GitHub Profile
             </Link>
             <span className="mx-2 text-muted-foreground">|</span>
-            <Link href="https://gossorg.in" target="_blank" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
+            <Link href="https://gossorg.in" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
               Enterprise Solution
             </Link>

The same pattern applies to the other external <Link target="_blank"> elements at lines 19, 22, and 25.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fingerprint-oss-demo/components/footer.tsx` around lines 45 - 51, The
external Link components in footer.tsx (the Link elements wrapping "GitHub
Profile" and "Enterprise Solution" and the other external Links at the top of
the file) use target="_blank" but are missing rel="noopener noreferrer"; update
each external Link component (the Link JSX elements pointing to external URLs
such as "https://github.com/IntegerAlex" and "https://gossorg.in") to include
rel="noopener noreferrer" so all target="_blank" links follow the same secure
pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fingerprint-oss-demo/app/api/llms/route.ts`:
- Around line 42-48: Add an exception in next.config.mjs before the general
`/api/(.*)` no-store rule that specifically matches `/api/llms` and sets the
intended Cache-Control (e.g., "public, max-age=3600, s-maxage=3600"), then
remove the explicit Cache-Control header from the NextResponse created in
app/api/llms/route.ts (the new NextResponse(...) return) so the response only
carries the single, non-conflicting Cache-Control coming from the config.

In `@fingerprint-oss-demo/lib/site.ts`:
- Around line 3-4: The exported constant siteBaseUrl may include a trailing
slash from NEXT_PUBLIC_SITE_URL which causes double-slash URLs; update the
siteBaseUrl definition to normalize input by trimming any trailing slash when
creating the value (keep the fallback 'https://fingerprint-oss.gossorg.in'
as-is), e.g., compute siteBaseUrl from process.env.NEXT_PUBLIC_SITE_URL (if set)
with a trailing-slash removal (use string.replace(/\/+$/, '') or equivalent) so
all downstream interpolations like `${siteBaseUrl}/sitemap.xml` never produce
double slashes.

---

Nitpick comments:
In `@fingerprint-oss-demo/components/footer.tsx`:
- Around line 45-51: The external Link components in footer.tsx (the Link
elements wrapping "GitHub Profile" and "Enterprise Solution" and the other
external Links at the top of the file) use target="_blank" but are missing
rel="noopener noreferrer"; update each external Link component (the Link JSX
elements pointing to external URLs such as "https://github.com/IntegerAlex" and
"https://gossorg.in") to include rel="noopener noreferrer" so all
target="_blank" links follow the same secure pattern.

Comment on lines +42 to +48
return new NextResponse(body, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
},
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Conflicting Cache-Control headers for direct /api/llms access.

The global /api/(.*) headers rule in next.config.mjs (lines 68–75) sets Cache-Control: no-store, must-revalidate for all API routes. The route handler here also sets Cache-Control: public, max-age=3600, s-maxage=3600. When /api/llms is accessed directly (bypassing the /llms.txt rewrite), both headers land on the same response — which is RFC-noncompliant and produces unpredictable CDN/browser behavior. The /llms.txt/api/llms rewrite path is unaffected since /llms.txt never matches the /api/(.*) pattern.

To fix, add an exception rule before the general no-store API rule in next.config.mjs:

🛠️ Proposed fix in next.config.mjs
+    // LLMs.txt static caching
+    {
+      source: '/api/llms',
+      headers: [
+        {
+          key: 'Cache-Control',
+          value: 'public, max-age=3600, s-maxage=3600',
+        },
+      ],
+    },
     // API routes caching
     {
       source: '/api/(.*)',
       headers: [

Then remove the explicit Cache-Control from the handler to avoid the double-set entirely:

   return new NextResponse(body, {
     status: 200,
     headers: {
       'Content-Type': 'text/plain; charset=utf-8',
-      'Cache-Control': 'public, max-age=3600, s-maxage=3600',
     },
   })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return new NextResponse(body, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
},
})
return new NextResponse(body, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fingerprint-oss-demo/app/api/llms/route.ts` around lines 42 - 48, Add an
exception in next.config.mjs before the general `/api/(.*)` no-store rule that
specifically matches `/api/llms` and sets the intended Cache-Control (e.g.,
"public, max-age=3600, s-maxage=3600"), then remove the explicit Cache-Control
header from the NextResponse created in app/api/llms/route.ts (the new
NextResponse(...) return) so the response only carries the single,
non-conflicting Cache-Control coming from the config.

Comment on lines +3 to +4
/** Base URL for the site (no trailing slash). */
export const siteBaseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://fingerprint-oss.gossorg.in'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Strip trailing slash from siteBaseUrl at definition to prevent double-slash URLs.

The comment documents the "no trailing slash" contract, but nothing enforces it. If NEXT_PUBLIC_SITE_URL is configured with a trailing slash (e.g., https://example.com/), all downstream interpolations (${siteBaseUrl}/sitemap.xml, ${siteBaseUrl}/demo, etc.) silently produce double-slash URLs, breaking sitemap, robots, and llms.txt entries.

🛡️ Proposed fix
-export const siteBaseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://fingerprint-oss.gossorg.in'
+export const siteBaseUrl = (
+  process.env.NEXT_PUBLIC_SITE_URL ?? 'https://fingerprint-oss.gossorg.in'
+).replace(/\/$/, '')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Base URL for the site (no trailing slash). */
export const siteBaseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://fingerprint-oss.gossorg.in'
/** Base URL for the site (no trailing slash). */
export const siteBaseUrl = (
process.env.NEXT_PUBLIC_SITE_URL ?? 'https://fingerprint-oss.gossorg.in'
).replace(/\/$/, '')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fingerprint-oss-demo/lib/site.ts` around lines 3 - 4, The exported constant
siteBaseUrl may include a trailing slash from NEXT_PUBLIC_SITE_URL which causes
double-slash URLs; update the siteBaseUrl definition to normalize input by
trimming any trailing slash when creating the value (keep the fallback
'https://fingerprint-oss.gossorg.in' as-is), e.g., compute siteBaseUrl from
process.env.NEXT_PUBLIC_SITE_URL (if set) with a trailing-slash removal (use
string.replace(/\/+$/, '') or equivalent) so all downstream interpolations like
`${siteBaseUrl}/sitemap.xml` never produce double slashes.

Add a structured llms.txt endpoint at /api/llms to provide Large Language
Models with concise technical

Co-authored-by: gitbot <gitbot@gossorg.in>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
fingerprint-oss-demo/components/footer.tsx (1)

52-55: Consider opening llms.txt in a new tab.

/llms.txt is served as text/plain. Without target="_blank", clicking it navigates the user away from the current page entirely, which breaks the browsing context. Adding target="_blank" (with rel="noopener noreferrer") is consistent with the other external links in this footer.

♻️ Proposed fix
-            <Link href="/llms.txt" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
+            <Link href="/llms.txt" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
               llms.txt
             </Link>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fingerprint-oss-demo/components/footer.tsx` around lines 52 - 55, The Link
element rendering llms.txt currently navigates the user away; update the Link
for href="/llms.txt" in the Footer component to open in a new tab by adding
target="_blank" and rel="noopener noreferrer" (matching the other external
links) so the file opens in a separate browsing context without breaking the
current page.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@fingerprint-oss-demo/lib/site.ts`:
- Around line 3-4: The exported constant siteBaseUrl currently trusts
NEXT_PUBLIC_SITE_URL and can include a trailing slash contrary to its JSDoc;
update the initialization of siteBaseUrl (the exported symbol siteBaseUrl) to
normalize the value by removing any trailing slash(es) from
process.env.NEXT_PUBLIC_SITE_URL (e.g., trim with a regex like replace(/\/+$/,
'') or similar) and fall back to the existing default, so all downstream
interpolations like `${siteBaseUrl}/sitemap.xml` never produce double slashes.

---

Nitpick comments:
In `@fingerprint-oss-demo/components/footer.tsx`:
- Around line 52-55: The Link element rendering llms.txt currently navigates the
user away; update the Link for href="/llms.txt" in the Footer component to open
in a new tab by adding target="_blank" and rel="noopener noreferrer" (matching
the other external links) so the file opens in a separate browsing context
without breaking the current page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant