@thumbmarkjs/thumbmarkjs 1.6.3 → 1.6.4

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.
@@ -6,7 +6,7 @@
6
6
  *
7
7
  */
8
8
 
9
- import {defaultOptions, OptionsAfterDefaults, optionsInterface} from "../options";
9
+ import { defaultOptions, OptionsAfterDefaults, optionsInterface } from "../options";
10
10
  import {
11
11
  timeoutInstance,
12
12
  componentInterface,
@@ -27,24 +27,45 @@ import { stableStringify } from "../utils/stableStringify";
27
27
  /**
28
28
  * Final thumbmark response structure
29
29
  */
30
- interface thumbmarkResponse {
31
- components: componentInterface,
32
- info: { [key: string]: any },
33
- version: string,
34
- thumbmark: string,
35
- visitorId?: string,
36
- elapsed?: any;
30
+ export interface ThumbmarkResponse {
31
+ /** Hash of all components - the main fingerprint identifier */
32
+ thumbmark: string;
33
+ /** All resolved fingerprint components */
34
+ components: componentInterface;
35
+ /** Information from the API (IP, classification, uniqueness score) */
36
+ info: infoInterface;
37
+ /** Library version */
38
+ version: string;
39
+ /** Persistent visitor identifier (requires API key) */
40
+ visitorId?: string;
41
+ /** Performance timing for each component (only when options.performance is true) */
42
+ elapsed?: Record<string, number>;
43
+ /** Error message if something went wrong */
37
44
  error?: string;
45
+ /** Experimental components (only when options.experimental is true) */
38
46
  experimental?: componentInterface;
47
+ /** Unique identifier for this API request */
48
+ requestId?: string;
39
49
  }
40
50
 
41
51
  /**
42
52
  * Main entry point: collects all components, optionally calls API, and returns thumbmark data.
43
53
  *
44
54
  * @param options - Options for fingerprinting and API
45
- * @returns thumbmarkResponse (elapsed is present only if options.performance is true)
55
+ * @returns ThumbmarkResponse (elapsed is present only if options.performance is true)
46
56
  */
47
- export async function getThumbmark(options?: optionsInterface): Promise<thumbmarkResponse> {
57
+ export async function getThumbmark(options?: optionsInterface): Promise<ThumbmarkResponse> {
58
+ // Early exit for non-browser environments (Node.js, Jest, SSR)
59
+ if (typeof document === 'undefined' || typeof window === 'undefined') {
60
+ return {
61
+ thumbmark: '',
62
+ components: {},
63
+ info: {},
64
+ version: getVersion(),
65
+ error: 'Browser environment required'
66
+ };
67
+ }
68
+
48
69
  const _options = { ...defaultOptions, ...options } as OptionsAfterDefaults;
49
70
 
50
71
  // Early logging decision
@@ -100,7 +121,7 @@ export async function getThumbmark(options?: optionsInterface): Promise<thumbmar
100
121
  logThumbmarkData(thumbmark, components, _options, experimentalComponents).catch(() => { /* do nothing */ });
101
122
  }
102
123
 
103
- const result: thumbmarkResponse = {
124
+ const result: ThumbmarkResponse = {
104
125
  ...(apiResult?.visitorId && { visitorId: apiResult.visitorId }),
105
126
  thumbmark,
106
127
  components: components,
@@ -108,6 +129,7 @@ export async function getThumbmark(options?: optionsInterface): Promise<thumbmar
108
129
  version,
109
130
  ...maybeElapsed,
110
131
  ...(Object.keys(experimentalComponents).length > 0 && _options.experimental && { experimental: experimentalComponents }),
132
+ ...(apiResult?.requestId && { requestId: apiResult.requestId }),
111
133
  };
112
134
 
113
135
  return result;
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  getFingerprintData,
4
4
  getFingerprintPerformance
5
5
  } from './functions/legacy_functions'
6
- import { getThumbmark } from './functions'
6
+ import { getThumbmark, ThumbmarkResponse } from './functions'
7
7
  import { getVersion } from './utils/version';
8
8
  import { setOption, optionsInterface, stabilizationExclusionRules } from './options'
9
9
  import { includeComponent } from './factory'
@@ -12,7 +12,7 @@ import { filterThumbmarkData } from './functions/filterComponents'
12
12
  import { stableStringify } from './utils/stableStringify'
13
13
 
14
14
  export {
15
- Thumbmark, getThumbmark, getVersion,
15
+ Thumbmark, getThumbmark, getVersion, ThumbmarkResponse,
16
16
 
17
17
  // Filtering functions for server-side use
18
18
  filterThumbmarkData, optionsInterface, stabilizationExclusionRules,
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Tests for Thumbmark class behavior in Node/Jest environment
3
+ *
4
+ * These tests verify that Thumbmark works gracefully when browser APIs
5
+ * are not available (e.g., in Jest/jsdom or server-side environments).
6
+ */
7
+
8
+ import { Thumbmark } from './thumbmark';
9
+ import { jest } from '@jest/globals';
10
+
11
+ // Polyfill for sessionStorage in Node environment
12
+ if (typeof sessionStorage === 'undefined') {
13
+ (global as any).sessionStorage = {
14
+ getItem: jest.fn(),
15
+ setItem: jest.fn(),
16
+ removeItem: jest.fn(),
17
+ clear: jest.fn(),
18
+ length: 0,
19
+ key: jest.fn(),
20
+ };
21
+ }
22
+
23
+
24
+ describe('Thumbmark in Node/Jest environment', () => {
25
+ test('Thumbmark.get() should not throw in non-browser environment', async () => {
26
+ const tm = new Thumbmark();
27
+
28
+ // This should not throw, even if browser APIs are unavailable
29
+ const result = await tm.get();
30
+
31
+ // Should return a valid result with structure
32
+ expect(result).toBeDefined();
33
+ expect(result.thumbmark).toBeDefined();
34
+ expect(typeof result.thumbmark).toBe('string');
35
+ });
36
+
37
+ test('Thumbmark.get() returns valid structure even with missing browser APIs', async () => {
38
+ const tm = new Thumbmark();
39
+
40
+ const result = await tm.get();
41
+
42
+ // Verify the response structure
43
+ expect(result).toHaveProperty('thumbmark');
44
+ expect(result).toHaveProperty('components');
45
+ expect(result).toHaveProperty('version');
46
+ expect(result).toHaveProperty('info');
47
+
48
+ // Components should be an object (possibly with fewer values than in real browser)
49
+ expect(typeof result.components).toBe('object');
50
+ });
51
+
52
+ test('Thumbmark.getVersion() works in any environment', () => {
53
+ const tm = new Thumbmark();
54
+ const version = tm.getVersion();
55
+
56
+ expect(version).toBeDefined();
57
+ expect(typeof version).toBe('string');
58
+ });
59
+ });
package/src/thumbmark.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { optionsInterface } from "./options";
2
- import { getThumbmark, includeComponent as globalIncludeComponent } from './functions';
2
+ import { getThumbmark, includeComponent as globalIncludeComponent, ThumbmarkResponse } from './functions';
3
3
  import { getVersion } from "./utils/version";
4
4
  import { defaultOptions } from "./options";
5
5
  import { componentInterface } from "./factory";
@@ -22,9 +22,9 @@ export class Thumbmark {
22
22
  /**
23
23
  * Generates a thumbmark using the instance's configuration.
24
24
  * @param overrideOptions - Options to override for this specific call.
25
- * @returns The thumbmark result.
25
+ * @returns The thumbmark result containing the fingerprint hash, components, and metadata.
26
26
  */
27
- public async get(overrideOptions?: optionsInterface): Promise<any> {
27
+ public async get(overrideOptions?: optionsInterface): Promise<ThumbmarkResponse> {
28
28
  const finalOptions = { ...this.options, ...overrideOptions };
29
29
  return getThumbmark(finalOptions);
30
30
  }
package/src/utils/hash.ts CHANGED
@@ -4,8 +4,35 @@
4
4
  */
5
5
 
6
6
  function encodeUtf8(text: string): ArrayBuffer {
7
- const encoder = new TextEncoder();
8
- return encoder.encode(text).buffer;
7
+ // Use TextEncoder if available (browser, modern Node.js)
8
+ if (typeof TextEncoder !== 'undefined') {
9
+ const encoder = new TextEncoder();
10
+ return encoder.encode(text).buffer;
11
+ }
12
+
13
+ // Fallback for environments without TextEncoder (older Node.js, some test runners)
14
+ const utf8: number[] = [];
15
+ for (let i = 0; i < text.length; i++) {
16
+ let charCode = text.charCodeAt(i);
17
+ if (charCode < 0x80) {
18
+ utf8.push(charCode);
19
+ } else if (charCode < 0x800) {
20
+ utf8.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
21
+ } else if (charCode < 0xd800 || charCode >= 0xe000) {
22
+ utf8.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
23
+ } else {
24
+ // Surrogate pair
25
+ i++;
26
+ charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (text.charCodeAt(i) & 0x3ff));
27
+ utf8.push(
28
+ 0xf0 | (charCode >> 18),
29
+ 0x80 | ((charCode >> 12) & 0x3f),
30
+ 0x80 | ((charCode >> 6) & 0x3f),
31
+ 0x80 | (charCode & 0x3f)
32
+ );
33
+ }
34
+ }
35
+ return new Uint8Array(utf8).buffer;
9
36
  }
10
37
 
11
38
  function fmix (input : number) : number {