Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/*.map
LICENSE.md.save

20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Config Validation**: Added lightweight `validateUserInfoConfig()` with normalization and warnings
- Supports validation for `environment`, `logLevel`, `geoTimeout`, and telemetry sample rate
- Supports `strictConfig` mode for rejecting unknown keys
- **Structured Errors**: Added `FingerprintError` with typed codes
- Codes include: `CONFIG_INVALID`, `GEO_TIMEOUT`, `GEO_FAILED`, `SYSTEM_INFO_FAILED`, `HASH_FAILED`, `UNKNOWN`
- **Geolocation Timeout**: Added `geoTimeout` support for geolocation fetch
- Uses low-overhead abort-based timeout handling
- Falls back gracefully to mock data and emits warning metadata
- **Minimal Preset**: Added `preset: 'minimal'` for reduced client compute
- Skips heavy collectors (Canvas fingerprint, Audio fingerprint, WebGL image hash, full font detection)
- Uses shorter timeout behavior and surfaces runtime warnings
- **Warnings Surface**: Added optional top-level `warnings?: string[]` in `userInfo()` output

### Improved
- **Telemetry Error Context**: Error metrics/spans now include structured error codes where available
- **Performance Focus**: New validation and timeout logic designed with O(field-count) checks and single-timer geo timeout

## [0.9.4] - 2025-02-08

### Added
Expand Down
62 changes: 62 additions & 0 deletions fingerprint-oss-demo/app/api/llms/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { NextResponse } from 'next/server'
import { siteBaseUrl, siteInfo, siteWhatWeDo, siteHowToUse } from '@/lib/site'

/** Prerender at build time so /llms.txt is static like sitemap and robots. */
export const dynamic = 'force-static'

/**
* Serves llms.txt at /llms.txt (via rewrite in next.config).
* Follows the llms.txt specification for AI/LLM discovery.
* Content-Type: text/plain; charset=utf-8
*/
export async function GET() {
const body = [
`# ${siteInfo.name}`,
'',
`> ${siteInfo.description}`,
'',
'## What We Do',
'',
...siteWhatWeDo.map((line) => `- ${line}`),
'',
'## How To Use',
'',
'Install the library:',
`\`\`\`\n${siteHowToUse.install}\n\`\`\``,
'',
'Basic usage in JavaScript/TypeScript:',
`\`\`\`\n${siteHowToUse.usage}\n\`\`\``,
'',
siteHowToUse.docs,
'',
'## Services',
'',
`- [Live Demo](${siteBaseUrl}/demo): Interactive browser fingerprinting demo`,
`- [Roadmap](${siteBaseUrl}/roadmap): Product roadmap and planned features`,
`- [Home](${siteBaseUrl}): Overview and installation guide`,
'',
'## Key Information',
'',
`- [NPM Package](${siteInfo.npm}): Install fingerprint-oss from npm`,
`- [Source Code](${siteInfo.repository}): GitHub repository`,
`- [Organization](${siteInfo.organizationUrl}): ${siteInfo.organization}`,
'',
'## AI Discovery Files',
'',
`- [Sitemap](${siteBaseUrl}/sitemap.xml): Site structure`,
`- [Robots](${siteBaseUrl}/robots.txt): Crawler directives`,
'',
'## Contact',
'',
`- Organization: ${siteInfo.organization}`,
`- Website: ${siteInfo.organizationUrl}`,
`- Repository: ${siteInfo.repository}`,
].join('\n')

return new NextResponse(body, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
})
Comment on lines +56 to +61
Copy link
Copy Markdown
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.

}
18 changes: 7 additions & 11 deletions fingerprint-oss-demo/app/robots.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { MetadataRoute } from 'next'
import type { MetadataRoute } from 'next'
import { siteBaseUrl } from '@/lib/site'

/**
* Provides robots metadata for Next.js to generate a robots.txt-like response.
*
* Returns crawler directives allowing all user agents to access the site root while
* disallowing API, Next.js internals, a private folder, and JSON files. Also exposes
* the site's sitemap and host based on the configured base URL.
* Provides robots metadata for Next.js to generate robots.txt.
* Sitemap URL is generated automatically from the site base URL.
*
* @returns The MetadataRoute.Robots object describing rules, sitemap, and host.
*/
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://fingerprint-oss.gossorg.in'

return {
rules: {
userAgent: '*',
Expand All @@ -23,7 +19,7 @@ export default function robots(): MetadataRoute.Robots {
'*.json$',
],
},
sitemap: `${baseUrl}/sitemap.xml`,
host: baseUrl,
sitemap: `${siteBaseUrl}/sitemap.xml`,
host: siteBaseUrl,
}
}
}
40 changes: 12 additions & 28 deletions fingerprint-oss-demo/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
import { MetadataRoute } from 'next'
import type { MetadataRoute } from 'next'
import { siteBaseUrl, siteRoutes } from '@/lib/site'

/**
* Build the sitemap for the site.
*
* Returns a sitemap array containing three entries: the site root and two fragment anchors (`#demo`, `#installation`).
* Each entry includes `url`, `lastModified` (set to the time the function runs), `changeFrequency`, and `priority`.
* Build the sitemap for the site from the shared route config.
* Add or remove routes in `lib/site.ts` to update the sitemap automatically.
*
* @returns The generated MetadataRoute.Sitemap for the site.
*/
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://fingerprint-oss.gossorg.in'

