@vigilkids/device-fingerprint 0.1.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.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/index.d.mts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.mjs +41 -0
- package/dist/types.d.mts +21 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.mjs +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present VigilKids
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @vigilkids/device-fingerprint
|
|
2
|
+
|
|
3
|
+
Browser device fingerprint and platform standard client headers for VigilKids / Visiva frontend applications. Built on [FingerprintJS v4](https://github.com/nicknisi/fingerprintjs) (MIT, open-source).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Deterministic fingerprint** — same hash in normal and incognito mode (canvas/webgl/audio signals)
|
|
8
|
+
- **Memory-only cache** — no cookie/localStorage dependency, recomputes on page load (~50ms)
|
|
9
|
+
- **Platform headers** — generates `X-Device-ID`, `X-Client-Type`, `X-Client-Platform`, `X-Product-Code` headers
|
|
10
|
+
- **Multi-product** — works with any VigilKids product (`vigilkids`, `visiva`, etc.)
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @vigilkids/device-fingerprint
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Client Headers (recommended)
|
|
21
|
+
|
|
22
|
+
Most use cases only need `getClientHeaders` — it computes the fingerprint internally and returns all standard headers:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { getClientHeaders } from '@vigilkids/device-fingerprint'
|
|
26
|
+
|
|
27
|
+
const headers = await getClientHeaders({
|
|
28
|
+
productCode: 'visiva',
|
|
29
|
+
appVersion: '1.0.0', // optional
|
|
30
|
+
})
|
|
31
|
+
// {
|
|
32
|
+
// 'X-Device-ID': 'a1b2c3d4e5f6...',
|
|
33
|
+
// 'X-Client-Type': 'web',
|
|
34
|
+
// 'X-Client-Platform': 'web',
|
|
35
|
+
// 'X-Product-Code': 'visiva',
|
|
36
|
+
// 'X-Client-Version': '1.0.0',
|
|
37
|
+
// }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Raw Fingerprint
|
|
41
|
+
|
|
42
|
+
For direct access to the fingerprint result:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { getDeviceFingerprint } from '@vigilkids/device-fingerprint'
|
|
46
|
+
|
|
47
|
+
const { visitorId, confidence } = await getDeviceFingerprint()
|
|
48
|
+
// visitorId: hex string (FingerprintJS hash)
|
|
49
|
+
// confidence: 0-1 (higher = more reliable)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Reset Cache
|
|
53
|
+
|
|
54
|
+
Force recomputation on next call (typically not needed):
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { resetFingerprint } from '@vigilkids/device-fingerprint'
|
|
58
|
+
|
|
59
|
+
resetFingerprint()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Exports
|
|
63
|
+
|
|
64
|
+
| Entry Point | Import | Content |
|
|
65
|
+
|-------------|--------|---------|
|
|
66
|
+
| Main | `@vigilkids/device-fingerprint` | `getDeviceFingerprint`, `getClientHeaders`, `resetFingerprint` |
|
|
67
|
+
| Types | `@vigilkids/device-fingerprint/types` | `DeviceFingerprintResult`, `ClientHeaderOptions` |
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
getClientHeaders()
|
|
73
|
+
└─ getDeviceFingerprint()
|
|
74
|
+
└─ FingerprintJS.load() → agent (singleton, cached)
|
|
75
|
+
└─ agent.get() → { visitorId, confidence }
|
|
76
|
+
↓ (memory cache)
|
|
77
|
+
└─ return cached result
|
|
78
|
+
└─ build headers map with X-Device-ID = visitorId
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The fingerprint is computed from browser hardware signals (canvas rendering, WebGL renderer, audio context, etc.). These signals are consistent across normal and incognito sessions on the same device/browser combination.
|
|
82
|
+
|
|
83
|
+
## Backend Integration
|
|
84
|
+
|
|
85
|
+
The generated headers are consumed by the OneX backend middleware chain:
|
|
86
|
+
|
|
87
|
+
1. `ClientContextMiddleware` extracts `X-Device-ID` → `ClientContext.DeviceID`
|
|
88
|
+
2. `SessionService.Create()` stores `DeviceID` in `p_identity_sessions.device_id`
|
|
89
|
+
3. Audit and promotion modules use the device ID for logging and fraud analysis
|
|
90
|
+
|
|
91
|
+
## Compatibility
|
|
92
|
+
|
|
93
|
+
- Browser only (requires `window`, `document`, `canvas`)
|
|
94
|
+
- ESM only
|
|
95
|
+
- TypeScript >= 5.0
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
[MIT](./LICENSE)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DeviceFingerprintResult, ClientHeaderOptions } from './types.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 获取当前设备的指纹
|
|
5
|
+
*
|
|
6
|
+
* 首次调用会计算指纹(~50ms),后续调用返回内存缓存。
|
|
7
|
+
* 无痕模式下产生相同 hash(基于 canvas/webgl/audio 等硬件信号,不依赖 cookie/storage)。
|
|
8
|
+
*
|
|
9
|
+
* @throws 当浏览器环境不支持指纹计算时抛出错误(不使用随机 fallback)
|
|
10
|
+
*/
|
|
11
|
+
declare function getDeviceFingerprint(): Promise<DeviceFingerprintResult>;
|
|
12
|
+
/**
|
|
13
|
+
* 清除内存缓存,强制下次调用重新计算
|
|
14
|
+
*
|
|
15
|
+
* 一般不需要调用。仅用于测试或特殊场景(如检测到硬件变更)。
|
|
16
|
+
*/
|
|
17
|
+
declare function resetFingerprint(): void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 生成平台标准客户端请求头
|
|
21
|
+
*
|
|
22
|
+
* 所有前端站点的 API 请求应携带这些 header,供后端:
|
|
23
|
+
* - Identity 模块:记录设备信息到 session
|
|
24
|
+
* - Audit 模块:记录访问日志
|
|
25
|
+
* - Promotion 模块:防欺诈分析
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const headers = await getClientHeaders({ productCode: 'visiva' })
|
|
30
|
+
* // 结果:
|
|
31
|
+
* // {
|
|
32
|
+
* // 'X-Device-ID': 'a1b2c3d4e5f6...',
|
|
33
|
+
* // 'X-Client-Type': 'web',
|
|
34
|
+
* // 'X-Client-Platform': 'web',
|
|
35
|
+
* // 'X-Product-Code': 'visiva',
|
|
36
|
+
* // }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function getClientHeaders(options: ClientHeaderOptions): Promise<Record<string, string>>;
|
|
40
|
+
|
|
41
|
+
export { ClientHeaderOptions, DeviceFingerprintResult, getClientHeaders, getDeviceFingerprint, resetFingerprint };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DeviceFingerprintResult, ClientHeaderOptions } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 获取当前设备的指纹
|
|
5
|
+
*
|
|
6
|
+
* 首次调用会计算指纹(~50ms),后续调用返回内存缓存。
|
|
7
|
+
* 无痕模式下产生相同 hash(基于 canvas/webgl/audio 等硬件信号,不依赖 cookie/storage)。
|
|
8
|
+
*
|
|
9
|
+
* @throws 当浏览器环境不支持指纹计算时抛出错误(不使用随机 fallback)
|
|
10
|
+
*/
|
|
11
|
+
declare function getDeviceFingerprint(): Promise<DeviceFingerprintResult>;
|
|
12
|
+
/**
|
|
13
|
+
* 清除内存缓存,强制下次调用重新计算
|
|
14
|
+
*
|
|
15
|
+
* 一般不需要调用。仅用于测试或特殊场景(如检测到硬件变更)。
|
|
16
|
+
*/
|
|
17
|
+
declare function resetFingerprint(): void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 生成平台标准客户端请求头
|
|
21
|
+
*
|
|
22
|
+
* 所有前端站点的 API 请求应携带这些 header,供后端:
|
|
23
|
+
* - Identity 模块:记录设备信息到 session
|
|
24
|
+
* - Audit 模块:记录访问日志
|
|
25
|
+
* - Promotion 模块:防欺诈分析
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const headers = await getClientHeaders({ productCode: 'visiva' })
|
|
30
|
+
* // 结果:
|
|
31
|
+
* // {
|
|
32
|
+
* // 'X-Device-ID': 'a1b2c3d4e5f6...',
|
|
33
|
+
* // 'X-Client-Type': 'web',
|
|
34
|
+
* // 'X-Client-Platform': 'web',
|
|
35
|
+
* // 'X-Product-Code': 'visiva',
|
|
36
|
+
* // }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function getClientHeaders(options: ClientHeaderOptions): Promise<Record<string, string>>;
|
|
40
|
+
|
|
41
|
+
export { ClientHeaderOptions, DeviceFingerprintResult, getClientHeaders, getDeviceFingerprint, resetFingerprint };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
|
2
|
+
|
|
3
|
+
let agentPromise = null;
|
|
4
|
+
let cachedResult = null;
|
|
5
|
+
function getAgent() {
|
|
6
|
+
if (!agentPromise) {
|
|
7
|
+
agentPromise = FingerprintJS.load();
|
|
8
|
+
}
|
|
9
|
+
return agentPromise;
|
|
10
|
+
}
|
|
11
|
+
async function getDeviceFingerprint() {
|
|
12
|
+
if (cachedResult) {
|
|
13
|
+
return cachedResult;
|
|
14
|
+
}
|
|
15
|
+
const agent = await getAgent();
|
|
16
|
+
const result = await agent.get();
|
|
17
|
+
cachedResult = {
|
|
18
|
+
visitorId: result.visitorId,
|
|
19
|
+
confidence: result.confidence.score
|
|
20
|
+
};
|
|
21
|
+
return cachedResult;
|
|
22
|
+
}
|
|
23
|
+
function resetFingerprint() {
|
|
24
|
+
cachedResult = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function getClientHeaders(options) {
|
|
28
|
+
const fp = await getDeviceFingerprint();
|
|
29
|
+
const headers = {
|
|
30
|
+
"X-Device-ID": fp.visitorId,
|
|
31
|
+
"X-Client-Type": "web",
|
|
32
|
+
"X-Client-Platform": "web",
|
|
33
|
+
"X-Product-Code": options.productCode
|
|
34
|
+
};
|
|
35
|
+
if (options.appVersion) {
|
|
36
|
+
headers["X-Client-Version"] = options.appVersion;
|
|
37
|
+
}
|
|
38
|
+
return headers;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { getClientHeaders, getDeviceFingerprint, resetFingerprint };
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设备指纹计算结果
|
|
3
|
+
*
|
|
4
|
+
* visitorId 基于浏览器硬件信号(canvas/webgl/audio)计算,
|
|
5
|
+
* 同一浏览器+硬件在正常和无痕模式下产生相同 hash。
|
|
6
|
+
*/
|
|
7
|
+
interface DeviceFingerprintResult {
|
|
8
|
+
/** 设备指纹 hash(十六进制字符串,由 FingerprintJS 生成) */
|
|
9
|
+
visitorId: string;
|
|
10
|
+
/** 指纹置信度 0-1(越高越可靠) */
|
|
11
|
+
confidence: number;
|
|
12
|
+
}
|
|
13
|
+
/** 平台标准请求头(所有 API 请求携带) */
|
|
14
|
+
interface ClientHeaderOptions {
|
|
15
|
+
/** 产品标识(如 'visiva', 'vigilkids') */
|
|
16
|
+
productCode: string;
|
|
17
|
+
/** 客户端版本号 */
|
|
18
|
+
appVersion?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type { ClientHeaderOptions, DeviceFingerprintResult };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设备指纹计算结果
|
|
3
|
+
*
|
|
4
|
+
* visitorId 基于浏览器硬件信号(canvas/webgl/audio)计算,
|
|
5
|
+
* 同一浏览器+硬件在正常和无痕模式下产生相同 hash。
|
|
6
|
+
*/
|
|
7
|
+
interface DeviceFingerprintResult {
|
|
8
|
+
/** 设备指纹 hash(十六进制字符串,由 FingerprintJS 生成) */
|
|
9
|
+
visitorId: string;
|
|
10
|
+
/** 指纹置信度 0-1(越高越可靠) */
|
|
11
|
+
confidence: number;
|
|
12
|
+
}
|
|
13
|
+
/** 平台标准请求头(所有 API 请求携带) */
|
|
14
|
+
interface ClientHeaderOptions {
|
|
15
|
+
/** 产品标识(如 'visiva', 'vigilkids') */
|
|
16
|
+
productCode: string;
|
|
17
|
+
/** 客户端版本号 */
|
|
18
|
+
appVersion?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type { ClientHeaderOptions, DeviceFingerprintResult };
|
package/dist/types.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vigilkids/device-fingerprint",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser device fingerprint and platform standard client headers for VigilKids frontend applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./types": {
|
|
14
|
+
"types": "./dist/types.d.ts",
|
|
15
|
+
"import": "./dist/types.mjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "unbuild"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/1yhy/onex.git",
|
|
31
|
+
"directory": "packages/device-fingerprint"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"device-fingerprint",
|
|
35
|
+
"fingerprintjs",
|
|
36
|
+
"browser-fingerprint",
|
|
37
|
+
"client-headers",
|
|
38
|
+
"vigilkids"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@fingerprintjs/fingerprintjs": "^4.6.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"unbuild": "^3.5.0"
|
|
46
|
+
}
|
|
47
|
+
}
|