@thumbmarkjs/thumbmarkjs 1.9.0 → 1.10.0

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.
Files changed (68) hide show
  1. package/README.md +96 -102
  2. package/dist/thumbmark.cjs.js +1 -1
  3. package/dist/thumbmark.cjs.js.map +1 -1
  4. package/dist/thumbmark.esm.d.ts +6 -0
  5. package/dist/thumbmark.esm.js +1 -1
  6. package/dist/thumbmark.esm.js.map +1 -1
  7. package/dist/thumbmark.umd.js +1 -1
  8. package/dist/thumbmark.umd.js.map +1 -1
  9. package/dist/types/components/audio/index.d.ts +2 -0
  10. package/dist/types/components/canvas/index.d.ts +3 -0
  11. package/dist/types/components/fonts/index.d.ts +4 -0
  12. package/dist/types/components/hardware/index.d.ts +2 -0
  13. package/dist/types/components/locales/index.d.ts +2 -0
  14. package/dist/types/components/math/index.d.ts +2 -0
  15. package/dist/types/components/mathml/index.d.ts +2 -0
  16. package/dist/types/components/permissions/index.d.ts +3 -0
  17. package/dist/types/components/plugins/index.d.ts +2 -0
  18. package/dist/types/components/screen/index.d.ts +2 -0
  19. package/dist/types/components/speech/index.d.ts +2 -0
  20. package/dist/types/components/system/browser.d.ts +9 -0
  21. package/dist/types/components/system/index.d.ts +2 -0
  22. package/dist/types/components/webgl/index.d.ts +13 -0
  23. package/dist/types/components/webrtc/index.d.ts +3 -0
  24. package/dist/types/factory.d.ts +62 -0
  25. package/dist/types/functions/api.d.ts +73 -0
  26. package/dist/types/functions/filterComponents.d.ts +15 -0
  27. package/dist/types/functions/index.d.ts +63 -0
  28. package/dist/types/functions/legacy_functions.d.ts +27 -0
  29. package/dist/types/index.d.ts +9 -0
  30. package/dist/types/options.d.ts +82 -0
  31. package/dist/types/thumbmark.d.ts +28 -0
  32. package/dist/types/utils/cache.d.ts +23 -0
  33. package/dist/types/utils/commonPixels.d.ts +1 -0
  34. package/dist/types/utils/ephemeralIFrame.d.ts +4 -0
  35. package/dist/types/utils/getMostFrequent.d.ts +5 -0
  36. package/dist/types/utils/hash.d.ts +5 -0
  37. package/dist/types/utils/imageDataToDataURL.d.ts +1 -0
  38. package/dist/types/utils/log.d.ts +9 -0
  39. package/dist/types/utils/raceAll.d.ts +10 -0
  40. package/dist/types/utils/sort.d.ts +8 -0
  41. package/dist/types/utils/stableStringify.d.ts +22 -0
  42. package/dist/types/utils/version.d.ts +4 -0
  43. package/dist/types/utils/visitorId.d.ts +14 -0
  44. package/package.json +102 -70
  45. package/src/factory.ts +0 -4
  46. package/src/functions/api.ts +19 -6
  47. package/src/functions/filterComponents.ts +2 -3
  48. package/src/functions/index.ts +1 -1
  49. package/src/options.ts +7 -0
  50. package/src/components/canvas/index.test.ts +0 -38
  51. package/src/components/hardware/index.test.ts +0 -80
  52. package/src/components/intl/index.test.ts +0 -124
  53. package/src/components/intl/index.ts +0 -41
  54. package/src/components/mediaDevices/index.test.ts +0 -120
  55. package/src/components/mediaDevices/index.ts +0 -26
  56. package/src/components/system/browser.test.ts +0 -108
  57. package/src/components/webgl/index.test.ts +0 -223
  58. package/src/functions/api.test.ts +0 -366
  59. package/src/functions/filterComponents.test.ts +0 -238
  60. package/src/functions/functions.test.ts +0 -142
  61. package/src/functions/metadata.test.ts +0 -211
  62. package/src/options.test.ts +0 -10
  63. package/src/thumbmark.custom-components.test.ts +0 -87
  64. package/src/thumbmark.test.ts +0 -59
  65. package/src/utils/cache.test.ts +0 -95
  66. package/src/utils/raceAll.test.ts +0 -86
  67. package/src/utils/stableStringify.test.ts +0 -335
  68. package/src/utils/visitorId.test.ts +0 -102