return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
},
{
url: `${baseUrl}/#demo`,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.8,
},
{
url: `${baseUrl}/#installation`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.6,
}
]
}
const now = new Date()
return siteRoutes.map((route) => ({
url: `${siteBaseUrl}${route.path === '/' ? '' : route.path}`,
lastModified: now,
changeFrequency: route.changeFrequency ?? 'weekly',
priority: route.priority ?? 0.7,
}))
}
6 changes: 5 additions & 1 deletion fingerprint-oss-demo/components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ export default function Footer() {
<div className="text-center md:text-right">
<p className="text-muted-foreground text-sm">&copy; {new Date().getFullYear()} Global Open Source Softwares. All rights reserved.</p>
<div className="mt-2">
<Link href="https://github.com/globalopensourcesoftwares" target="_blank" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
<Link href="https://github.com/IntegerAlex" target="_blank" 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">
Enterprise Solution
</Link>
<span className="mx-2 text-muted-foreground">|</span>
<Link href="/llms.txt" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
llms.txt
</Link>
</div>
<p className="text-muted-foreground text-sm mt-2">A product of <strong>Global Open Source Softwares (GOSS)</strong></p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion fingerprint-oss-demo/components/roadmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const RELEASE_HISTORY = [
];

const FUTURE_ROADMAP = [
{ version: "0.9.5", theme: "Robustness & DX", focus: ["Config validation", "Structured errors", "Geo timeout", "Minimal preset"] },
{ version: "0.9.5", theme: "Robustness & DX", focus: ["Config validation", "Structured errors", "Geo timeout", "Low-compute minimal preset"] },
{ version: "0.9.6", theme: "Config & Extensibility", focus: ["Custom geo endpoint", "Feature flags", "Plugin hooks", "Lazy loading"] },
{ version: "0.9.7", theme: "Advanced & Hardening", focus: ["Connection type", "CSP-friendly", "SRI hashes", "Pre-1.0 audit"] },
];
Expand Down
49 changes: 49 additions & 0 deletions fingerprint-oss-demo/lib/site.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { MetadataRoute } from 'next'

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


/**
* Public routes included in the sitemap.
* Add new pages here to have them appear in the sitemap automatically.
*/
export const siteRoutes: Array<{
path: string
changeFrequency?: MetadataRoute.Sitemap[number]['changeFrequency']
priority?: number
}> = [
{ path: '/', changeFrequency: 'weekly', priority: 1 },
{ path: '/demo', changeFrequency: 'weekly', priority: 0.9 },
{ path: '/roadmap', changeFrequency: 'monthly', priority: 0.8 },
]

/** Site name and short description for llms.txt and metadata. */
export const siteInfo = {
name: 'Fingerprint OSS Demo',
description:
'Free, open-source browser fingerprinting library for identifying unique visitors. Device detection, geolocation, VPN detection, and visitor analytics.',
repository: 'https://github.com/IntegerAlex/fingerprint-oss',
npm: 'https://www.npmjs.com/package/fingerprint-oss',
organization: 'Global Open Source Softwares (GOSS)',
organizationUrl: 'https://gossorg.in',
}

/** What fingerprint-oss does: features and use cases for llms.txt. */
export const siteWhatWeDo = [
'Browser fingerprinting: generate a stable visitor ID from device and browser signals.',
'Device detection: device type, OS, browser, screen resolution, and hardware hints.',
'Geolocation: approximate location (country/region) from IP when permitted.',
'VPN and proxy detection: flag likely VPN or proxy usage for fraud or analytics.',
'Canvas and WebGL fingerprinting: additional signals for uniqueness and bot detection.',
'Privacy-focused: runs client-side; no PII required; configurable transparency and data retention.',
'Use cases: fraud prevention, analytics, A/B testing, rate limiting, and anonymous visitor identification.',
]

/** How to use fingerprint-oss: install and basic API for llms.txt. */
export const siteHowToUse = {
install: 'npm install fingerprint-oss@latest',
usage: `import userInfo from 'fingerprint-oss';
const data = await userInfo({ transparency: true });
// data: visitorId, device, location, vpn, canvas, webgl, etc.`,
docs: 'Full API and options: see the Live Demo and Installation Guide on the site.',
}
17 changes: 16 additions & 1 deletion fingerprint-oss-demo/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
const nextConfig = {
// Build optimization for OpenNext
output: 'standalone',

// Serve llms.txt at root via API route
async rewrites() {
return [{ source: '/llms.txt', destination: '/api/llms' }]
},

// Performance optimizations for Cloudflare Workers
compress: true,
Expand Down Expand Up @@ -58,7 +63,17 @@ const nextConfig = {
},
],
},
// API routes caching
// /api/llms: cacheable (static llms.txt content)
{
source: '/api/llms',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=3600, s-maxage=3600',
},
],
},
// API routes: no-store by default
{
source: '/api/(.*)',
headers: [
Expand Down
6 changes: 3 additions & 3 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ export default [
{
input: 'src/index.ts',
output: [
{ file: 'dist/index.cjs.js', format: 'cjs', sourcemap: true, plugins: [terser()], inlineDynamicImports: true },
{ file: 'dist/index.esm.js', format: 'esm', sourcemap: true, plugins: [terser()], inlineDynamicImports: true },
{ file: 'dist/index.cjs.js', format: 'cjs', sourcemap: false, plugins: [terser()], inlineDynamicImports: true },
{ file: 'dist/index.esm.js', format: 'esm', sourcemap: false, plugins: [terser()], inlineDynamicImports: true },
{
file: 'dist/index.min.js',
format: 'umd',
name: 'fingerprintOss', // UMD name, corrected to camelCase
plugins: [terser()],
sourcemap: true,
sourcemap: false,
inlineDynamicImports: true,
globals: { // Required if any external deps are not bundled for UMD
// Example: 'hash-wasm': 'hashWasm'
Expand Down
Loading
Loading