@@ -0,0 +1,82 @@
1
+ type JSONValue = string | number | boolean | null | JSONValue[] | {
2
+ [key: string]: JSONValue;
3
+ };
4
+ export interface OptionsAfterDefaults {
5
+ /**
6
+ * A function to customise localStorage names used by thumbmark
7
+ * @param name Original name of the storage property eg. visitor_id
8
+ * @returns The name under which the storage property should be saved eg. myprefix_visitor_id
9
+ */
10
+ property_name_factory: (name: string) => string;
11
+ /**
12
+ * @deprecated use property_name_factory
13
+ */
14
+ storage_property_name?: string;
15
+ exclude?: string[];
16
+ include?: string[];
17
+ permissions_to_check?: PermissionName[];
18
+ timeout?: number;
19
+ logging?: boolean;
20
+ api_key?: string;
21
+ api_endpoint?: string;
22
+ /**
23
+ * When true, the API request is sent as a CORS "simple request" (Content-Type: text/plain,
24
+ * no custom headers). Use this when routing through a proxy that attaches the real API key.
25
+ * Requires neither api_key nor Authorization header from the client side.
26
+ */
27
+ simple_request?: boolean;
28
+ /**
29
+ * @deprecated This will be removed in Thumbmarkjs 2.0, use cache_lifetime_in_ms instead
30
+ */
31
+ cache_api_call?: boolean;
32
+ /**
33
+ * How long the cache will be valid for, maximum is 72h (259_200_000)
34
+ */
35
+ cache_lifetime_in_ms: number;
36
+ performance?: boolean;
37
+ stabilize?: string[];
38
+ experimental?: boolean;
39
+ /**
40
+ * Optional metadata to pass through to the API and webhooks.
41
+ * Can be a static JSON object or a function that returns a JSON object (evaluated at request time).
42
+ * This field is excluded from fingerprint calculation.
43
+ * Maximum length: 1000 characters when stringified.
44
+ * @example metadata: { userId: "123", eventType: "login" }
45
+ * @example metadata: () => ({ timestamp: Date.now(), sessionId: "abc" })
46
+ */
47
+ metadata?: JSONValue | (() => JSONValue);
48
+ }
49
+ export type optionsInterface = Partial<OptionsAfterDefaults>;
50
+ export declare const DEFAULT_CACHE_LIFETIME = 0;
51
+ export declare const MAXIMUM_CACHE_LIFETIME = 259200000;
52
+ export declare const DEFAULT_STORAGE_PREFIX = "thumbmark";
53
+ export declare const DEFAULT_API_ENDPOINT = "https://api.thumbmarkjs.com";
54
+ export declare const defaultOptions: OptionsAfterDefaults;
55
+ export declare let options: OptionsAfterDefaults;
56
+ /**
57
+ *
58
+ * @param key @deprecated this function will be removed
59
+ * @param value
60
+ */
61
+ export declare function setOption<K extends keyof optionsInterface>(key: K, value: OptionsAfterDefaults[K]): void;
62
+ export declare const stabilizationExclusionRules: {
63
+ private: {
64
+ exclude: string[];
65
+ browsers: string[];
66
+ }[];
67
+ iframe: ({
68
+ exclude: string[];
69
+ browsers: string[];
70
+ } | {
71
+ exclude: string[];
72
+ browsers?: undefined;
73
+ })[];
74
+ vpn: {
75
+ exclude: string[];
76
+ }[];
77
+ always: {
78
+ exclude: string[];
79
+ browsers: string[];
80
+ }[];
81
+ };
82
+ export {};
@@ -0,0 +1,28 @@
1
+ import { optionsInterface } from "./options";
2
+ import { ThumbmarkResponse } from './functions';
3
+ import { componentInterface } from "./factory";
4
+ /**
5
+ * A client for generating thumbmarks with a persistent configuration.
6
+ */
7
+ export declare class Thumbmark {
8
+ private options;
9
+ private customComponents;
10
+ /**
11
+ * Creates a new Thumbmarker client instance.
12
+ * @param options - Default configuration options for this instance.
13
+ */
14
+ constructor(options?: optionsInterface);
15
+ /**
16
+ * Generates a thumbmark using the instance's configuration.
17
+ * @param overrideOptions - Options to override for this specific call.
18
+ * @returns The thumbmark result containing the fingerprint hash, components, and metadata.
19
+ */
20
+ get(overrideOptions?: optionsInterface): Promise<ThumbmarkResponse>;
21
+ getVersion(): string;
22
+ /**
23
+ * Register a custom component to be included in the fingerprint.
24
+ * @param key - The component name
25
+ * @param fn - The component function
26
+ */
27
+ includeComponent(key: string, fn: (options?: optionsInterface) => Promise<componentInterface | null>): void;
28
+ }
@@ -0,0 +1,23 @@
1
+ import { apiResponse } from "../functions/api";
2
+ import { OptionsAfterDefaults } from "../options";
3
+ export declare const CACHE_KEY = "cache";
4
+ export interface Cache {
5
+ apiResponse?: apiResponse;
6
+ apiResponseExpiry?: number;
7
+ }
8
+ /**
9
+ * Get all values from cache
10
+ * @param _options
11
+ */
12
+ export declare function getCache(_options: Pick<OptionsAfterDefaults, 'property_name_factory'>): Cache;
13
+ /**
14
+ * Write given values to cache
15
+ * @param _options
16
+ * @param values
17
+ */
18
+ export declare function setCache(_options: OptionsAfterDefaults, values: Partial<Cache>): void;
19
+ /**
20
+ * Returns the expiry time for cache
21
+ * @param _options
22
+ */
23
+ export declare function getApiResponseExpiry(_options: Pick<OptionsAfterDefaults, 'cache_lifetime_in_ms'>): number;
@@ -0,0 +1 @@
1
+ export declare function getCommonPixels(images: ImageData[], width: number, height: number): ImageData;
@@ -0,0 +1,4 @@
1
+ export declare function ephemeralIFrame(callback: ({ iframe }: {
2
+ iframe: Document;
3
+ }) => void): Promise<any>;
4
+ export declare function wait<T = void>(durationMs: number, resolveWith?: T): Promise<T>;
@@ -0,0 +1,5 @@
1
+ export declare function mostFrequentValuesInArrayOfDictionaries(arr: {
2
+ [key: string]: any;
3
+ }[], keys: string[]): {
4
+ [key: string]: any;
5
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * This code is taken from https://github.com/LinusU/murmur-128/blob/master/index.js
3
+ * But instead of dependencies to encode-utf8 and fmix, I've implemented them here.
4
+ */
5
+ export declare function hash(key: ArrayBuffer | string, seed?: number): string;
@@ -0,0 +1 @@
1
+ export declare function imageDataToDataURL(imageData: ImageData): string;
@@ -0,0 +1,9 @@
1
+ import { componentInterface } from '../factory';
2
+ import { optionsInterface } from '../options';
3
+ import type { ThumbmarkError } from '../functions';
4
+ /**
5
+ * Logs thumbmark data to remote logging endpoint (only once per session)
6
+ * You can disable this by setting options.logging to false.
7
+ * @internal
8
+ */
9
+ export declare function logThumbmarkData(thisHash: string, thumbmarkData: componentInterface, options: optionsInterface, experimentalData?: componentInterface, errors?: ThumbmarkError[]): Promise<void>;
@@ -0,0 +1,10 @@
1
+ type DelayedPromise<T> = Promise<T>;
2
+ export declare function delay<T>(t: number, val: T): DelayedPromise<T>;
3
+ export interface RaceResult<T> {
4
+ value: T;
5
+ elapsed?: number;
6
+ error?: string;
7
+ }
8
+ export declare function raceAllPerformance<T>(promises: Promise<T>[], timeoutTime: number, timeoutVal: T): Promise<RaceResult<T>[]>;
9
+ export declare function raceAll<T>(promises: Promise<T>[], timeoutTime: number, timeoutVal: T): Promise<(T | undefined)[]>;
10
+ export {};
@@ -0,0 +1,8 @@
1
+ import { componentInterface } from '../factory';
2
+ /**
3
+ * Recursively sorts the keys of a component object alphabetically.
4
+ * This ensures a consistent order for hashing.
5
+ * @param obj The component object to sort.
6
+ * @returns A new object with sorted keys.
7
+ */
8
+ export declare function sortComponentKeys(obj: componentInterface): componentInterface;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Stable JSON stringify implementation
3
+ * Based on fast-json-stable-stringify by Evgeny Poberezkin
4
+ * https://github.com/epoberezkin/fast-json-stable-stringify
5
+ *
6
+ * This implementation ensures consistent JSON serialization by sorting object keys,
7
+ * which is critical for generating stable hashes from fingerprint data.
8
+ */
9
+ /**
10
+ * Converts data to a stable JSON string with sorted keys
11
+ *
12
+ * @param data - The data to stringify
13
+ * @returns Stable JSON string representation
14
+ * @throws TypeError if circular reference is detected
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const obj = { b: 2, a: 1 };
19
+ * stableStringify(obj); // '{"a":1,"b":2}'
20
+ * ```
21
+ */
22
+ export declare function stableStringify(data: any): string;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Returns the current package version
3
+ */
4
+ export declare function getVersion(): string;
@@ -0,0 +1,14 @@
1
+ import { OptionsAfterDefaults } from "../options";
2
+ /**
3
+ * Get the storage property name for visitor id
4
+ * @param _options
5
+ */
6
+ export declare function getVisitorIdPropertyName(_options: Pick<OptionsAfterDefaults, 'storage_property_name' | 'property_name_factory'>): string;
7
+ /**
8
+ * Gets visitor ID from localStorage, returns null if unavailable
9
+ */
10
+ export declare function getVisitorId(_options: OptionsAfterDefaults): string | null;
11
+ /**
12
+ * Sets visitor ID in localStorage
13
+ */
14
+ export declare function setVisitorId(visitorId: string, _options: OptionsAfterDefaults): void;
package/package.json CHANGED
@@ -1,70 +1,102 @@
1
- {
2
- "name": "@thumbmarkjs/thumbmarkjs",
3
- "version": "1.9.0",
4
- "description": "",
5
- "type": "module",
6
- "main": "./dist/thumbmark.cjs.js",
7
- "module": "./dist/thumbmark.esm.js",
8
- "types": "./dist/thumbmark.esm.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/thumbmark.esm.d.ts",
12
- "import": "./dist/thumbmark.esm.js",
13
- "require": "./dist/thumbmark.cjs.js"
14
- }
15
- },
16
- "public": true,
17
- "files": [
18
- "dist",
19
- "src"
20
- ],
21
- "scripts": {
22
- "test": "jest",
23
- "test:e2e": "npx playwright test",
24
- "build": "rm -rf dist/* && rm -rf types/* && rollup -c",
25
- "watch": "rollup -c -w"
26
- },
27
- "keywords": [
28
- "thumbmark",
29
- "browser-thumbmarking",
30
- "browser-thumbmark",
31
- "thumbmarking",
32
- "fingerprint",
33
- "fingerprinting",
34
- "browser-fingerprint",
35
- "browser-fingerprinting",
36
- "audio-fingerprinting",
37
- "canvas-fingerprinting",
38
- "visitor-identifier",
39
- "fraud-detection"
40
- ],
41
- "author": "Ilkka Peltola",
42
- "license": "MIT",
43
- "unpkg": "dist/thumbmark.umd.js",
44
- "devDependencies": {
45
- "@playwright/test": "^1.57.0",
46
- "@rollup/plugin-commonjs": "^25.0.7",
47
- "@rollup/plugin-json": "^6.1.0",
48
- "@rollup/plugin-node-resolve": "^15.2.3",
49
- "@rollup/plugin-terser": "^0.4.4",
50
- "@rollup/plugin-typescript": "^11.1.6",
51
- "@testing-library/jest-dom": "^6.1.6",
52
- "@types/jest": "^29.5.11",
53
- "@types/jsdom": "^21.1.6",
54
- "@types/node": "^20.10.6",
55
- "@types/ua-parser-js": "^0.7.39",
56
- "@types/webpack-env": "^1.18.4",
57
- "@webpack-cli/generators": "^3.0.7",
58
- "jest": "^29.7.0",
59
- "jest-environment-jsdom": "^29.7.0",
60
- "jsdom": "^23.0.1",
61
- "path": "^0.12.7",
62
- "rollup": ">=4.22.4",
63
- "rollup-plugin-dts": "^6.1.0",
64
- "serve": "^14.2.5",
65
- "ts-jest": "^29.1.1",
66
- "ts-loader": "^9.5.1",
67
- "ts-md5": "^1.3.1",
68
- "typescript": "^5.3.3"
69
- }
70
- }
1
+ {
2
+ "name": "@thumbmarkjs/thumbmarkjs",
3
+ "version": "1.10.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "./dist/thumbmark.cjs.js",
7
+ "module": "./dist/thumbmark.esm.js",
8
+ "types": "./dist/thumbmark.esm.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/thumbmark.esm.d.ts",
12
+ "import": "./dist/thumbmark.esm.js",
13
+ "require": "./dist/thumbmark.cjs.js"
14
+ }
15
+ },
16
+ "public": true,
17
+ "files": [
18
+ "dist",
19
+ "src/components/audio/index.ts",
20
+ "src/components/canvas/index.ts",
21
+ "src/components/fonts/index.ts",
22
+ "src/components/hardware/index.ts",
23
+ "src/components/locales/index.ts",
24
+ "src/components/math/index.ts",
25
+ "src/components/mathml/index.ts",
26
+ "src/components/permissions/index.ts",
27
+ "src/components/plugins/index.ts",
28
+ "src/components/screen/index.ts",
29
+ "src/components/speech/index.ts",
30
+ "src/components/system/browser.ts",
31
+ "src/components/system/index.ts",
32
+ "src/components/webgl/index.ts",
33
+ "src/components/webrtc/index.ts",
34
+ "src/factory.ts",
35
+ "src/functions/api.ts",
36
+ "src/functions/filterComponents.ts",
37
+ "src/functions/index.ts",
38
+ "src/functions/legacy_functions.ts",
39
+ "src/index.ts",
40
+ "src/options.ts",
41
+ "src/thumbmark.ts",
42
+ "src/types/global.d.ts",
43
+ "src/utils/cache.ts",
44
+ "src/utils/commonPixels.ts",
45
+ "src/utils/ephemeralIFrame.ts",
46
+ "src/utils/getMostFrequent.ts",
47
+ "src/utils/hash.ts",
48
+ "src/utils/imageDataToDataURL.ts",
49
+ "src/utils/log.ts",
50
+ "src/utils/raceAll.ts",
51
+ "src/utils/sort.ts",
52
+ "src/utils/stableStringify.ts",
53
+ "src/utils/version.ts",
54
+ "src/utils/visitorId.ts"
55
+ ],
56
+ "scripts": {
57
+ "test": "jest",
58
+ "test:e2e": "npx playwright test",
59
+ "build": "rm -rf dist/* && rm -rf types/* && rollup -c",
60
+ "watch": "rollup -c -w",
61
+ "perf": "playwright test perf/perf.spec.ts"
62
+ },
63
+ "keywords": [
64
+ "thumbmark",
65
+ "browser-thumbmarking",
66
+ "browser-thumbmark",
67
+ "thumbmarking",
68
+ "fingerprint",
69
+ "fingerprinting",
70
+ "browser-fingerprint",
71
+ "browser-fingerprinting",
72
+ "audio-fingerprinting",
73
+ "canvas-fingerprinting",
74
+ "visitor-identifier",
75
+ "fraud-detection"
76
+ ],
77
+ "author": "Ilkka Peltola",
78
+ "license": "MIT",
79
+ "unpkg": "dist/thumbmark.umd.js",
80
+ "devDependencies": {
81
+ "@playwright/test": "^1.57.0",
82
+ "@rollup/plugin-commonjs": "^25.0.7",
83
+ "@rollup/plugin-json": "^6.1.0",
84
+ "@rollup/plugin-node-resolve": "^15.2.3",
85
+ "@rollup/plugin-terser": "^1.0.0",
86
+ "@rollup/plugin-typescript": "^11.1.6",
87
+ "@testing-library/jest-dom": "^6.1.6",
88
+ "@types/jest": "^29.5.11",
89
+ "@types/jsdom": "^21.1.6",
90
+ "@types/node": "^20.10.6",
91
+ "@types/ua-parser-js": "^0.7.39",
92
+ "jest": "^29.7.0",
93
+ "jest-environment-jsdom": "^29.7.0",
94
+ "jsdom": "^23.0.1",
95
+ "rollup": ">=4.22.4",
96
+ "rollup-plugin-dts": "^6.1.0",
97
+ "serve": "^14.2.5",
98
+ "ts-jest": "^29.1.1",
99
+ "ts-md5": "^1.3.1",
100
+ "typescript": "^5.3.3"
101
+ }
102
+ }
package/src/factory.ts CHANGED
@@ -21,8 +21,6 @@ import getSystem from "./components/system";
21
21
  import getWebGL from "./components/webgl";
22
22
 
23
23
  // Import experimental component functions
24
- import getIntl from "./components/intl";
25
- import getMediaDevices from "./components/mediaDevices";
26
24
  import getWebRTC from "./components/webrtc";
27
25
  import getMathML from "./components/mathml";
28
26
  import getSpeech from "./components/speech";
@@ -50,9 +48,7 @@ export const tm_component_promises = {
50
48
  * @description key->function map of experimental components. Only resolved during logging.
51
49
  */
52
50
  export const tm_experimental_component_promises = {
53
- 'intl': getIntl,
54
51
  'mathml': getMathML,
55
- 'mediadevices': getMediaDevices,
56
52
  };
57
53
 
58
54
  // the component interface is the form of the JSON object the function's promise must return
@@ -82,13 +82,16 @@ const RETRY_BACKOFF_MS = 200;
82
82
  async function callApi(
83
83
  endpoint: string, body: any, options: OptionsAfterDefaults, visitorId: string | null,
84
84
  ): Promise<apiResponse> {
85
- const response = await fetch(endpoint, {
86
- method: 'POST',
87
- headers: {
85
+ const headers: Record<string, string> = options.simple_request
86
+ ? { 'Content-Type': 'text/plain' }
87
+ : {
88
88
  'x-api-key': options.api_key!,
89
89
  'Authorization': 'custom-authorized',
90
90
  'Content-Type': 'application/json',
91
- },
91
+ };
92
+ const response = await fetch(endpoint, {
93
+ method: 'POST',
94
+ headers,
92
95
  body: JSON.stringify(body),
93
96
  });
94
97
 
@@ -149,11 +152,21 @@ export const getApiPromise = (
149
152
 
150
153
  // 3. Otherwise, initiate a new API call with timeout.
151
154
  const apiEndpoint = options.api_endpoint || DEFAULT_API_ENDPOINT;
152
- const endpoint = `${apiEndpoint}/thumbmark`;
155
+ let endpoint: string;
156
+ try {
157
+ const u = new URL(apiEndpoint);
158
+ endpoint = (u.pathname === '/' && !apiEndpoint.endsWith('/'))
159
+ ? `${u.origin}/thumbmark`
160
+ : apiEndpoint;
161
+ } catch {
162
+ endpoint = `${apiEndpoint}/thumbmark`;
163
+ }
153
164
  const visitorId = getVisitorId(options);
165
+ // Omit api_key from the body — in normal mode it goes in the x-api-key header; in simple_request mode it is proxy-injected.
166
+ const { api_key: _omitApiKey, ...optionsForBody } = options;
154
167
  const requestBody: any = {
155
168
  components,
156
- options,
169
+ options: optionsForBody,
157
170
  clientHash: hash(stableStringify(components)),
158
171
  version: getVersion()
159
172
  };
@@ -27,7 +27,7 @@ export function getExcludeList(options?: optionsInterface, obj?: componentInterf
27
27
 
28
28
  const name = browser.name.toLowerCase();
29
29
  const majorVer = parseInt(browser.version.split('.')[0] || '0', 10);
30
- const excludeList = [...(options?.exclude || [])];
30
+ const excludeList = Array.isArray(options?.exclude) ? [...options.exclude] : [];
31
31
  const stabilizationOptions = [...new Set([...(options?.stabilize || []), 'always'])];
32
32
 
33
33
  for (const option of stabilizationOptions) {
@@ -52,7 +52,7 @@ export function filterThumbmarkData(
52
52
  options?: optionsInterface,
53
53
  ): componentInterface {
54
54
  const excludeList = getExcludeList(options, obj);
55
- const includeList = options?.include || [];
55
+ const includeList = Array.isArray(options?.include) ? options.include : [];
56
56
 
57
57
  /**
58
58
  * Inner recursive function to perform the actual filtering.
@@ -84,6 +84,5 @@ export function filterThumbmarkData(
84
84
  return result;
85
85
  }
86
86
 
87
- // Start the filtering process
88
87
  return performFilter(obj);
89
88
  }
@@ -106,7 +106,7 @@ export async function getThumbmark(
106
106
  allErrors.push(...expErrors);
107
107
  }
108
108
 
109
- const apiPromise = _options.api_key ? getApiPromise(_options, clientComponentsResult) : null;
109
+ const apiPromise = (_options.api_key || _options.simple_request) ? getApiPromise(_options, clientComponentsResult) : null;
110
110
  let apiResult = null;
111
111
 
112
112
  if (apiPromise) {
package/src/options.ts CHANGED
@@ -18,6 +18,12 @@ export interface OptionsAfterDefaults {
18
18
  logging?: boolean,
19
19
  api_key?: string,
20
20
  api_endpoint?: string,
21
+ /**
22
+ * When true, the API request is sent as a CORS "simple request" (Content-Type: text/plain,
23
+ * no custom headers). Use this when routing through a proxy that attaches the real API key.
24
+ * Requires neither api_key nor Authorization header from the client side.
25
+ */
26
+ simple_request?: boolean,
21
27
  /**
22
28
  * @deprecated This will be removed in Thumbmarkjs 2.0, use cache_lifetime_in_ms instead
23
29
  */
@@ -58,6 +64,7 @@ export const defaultOptions: OptionsAfterDefaults = {
58
64
  cache_lifetime_in_ms: DEFAULT_CACHE_LIFETIME,
59
65
  performance: false,
60
66
  experimental: false,
67
+ simple_request: false,
61
68
  property_name_factory: (name: string) => {
62
69
  return `${DEFAULT_STORAGE_PREFIX}_${name}`;
63
70
  },
@@ -1,38 +0,0 @@
1
- import { getCommonPixels } from '../../utils/commonPixels';
2
-
3
- class MockImageData {
4
- data: Uint8ClampedArray;
5
- width: number;
6
- height: number;
7
-
8
- constructor(pixelData: number[], width: number, height: number) {
9
- this.width = width;
10
- this.height = height;
11
- this.data = new Uint8ClampedArray(pixelData);; // Mimic the data structure of ImageData
12
- }
13
- }
14
-
15
- // Mock the global ImageData object in jsdom environment
16
- (global as any).ImageData = MockImageData;
17
-
18
- const createImageFromArray = (array: number[]) => {
19
- const width = 1;
20
- const height = 1;
21
- const pixelData = array;
22
- const pixelArray = new Uint8ClampedArray(pixelData);
23
- return new ImageData(pixelArray, width, height);
24
- }
25
-
26
- const pixel1 = createImageFromArray([128, 255, 255, 255]);
27
- const pixel2 = createImageFromArray([255, 200, 255, 230]);
28
- const pixel3 = createImageFromArray([255, 255, 250, 255]);
29
- const pixel4 = createImageFromArray([255, 255, 255, 255]);
30
-
31
- describe('canvas tests', () => {
32
- test("commonPixels is an ImageData", () => {
33
- expect(getCommonPixels([pixel1, pixel2, pixel3], 1, 1)).toBeInstanceOf(ImageData);
34
- });
35
- test("commonPixels returns the most common bytes", () => {
36
- expect(getCommonPixels([pixel1, pixel2, pixel3], 1, 1).data).toStrictEqual(pixel4.data);
37
- });
38
- });
@@ -1,80 +0,0 @@
1
- import getHardware from './index';
2
-
3
- describe('hardware component tests', () => {
4
- let originalGetContext: any;
5
-
6
- beforeAll(() => {
7
- originalGetContext = HTMLCanvasElement.prototype.getContext;
8
- });
9
-
10
- afterAll(() => {
11
- HTMLCanvasElement.prototype.getContext = originalGetContext;
12
- });
13
-
14
- test("getHardware returns valid structure even when WebGL is blocked", async () => {
15
- // Mock getContext to return null (simulating WebGL being disabled/blocked)
16
- HTMLCanvasElement.prototype.getContext = jest.fn().mockReturnValue(null);
17
-
18
- const result = await getHardware();
19
-
20
- expect(result).toBeDefined();
21
- if (result) {
22
- expect(result.videocard).toBe("undefined");
23
- expect(result.deviceMemory).toBeDefined();
24
- expect(result.architecture).toBeDefined();
25
- }
26
- });
27
-
28
- test("getHardware handles WebGL getParameter throwing gracefully", async () => {
29
- // Mock a broken WebGL context
30
- const mockGl = {
31
- getParameter: jest.fn().mockImplementation(() => {
32
- throw new Error("WebGL error");
33
- }),
34
- getExtension: jest.fn().mockReturnValue(null),
35
- VENDOR: 1,
36
- RENDERER: 2,
37
- VERSION: 3,
38
- SHADING_LANGUAGE_VERSION: 4
39
- };
40
-
41
- HTMLCanvasElement.prototype.getContext = jest.fn().mockReturnValue(mockGl);
42
-
43
- const result = await getHardware();
44
-
45
- expect(result).toBeDefined();
46
- if (result) {
47
- expect(result.videocard).toBe("undefined");
48
- }
49
- });
50
-
51
- test("getHardware handles WebGL getExtension returning null", async () => {
52
- // Mock a context where getParameter works but getExtension (debug info) returns null
53
- const mockGl = {
54
- getParameter: jest.fn().mockImplementation((param) => {
55
- if (param === 1) return "Mock Vendor"; // VENDOR
56
- if (param === 2) return ""; // RENDERER (empty to trigger getExtension check)
57
- return "Mock Value";
58
- }),
59
- getExtension: jest.fn().mockReturnValue(null),
60
- VENDOR: 1,
61
- RENDERER: 2,
62
- VERSION: 3,
63
- SHADING_LANGUAGE_VERSION: 4
64
- };
65
-
66
- HTMLCanvasElement.prototype.getContext = jest.fn().mockReturnValue(mockGl);
67
-
68
- const result = await getHardware();
69
-
70
- expect(result).toBeDefined();
71
- if (result) {
72
- expect(result.videocard).toBeDefined();
73
- const videocard = result.videocard as any;
74
- expect(videocard.vendor).toBe("Mock Vendor");
75
- expect(videocard.renderer).toBe("");
76
- // Should not have unmasked fields
77
- expect(videocard.vendorUnmasked).toBeUndefined();
78
- }
79
- });
80
- });