101 |
102 | **There are 6 leading core features about TrustDevice Pro:**
103 |
104 | ### 1. Wide Coverage
105 | Comprehensive coverage of Android, iOS, Web, H5, applets and other device types.
106 |
107 | ### 2. Stable and Reliable
108 | TrustDevice served more than 10,000 clients, 200 million+ daily active users , and 6 billion+ devices , with excellent product functions and stability.
109 | The fingerprint accuracy of different terminal devices exceeded 99.9%, and the output of risk labels exceeded 70 items.
110 |
111 | ### 3. Unparalleled Safety
112 | TrustDevice's code virtualization & obfuscation technology make the malware fraudsters suffer from painful cost and imprecision when performing reverse-engineering.
113 |
114 | ### 4. Core Intellectual Property
115 | Fully independent intellectual property rights, with a number of patented technology.
116 |
117 | ### 5. Security Compliance
118 | TrustDevice is committed to the highest standards in security and compliance to keep your data safe.
119 | GDPR/CCA/PCI DSS/ISO 27701/ISO 9001 Compliant.
120 |
121 | ### 6. Easy to Deploy
122 | SaaS(Software as a Service)deployment supported, reducing massively your integration cost and enabling rapid access to device fingerprint service.
123 |
124 |
125 |
126 | # Where to get support
127 | We are happy to provide technical support for our open-source trustdevice-js library. We recommend using GitHub Issues to submit bugs or Discussions to ask questions. Using [issues](https://github.com/trustdecision/trustdevice-js/issues) and [discussions](https://github.com/trustdecision/trustdevice-js/discussions) publicly will help the open-source community and other users with similar issues.
128 |
129 | In addition, any idea or interest in using TrustDevice Pro can be found on the official website( [https://www.trustdecision.com/solutions/trustdevice](https://www.trustdecision.com/solutions/trustdevice) ), registered account, free trial; Or via email( TrustDevice@TrustDecision.com ), contact us directly and quickly open the service.
130 |
131 | ## TrustDevice Pro VS Others
132 |
133 |
134 |
Type
Scene
Result
TrusDevice Pro
Fingerprint
Seon
135 |
136 |
137 |
Device Fingerprint Compatibility
IE9 and above
Able to collect device info and generate device ID
✅
❌ (not supported by IE)
❌ (IE10 and below are not supported)
138 |
139 |
140 |
Device fingerprint uniqueness
Access web application twice using browser on the same device
Device fingerprints/ID matches
✅
✅
✅
141 |
142 |
143 |
Access web application using browser on two different devices
Device fingerprint/ID should not match. Each device to have its own unique device fingeprint/ID
✅
✅
✅
144 |
145 |
146 |
Device Fingerprint Stability
Clears the browser cache and cookies
Device fingerprints/ID still matches before and after clearing
✅
✅
✅
147 |
148 |
149 |
Incognito and non-incognito mode on the same device browser
Device fingerprints/ID match
✅
✅
✅
150 |
151 |
152 |
The same device browser before and after User-Agent modification
Device fingerprints/ID match
✅
✅
❌
153 |
154 |
155 |
Before and after browser upgrade on the same device
Device fingerprints/ID match
✅
✅
✅
156 |
157 |
158 |
Device Fingerprint Risk Identification
Web crawlers (bot attacks)
Ability to identify web crawlers (bot attacks)
✅
❌
✅
159 |
160 |
161 |
Incognito mode
Ability to recognize that the browser is in incognito mode/private browsing
Able to identify the risk of use of headless browser
✅
❌
✅
165 |
166 |
167 |
Abnormal user-agent
Ability to identify UA anomalies
✅
❌
✅
168 |
169 |
170 |
JS is debugged
Can identify debugging behavior on JS
✅
❌
❌
171 |
172 |
173 |
JS is tampered with
Can identify tampering
✅
❌
❌
174 |
175 |
176 |
Do not use cookies
Ability to recognize the prohibition of the use of cookies
✅
❌
✅
177 |
178 |
179 |
Browser parameters have been tampered with
Can identify browser environment tampering
✅
❌
✅
180 |
181 |
182 |
Common browser cheating plug-ins
Can identify common cheating plug-in risks
✅
❌
✅
183 |
184 |
185 |
Security and Stability
Code Protection
The device fingerprint SDK and JS have code protection mechanisms (such as VMP), which effectively resist black hat cracking attempts to manipulate code logic and falsify data
✅ (VMP)
❌
❌
186 |
187 |
188 |
Downgrade
Intercepting and sending collection requests in the Web environment can still generate device fingerprints normally
✅
✅
✅
189 |
190 |
Anti packet capture
Web (including iOS and android web) have the ability to prevent packet capture
✅
❌
❌
191 |
192 |
193 |
194 | ## License
195 |
196 | This library is MIT licensed. Copyright trustdecision, Inc. 2022.
197 |
198 |
--------------------------------------------------------------------------------
/dist/fm.js:
--------------------------------------------------------------------------------
1 | var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},toConsumableArray=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");for(1>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");1>16)+(t>>16)+(n>>16)<<16|65535&n}function c(e,t,n,o,r,i){return f((t=f(f(t,e),f(o,i)))<>>32-r,n)}function d(e,t,n,o,r,i,a){return c(t&n|~t&o,e,t,r,i,a)}function m(e,t,n,o,r,i,a){return c(t&o|n&~o,e,t,r,i,a)}function p(e,t,n,o,r,i,a){return c(t^n^o,e,t,r,i,a)}function h(e,t,n,o,r,i,a){return c(n^(t|~o),e,t,r,i,a)}function a(e,t){e[t>>5]|=128<>>9<<4)]=t;for(var n,o,r,i,a=1732584193,c=-271733879,s=-1732584194,l=271733878,u=0;u>5]>>>o%32&255);return t}function l(e){var t=[];for(t[(e.length>>2)-1]=void 0,o=0;o>5]|=(255&e.charCodeAt(o/8))<>>4&15)+"0123456789abcdef".charAt(15&t);return n}function u(e){return unescape(encodeURIComponent(e))}function r(e){return s(a(l(e=u(e)),8*e.length))}function i(e,t){var n,e=u(e),t=u(t),o=l(e),r=[],i=[];for(r[15]=i[15]=void 0,16n[t-e]&&n[t]>n[t+e]})}(i)&&o.push(n[i]),13!==o.length);i++);return o.reduce(function(e,t){return Math.abs(e)+Math.abs(t)})}i.threshold&&(i.threshold.value=-50),i.knee&&(i.knee.value=40),i.ratio&&(i.ratio.value=12),i.reduction&&(i.reduction.value=-20),i.attack&&(i.attack.value=0),i.release&&(i.release.value=.25),o.type="sine",o.connect(i),i.connect(r),r.connect(e.destination),e.oncomplete=function(){var e=new Float32Array(r.frequencyBinCount),e=(r.getFloatFrequencyData(e),o.disconnect(),i.disconnect(),r.disconnect(),a(e));clearTimeout(n),t(e)},o.start(0),e.startRendering()}return new Promise(function(e){var t;window.OfflineAudioContext||window.webkitOfflineAudioContext?(t=setTimeout(function(){return e("")},window._fmOpt.timeout||2e3),n(e,t)):e("")})}function getFont(){var e=["Helvetica","Arial","Tahoma","PingFang SC","Hiragino Sans GB","Heiti SC","Segoe UI","Microsoft YaHei","WenQuanYi Micro Hei","Roboto","Noto Sans","Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji","Times New Roman","Verdana","sans-serif-smallcaps","cursive","sans-serif-black","sans-serif-condensed-light","sans-serif-thin","serif-monospace","sans-serif-medium","sans-serif-light","sans-serif-condensed-medium","casual"];for(var t=new function(){var e,r=["monospace","sans-serif","serif"],i=document.getElementsByTagName("body")[0],a=document.createElement("span"),c=(a.style.fontSize="72px",a.style.position="absolute",a.style.left="-9999px",a.style.lineHeight="normal",a.innerHTML="ssssssssssslli",{}),s={};for(e in r)a.style.fontFamily=r[e],i.appendChild(a),c[r[e]]=a.offsetWidth,s[r[e]]=a.offsetHeight,i.removeChild(a);this.detect=function(e){var t,n=!1;for(t in r){a.style.fontFamily=e+","+r[t],i.appendChild(a);var o=a.offsetWidth!==c[r[t]]||a.offsetHeight!==s[r[t]];i.removeChild(a),n=n||o;break}return n}},n="",o=0;o
2 |
3 |
4 |
5 | TrustDecisionJs
6 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
Device ID:
60 |
61 |
Device Risk Label:
62 |
63 |
User Agent:
64 |
65 |
Device Detail:
66 |
67 |
68 |
69 |
70 |
71 |
72 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@trustdevicejs/trustdevice-js",
3 | "version": "1.0.6",
4 | "main": "dist/fm.js",
5 | "description": "A lightweight library for determining device uniqueness and risk identification.",
6 | "devDependencies": {
7 | "babel-core": "^6.26.3",
8 | "babel-plugin-external-helpers": "^6.22.0",
9 | "babel-preset-env": "^1.7.0",
10 | "babel-preset-latest": "^6.24.1",
11 | "eslint-config-airbnb-base": "^13.0.0",
12 | "eslint-plugin-import": "^2.13.0",
13 | "rollup": "^1.19.4",
14 | "rollup-plugin-alias": "^1.5.1",
15 | "rollup-plugin-babel": "^3.0.7",
16 | "rollup-plugin-commonjs": "^9.1.3",
17 | "rollup-plugin-eslint": "^4.0.0",
18 | "rollup-plugin-node-builtins": "^2.1.2",
19 | "rollup-plugin-node-globals": "^1.4.0",
20 | "rollup-plugin-node-resolve": "^3.3.0",
21 | "rollup-plugin-uglify": "^6.0.2"
22 | },
23 | "scripts": {
24 | "build": "NODE_ENV=dev rollup -c"
25 | },
26 | "dependencies": {
27 | "promise-polyfill": "^8.1.0"
28 | },
29 | "author": "trustdevice",
30 | "keywords": [
31 | "trustdevice",
32 | "trustdecision",
33 | "Fingerprint",
34 | "deviceid",
35 | "device"
36 | ],
37 | "license": "MIT",
38 | "repository": {
39 | "type": "git",
40 | "url": "git+https://github.com/trustdecision/trustdevice-js.git"
41 | },
42 | "bugs": {
43 | "url": "https://github.com/trustdecision/trustdevice-js/issues"
44 | },
45 | "homepage": "https://github.com/trustdecision/trustdevice-js#readme"
46 | }
47 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve'
2 | import commonjs from 'rollup-plugin-commonjs'
3 | import babel from 'rollup-plugin-babel'
4 | import eslint from 'rollup-plugin-eslint'
5 | import { uglify } from "rollup-plugin-uglify";
6 |
7 | const plugins = [
8 | resolve(),
9 | commonjs(),
10 | eslint(),
11 | babel({
12 | exclude: 'node_modules/**', // 只编译源代码
13 | }),
14 | uglify()
15 | ]
16 |
17 | const output = {
18 | name: 'fm',
19 | file: 'dist/fm.js',
20 | // format: 'umd',
21 | strict: false,
22 | }
23 |
24 | let config = [{
25 | input: 'src/index.js',
26 | output,
27 | plugins,
28 | }]
29 |
30 | export default config;
31 |
--------------------------------------------------------------------------------
/src/detection/audioRipple.js:
--------------------------------------------------------------------------------
1 | export default function getAudioRipple() {
2 | function audioRipple(resolve, audioRippleTimeOut) {
3 | const AudioCtxFn = window.OfflineAudioContext
4 | || window.webkitOfflineAudioContext
5 | const audioCtx = new AudioCtxFn(1, 4000, 44100)
6 | const oscillator = audioCtx.createOscillator()
7 | const analyser = audioCtx.createAnalyser()
8 | const compressor = audioCtx.createDynamicsCompressor()
9 | compressor.threshold && (compressor.threshold.value = -50)
10 | compressor.knee && (compressor.knee.value = 40)
11 | compressor.ratio && (compressor.ratio.value = 12)
12 | compressor.reduction && (compressor.reduction.value = -20)
13 | compressor.attack && (compressor.attack.value = 0)
14 | compressor.release && (compressor.release.value = 0.25)
15 | oscillator.type = 'sine'
16 | oscillator.connect(compressor)
17 | compressor.connect(analyser)
18 | analyser.connect(audioCtx.destination)
19 |
20 | function getPeaksSum(bins, n = 20) {
21 | const peaks = []
22 | const checkers = [...Array(n + 1)
23 | .keys()].slice(1)
24 | const isPeak = i => checkers.every(j => bins[i] > bins[i - j] && bins[i] > bins[i + j])
25 | for (let i = n; i < bins.length - n; i++) {
26 | if (isPeak(i)) peaks.push(bins[i])
27 | // 只取前 13 个峰值
28 | if (peaks.length === 13) break
29 | }
30 | return peaks.reduce((a, b) => Math.abs(a) + Math.abs(b))
31 | }
32 |
33 | // eslint-disable-next-line func-names
34 | audioCtx.oncomplete = function () {
35 | const bins = new Float32Array(analyser.frequencyBinCount)
36 | analyser.getFloatFrequencyData(bins)
37 | oscillator.disconnect()
38 | compressor.disconnect()
39 | analyser.disconnect()
40 | const peaksSum = getPeaksSum(bins)
41 | // 获取到目标值,resolve 之前清理 audioRippleTimeOut
42 | clearTimeout(audioRippleTimeOut)
43 | resolve(peaksSum)
44 | }
45 |
46 | oscillator.start(0)
47 | audioCtx.startRendering()
48 | }
49 |
50 | return new Promise((resolve) => {
51 | if (window.OfflineAudioContext || window.webkitOfflineAudioContext) {
52 | const audioRippleTimeOut = setTimeout(() => resolve(''), window._fmOpt.timeout || 2000)
53 | audioRipple(resolve, audioRippleTimeOut)
54 | } else {
55 | resolve('')
56 | }
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/src/detection/canvas.js:
--------------------------------------------------------------------------------
1 | import md5 from '../utils/md5'
2 |
3 | export default function getCanvas() {
4 | try {
5 | const canvas = document.createElement('canvas')
6 | const ctx = canvas.getContext('2d')
7 | canvas.width = 120
8 | canvas.height = 20
9 | const txt = 'TrustDecisionJS'
10 | ctx.font = "14px 'Arial'"
11 | ctx.fillStyle = '#fff'
12 | ctx.fillRect(0, 0, 120, 20)
13 | ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'
14 | ctx.fillText(txt, 2, 15)
15 | const canvasData = canvas.toDataURL()
16 | if (canvas.toDataURL()) {
17 | return md5(canvasData)
18 | }
19 | return ''
20 | } catch (e) {
21 | return ''
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/detection/cookie.js:
--------------------------------------------------------------------------------
1 | const win = window
2 | const doc = document
3 | const nav = window.navigator
4 | let domain
5 | const ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/
6 | const points = (win.location.hostname || '').match(/\./g)
7 | const pointsCount = !points ? 0 : points.length
8 | if (ipRegex.exec(win.location.hostname)) {
9 | domain = win.location.hostname
10 | } else if (pointsCount > 2) {
11 | domain = `.${win.location.hostname.replace(/^(\w+)\./, '')}`
12 | } else {
13 | domain = `.${win.location.hostname.replace(/^(?:.+\.)?(\w+\.\w+)$/, '$1')}`
14 | }
15 | const cookie = {
16 | set(name, value) {
17 | if (nav.cookieEnabled) {
18 | try {
19 | if (win.localStorage) {
20 | localStorage[name] = value
21 | }
22 | } catch (e) {
23 | }
24 | try {
25 | if (win.sessionStorage) {
26 | win.sessionStorage.setItem(name, value)
27 | }
28 | } catch (e) {
29 | }
30 | const days = 365 * 1000 * 60 * 60 * 24
31 | let c = `${name}=${encodeURIComponent(value)}`
32 | c += `; domain=${domain}; expires=${new Date(new Date().getTime() + days).toGMTString()}; path=/`
33 | doc.cookie = c
34 | }
35 | },
36 | get(name) {
37 | let value = ''
38 | if (nav.cookieEnabled) {
39 | try {
40 | if (win.localStorage) {
41 | value = localStorage[name] || ''
42 | if (value.length === 32) {
43 | return value
44 | }
45 | }
46 | } catch (e) {
47 | }
48 | try {
49 | if (win.sessionStorage) {
50 | value = win.sessionStorage.getItem(name) || ''
51 | if (value.length === 32) {
52 | return value
53 | }
54 | }
55 | } catch (e) {
56 | }
57 |
58 | let start = doc.cookie.indexOf(`${name}=`)
59 | if (start !== -1) {
60 | start += name.length + 1
61 | let end = doc.cookie.indexOf(';', start)
62 | if (end === -1) { end = doc.cookie.length }
63 | value = decodeURIComponent(doc.cookie.substring(start, end)) || ''
64 | }
65 | }
66 | return value
67 | },
68 | }
69 |
70 | export default cookie
71 |
--------------------------------------------------------------------------------
/src/detection/debugger.js:
--------------------------------------------------------------------------------
1 | export default function getDebugger() {
2 | const threshold = 200
3 | const widthThreshold = window.outerWidth - window.innerWidth > threshold
4 | const heightThreshold = window.outerHeight - window.innerHeight > threshold
5 | if (window.screen && window.screen.width >= 800) {
6 | if (widthThreshold || heightThreshold) {
7 | return 1
8 | }
9 | }
10 | return 0
11 | }
12 |
--------------------------------------------------------------------------------
/src/detection/disabledCookie.js:
--------------------------------------------------------------------------------
1 | export default function getDisabledCookie() {
2 | let status = 0
3 | if (navigator.cookieEnabled !== undefined) {
4 | if (navigator.cookieEnabled) {
5 | status = 0
6 | } else {
7 | try {
8 | document.cookie = 'TDJScookieDemo=test; SameSite=Strict;'
9 | if (document.cookie.indexOf('TDJScookieDemo=') !== -1) {
10 | status = 1
11 | }
12 | document.cookie = 'TDJScookieDemo=test; SameSite=Strict; expires=Thu, 01-Jan-1970 00:00:01 GMT'
13 | } catch (e) {
14 | status = 1
15 | }
16 | }
17 | }
18 | return status
19 | }
20 |
--------------------------------------------------------------------------------
/src/detection/document.js:
--------------------------------------------------------------------------------
1 | export default function getDocument() {
2 | const {
3 | referrer = '',
4 | } = document
5 | return { referrer }
6 | }
7 |
--------------------------------------------------------------------------------
/src/detection/font.js:
--------------------------------------------------------------------------------
1 | import md5 from '../utils/md5'
2 |
3 | export default function getFont() {
4 | const fonts = [
5 | 'Helvetica',
6 | 'Arial',
7 | 'Tahoma',
8 | 'PingFang SC',
9 | 'Hiragino Sans GB',
10 | 'Heiti SC',
11 | 'Segoe UI',
12 | 'Microsoft YaHei',
13 | 'WenQuanYi Micro Hei',
14 | 'Roboto',
15 | 'Noto Sans',
16 | 'Apple Color Emoji',
17 | 'Segoe UI Emoji',
18 | 'Noto Color Emoji',
19 | 'Times New Roman',
20 | 'Verdana',
21 | 'sans-serif-smallcaps',
22 | 'cursive',
23 | 'sans-serif-black',
24 | 'sans-serif-condensed-light',
25 | 'sans-serif-thin',
26 | 'serif-monospace',
27 | 'sans-serif-medium',
28 | 'sans-serif-light',
29 | 'sans-serif-condensed-medium',
30 | 'casual',
31 | ]
32 |
33 | function Detector() {
34 | const dtFonts = ['monospace', 'sans-serif', 'serif']
35 | const testStr = 'ssssssssssslli'
36 | const testSize = '72px'
37 | const h = document.getElementsByTagName('body')[0]
38 | const s = document.createElement('span')
39 | s.style.fontSize = testSize
40 | s.style.position = 'absolute'
41 | s.style.left = '-9999px'
42 | s.style.lineHeight = 'normal'
43 | s.innerHTML = testStr
44 | const defaultWidth = {}
45 | const defaultHeight = {}
46 | /* eslint-disable guard-for-in */
47 | /* eslint-disable no-restricted-syntax */
48 | for (const index in dtFonts) {
49 | s.style.fontFamily = dtFonts[index]
50 | h.appendChild(s)
51 | defaultWidth[dtFonts[index]] = s.offsetWidth
52 | defaultHeight[dtFonts[index]] = s.offsetHeight
53 | h.removeChild(s)
54 | }
55 | function detect(font) {
56 | let detected = false
57 | for (const index in dtFonts) {
58 | s.style.fontFamily = `${font},${dtFonts[index]}`
59 | h.appendChild(s)
60 | const matched = s.offsetWidth !== defaultWidth[dtFonts[index]]
61 | || s.offsetHeight !== defaultHeight[dtFonts[index]]
62 | h.removeChild(s)
63 | detected = detected || matched
64 | if (detect) {
65 | break
66 | }
67 | }
68 | return detected
69 | }
70 | this.detect = detect
71 | }
72 | const d = new Detector()
73 | let fontString = ''
74 | for (let i = 0; i < fonts.length; i++) {
75 | if (d.detect(fonts[i])) {
76 | fontString += fonts[i]
77 | }
78 | }
79 | return md5(fontString)
80 | }
81 |
--------------------------------------------------------------------------------
/src/detection/gpu.js:
--------------------------------------------------------------------------------
1 | export default function getGpu() {
2 | try {
3 | const canvas = document.createElement('canvas')
4 | const gl = canvas.getContext('webgl')
5 | const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
6 | const result = `${gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)}|${gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)}`
7 | try {
8 | gl.getExtension('WEBGL_lose_context')
9 | .loseContext() // 清除WEBGL
10 | } catch (e) {
11 | /* .getExtension may be absent or broken (blocked by extension) */
12 | }
13 | return result || ''
14 | } catch (e) {
15 | return ''
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/detection/msMaxTouchPoints.js:
--------------------------------------------------------------------------------
1 | export default function getMaxTouchPoints() {
2 | let maxTouchPoints = 0
3 | if (typeof navigator.maxTouchPoints !== 'undefined') {
4 | // eslint-disable-next-line prefer-destructuring
5 | maxTouchPoints = navigator.maxTouchPoints
6 | } else if (typeof navigator.msMaxTouchPoints !== 'undefined') {
7 | maxTouchPoints = navigator.msMaxTouchPoints
8 | }
9 | return maxTouchPoints
10 | }
11 |
--------------------------------------------------------------------------------
/src/detection/navigator.js:
--------------------------------------------------------------------------------
1 | export default function getNavigator() {
2 | const {
3 | appCodeName,
4 | appName,
5 | appVersion,
6 | deviceMemory,
7 | doNotTrack,
8 | hardwareConcurrency,
9 | language,
10 | mimeTypes,
11 | platform,
12 | plugins,
13 | product,
14 | productSub,
15 | userAgent,
16 | vendor,
17 | vendorSub,
18 | webdriver,
19 | } = window.navigator
20 | return {
21 | appCodeName,
22 | appName,
23 | appVersion,
24 | deviceMemory,
25 | doNotTrack,
26 | hardwareConcurrency,
27 | language,
28 | // eslint-disable-next-line max-len
29 | mimeTypes: [...mimeTypes].map(item => ({ description: item.description, suffixes: item.suffixes, type: item.type })),
30 | platform,
31 | plugins: [...plugins].map(item => ({
32 | description: item.description, filename: item.filename, length: item.length, name: item.name,
33 | })),
34 | product,
35 | productSub,
36 | userAgent,
37 | vendor,
38 | vendorSub,
39 | webdriver,
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/detection/screen.js:
--------------------------------------------------------------------------------
1 | export default function getScreen() {
2 | const {
3 | height, width, colorDepth, pixelDepth,
4 | } = window.screen
5 | return {
6 | height, width, colorDepth, pixelDepth,
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/detection/support.js:
--------------------------------------------------------------------------------
1 | export default function getSupport() {
2 | let supportLocalStorage = false
3 | let supportSessionStorage = false
4 | let supportIndexedDB = false
5 | try {
6 | supportLocalStorage = !!window.localStorage
7 | } catch (e) {
8 | }
9 | try {
10 | supportSessionStorage = !!window.sessionStorage
11 | } catch (e) {
12 | }
13 | try {
14 | supportIndexedDB = !!window.indexedDB
15 | } catch (e) {
16 | }
17 | return {
18 | localStorage: supportLocalStorage,
19 | sessionStorage: supportSessionStorage,
20 | indexedDB: supportIndexedDB,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/detection/timezone.js:
--------------------------------------------------------------------------------
1 | export default function getTimezone() {
2 | const now = new Date()
3 | now.setDate(1)
4 | now.setMonth(5)
5 | const x1 = -now.getTimezoneOffset()
6 | now.setMonth(11)
7 | const x2 = -now.getTimezoneOffset()
8 | return Math.min(x1, x2)
9 | }
10 |
11 | export function getTimezoneStr() {
12 | if (window.Intl && window.Intl.DateTimeFormat
13 | && (new window.Intl.DateTimeFormat()).resolvedOptions) {
14 | return ((new window.Intl.DateTimeFormat()).resolvedOptions() || {}).timeZone || ''
15 | }
16 | return ''
17 | }
18 |
--------------------------------------------------------------------------------
/src/detection/touchSupport.js:
--------------------------------------------------------------------------------
1 | export default function getTouchSupport() {
2 | let touchEvent = false
3 | try {
4 | document.createEvent('TouchEvent')
5 | touchEvent = true
6 | } catch (e) {}
7 | return touchEvent
8 | }
9 |
--------------------------------------------------------------------------------
/src/detection/userAgentData.js:
--------------------------------------------------------------------------------
1 | export default function getUserAgentData() {
2 | return new Promise((resolve) => {
3 | if (navigator.userAgentData && navigator.userAgentData.getHighEntropyValues) {
4 | navigator.userAgentData.getHighEntropyValues([
5 | "architecture",
6 | "bitness",
7 | "formFactor",
8 | "fullVersionList",
9 | "model",
10 | "platformVersion",
11 | "uaFullVersion",
12 | "wow64"])
13 | .then((data) => {
14 | resolve(data)
15 | })
16 | }else {
17 | resolve('')
18 | }
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './utils/polyfill'
2 | import 'promise-polyfill/src/polyfill'
3 | import md5 from './utils/md5'
4 | import cookie from './detection/cookie'
5 | import getScreen from './detection/screen'
6 | import getNavigator from './detection/navigator'
7 | import getCanvas from './detection/canvas'
8 | import getMaxTouchPoints from './detection/msMaxTouchPoints'
9 | import getDebugger from './detection/debugger'
10 | import getDisabledCookie from './detection/disabledCookie'
11 | import getTimezone, { getTimezoneStr } from './detection/timezone'
12 | import getTouchSupport from './detection/touchSupport'
13 | import getGpu from './detection/gpu'
14 | import getSupport from './detection/support'
15 | import getAudioRipple from './detection/audioRipple'
16 | import getFont from './detection/font'
17 | import getDocument from './detection/document'
18 | import getUserAgentData from './detection/userAgentData'
19 |
20 | if (!window._fmOpt) {
21 | console.warn && console.warn('_fmOpt is not defined!')
22 | } else if (!window._fmOpt.success) {
23 | console.warn && console.warn('_fmOpt.success is not defined!')
24 | }
25 | const tasks = [
26 | {
27 | name: 'screen',
28 | active: getScreen,
29 | },
30 | {
31 | name: 'navigator',
32 | active: getNavigator,
33 | },
34 | {
35 | name: 'canvas',
36 | active: getCanvas,
37 | },
38 | {
39 | name: 'maxTouchPoints',
40 | active: getMaxTouchPoints,
41 | },
42 | {
43 | name: 'timezone',
44 | active: getTimezone,
45 | },
46 | {
47 | name: 'touchSupport',
48 | active: getTouchSupport,
49 | },
50 | {
51 | name: 'gpu',
52 | active: getGpu,
53 | },
54 | {
55 | name: 'support',
56 | active: getSupport,
57 | },
58 | {
59 | name: 'font',
60 | active: getFont,
61 | },
62 | ]
63 |
64 | const riskTasks = [
65 | {
66 | name: 'debugger',
67 | active: getDebugger,
68 | },
69 | {
70 | name: 'disabledCookie',
71 | active: getDisabledCookie,
72 | },
73 | ]
74 | const tasksAsync = [
75 | {
76 | name: 'audioRipple',
77 | active: getAudioRipple,
78 | },
79 | {
80 | name: 'userAgentData',
81 | active: getUserAgentData,
82 | },
83 | ]
84 |
85 | const result = {
86 | device_id: '',
87 | device_risk_label: {
88 | debugger: 0,
89 | disabledCookie: 0,
90 | },
91 | device_detail: {},
92 | }
93 |
94 | const deviceDetail = {}
95 | const deviceRiskLabel = {}
96 | const timezoneStr = getTimezoneStr()
97 | const documentDel = getDocument()
98 | const initTimestamp = Date.now()
99 | // eslint-disable-next-line array-callback-return
100 | tasks.map((item) => {
101 | if (item.name === 'navigator' || item.name === 'screen') {
102 | Object.assign(deviceDetail, item.active())
103 | } else {
104 | deviceDetail[item.name] = item.active()
105 | }
106 | })
107 | Promise.all(tasksAsync.map(item => item.active())).then((results) => {
108 | results.forEach((item, index) => {
109 | deviceDetail[tasksAsync[index].name] = item
110 | })
111 | const deviceIdCache = cookie.get('B106E2F6056FE017') || ''
112 | if (deviceIdCache && deviceIdCache.length === 32) {
113 | result.device_id = deviceIdCache
114 | } else if (Object.keys(deviceDetail).length !== 0) {
115 | const deviceId = md5(JSON.stringify(deviceDetail)) || ''
116 | if (deviceId && deviceId.length === 32) {
117 | result.device_id = deviceId
118 | cookie.set('B106E2F6056FE017', deviceId)
119 | }
120 | }
121 | // eslint-disable-next-line array-callback-return
122 | riskTasks.map((item) => {
123 | deviceRiskLabel[item.name] = item.active()
124 | })
125 | // eslint-disable-next-line camelcase
126 | result.device_detail = deviceDetail
127 | if (timezoneStr) {
128 | result.device_detail.timezoneStr = timezoneStr
129 | }
130 | if (documentDel.referrer) {
131 | result.device_detail.documentReferrer = documentDel.referrer
132 | }
133 | result.device_detail.initTime = Date.now() - initTimestamp
134 | result.device_risk_label = deviceRiskLabel
135 | window._fmOpt.success && window._fmOpt.success(result)
136 | })
137 |
--------------------------------------------------------------------------------
/src/utils/md5.js:
--------------------------------------------------------------------------------
1 | // eslint-disable
2 | function md5() {
3 | 'use strict'
4 |
5 | function t(n, t) {
6 | var r = (65535 & n) + (65535 & t)
7 | return (n >> 16) + (t >> 16) + (r >> 16) << 16 | 65535 & r
8 | }
9 |
10 | function r(n, t) {
11 | return n << t | n >>> 32 - t
12 | }
13 |
14 | function e(n, e, o, u, c, f) {
15 | return t(r(t(t(e, n), t(u, f)), c), o)
16 | }
17 |
18 | function o(n, t, r, o, u, c, f) {
19 | return e(t & r | ~t & o, n, t, u, c, f)
20 | }
21 |
22 | function u(n, t, r, o, u, c, f) {
23 | return e(t & o | r & ~o, n, t, u, c, f)
24 | }
25 |
26 | function c(n, t, r, o, u, c, f) {
27 | return e(t ^ r ^ o, n, t, u, c, f)
28 | }
29 |
30 | function f(n, t, r, o, u, c, f) {
31 | return e(r ^ (t | ~o), n, t, u, c, f)
32 | }
33 |
34 | function i(n, r) {
35 | n[r >> 5] |= 128 << r % 32, n[14 + (r + 64 >>> 9 << 4)] = r
36 | var e,
37 | i,
38 | a,
39 | d,
40 | h,
41 | l = 1732584193,
42 | g = -271733879,
43 | v = -1732584194,
44 | m = 271733878
45 | for (e = 0; e < n.length; e += 16) i = l, a = g, d = v, h = m, g = f(g = f(g = f(g = f(g = c(g = c(g = c(g = c(g = u(g = u(g = u(g = u(g = o(g = o(g = o(g = o(g, v = o(v, m = o(m, l = o(l, g, v, m, n[e], 7, -680876936), g, v, n[e + 1], 12, -389564586), l, g, n[e + 2], 17, 606105819), m, l, n[e + 3], 22, -1044525330), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 4], 7, -176418897), g, v, n[e + 5], 12, 1200080426), l, g, n[e + 6], 17, -1473231341), m, l, n[e + 7], 22, -45705983), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 8], 7, 1770035416), g, v, n[e + 9], 12, -1958414417), l, g, n[e + 10], 17, -42063), m, l, n[e + 11], 22, -1990404162), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 12], 7, 1804603682), g, v, n[e + 13], 12, -40341101), l, g, n[e + 14], 17, -1502002290), m, l, n[e + 15], 22, 1236535329), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 1], 5, -165796510), g, v, n[e + 6], 9, -1069501632), l, g, n[e + 11], 14, 643717713), m, l, n[e], 20, -373897302), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 5], 5, -701558691), g, v, n[e + 10], 9, 38016083), l, g, n[e + 15], 14, -660478335), m, l, n[e + 4], 20, -405537848), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 9], 5, 568446438), g, v, n[e + 14], 9, -1019803690), l, g, n[e + 3], 14, -187363961), m, l, n[e + 8], 20, 1163531501), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 13], 5, -1444681467), g, v, n[e + 2], 9, -51403784), l, g, n[e + 7], 14, 1735328473), m, l, n[e + 12], 20, -1926607734), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 5], 4, -378558), g, v, n[e + 8], 11, -2022574463), l, g, n[e + 11], 16, 1839030562), m, l, n[e + 14], 23, -35309556), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 1], 4, -1530992060), g, v, n[e + 4], 11, 1272893353), l, g, n[e + 7], 16, -155497632), m, l, n[e + 10], 23, -1094730640), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 13], 4, 681279174), g, v, n[e], 11, -358537222), l, g, n[e + 3], 16, -722521979), m, l, n[e + 6], 23, 76029189), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 9], 4, -640364487), g, v, n[e + 12], 11, -421815835), l, g, n[e + 15], 16, 530742520), m, l, n[e + 2], 23, -995338651), v = f(v, m = f(m, l = f(l, g, v, m, n[e], 6, -198630844), g, v, n[e + 7], 10, 1126891415), l, g, n[e + 14], 15, -1416354905), m, l, n[e + 5], 21, -57434055), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 12], 6, 1700485571), g, v, n[e + 3], 10, -1894986606), l, g, n[e + 10], 15, -1051523), m, l, n[e + 1], 21, -2054922799), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 8], 6, 1873313359), g, v, n[e + 15], 10, -30611744), l, g, n[e + 6], 15, -1560198380), m, l, n[e + 13], 21, 1309151649), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 4], 6, -145523070), g, v, n[e + 11], 10, -1120210379), l, g, n[e + 2], 15, 718787259), m, l, n[e + 9], 21, -343485551), l = t(l, i), g = t(g, a), v = t(v, d), m = t(m, h)
46 | return [l, g, v, m]
47 | }
48 |
49 | function a(n) {
50 | var t,
51 | r = '',
52 | e = 32 * n.length
53 | for (t = 0; t < e; t += 8) r += String.fromCharCode(n[t >> 5] >>> t % 32 & 255)
54 | return r
55 | }
56 |
57 | function d(n) {
58 | var t,
59 | r = []
60 | for (r[(n.length >> 2) - 1] = void 0, t = 0; t < r.length; t += 1) r[t] = 0
61 | var e = 8 * n.length
62 | for (t = 0; t < e; t += 8) r[t >> 5] |= (255 & n.charCodeAt(t / 8)) << t % 32
63 | return r
64 | }
65 |
66 | function h(n) {
67 | return a(i(d(n), 8 * n.length))
68 | }
69 |
70 | function l(n, t) {
71 | var r,
72 | e,
73 | o = d(n),
74 | u = [],
75 | c = []
76 | for (u[15] = c[15] = void 0, o.length > 16 && (o = i(o, 8 * n.length)), r = 0; r < 16; r += 1) u[r] = 909522486 ^ o[r], c[r] = 1549556828 ^ o[r]
77 | return e = i(u.concat(d(t)), 512 + 8 * t.length), a(i(c.concat(e), 640))
78 | }
79 |
80 | function g(n) {
81 | var t,
82 | r,
83 | e = ''
84 | for (r = 0; r < n.length; r += 1) t = n.charCodeAt(r), e += '0123456789abcdef'.charAt(t >>> 4 & 15) + '0123456789abcdef'.charAt(15 & t)
85 | return e
86 | }
87 |
88 | function v(n) {
89 | return unescape(encodeURIComponent(n))
90 | }
91 |
92 | function m(n) {
93 | return h(v(n))
94 | }
95 |
96 | function p(n) {
97 | return g(m(n))
98 | }
99 |
100 | function s(n, t) {
101 | return l(v(n), v(t))
102 | }
103 |
104 | function C(n, t) {
105 | return g(s(n, t))
106 | }
107 |
108 | function A(n, t, r) {
109 | return t ? r ? s(t, n) : C(t, n) : r ? m(n) : p(n)
110 | }
111 |
112 | return A
113 |
114 | }
115 | export default md5()
116 |
--------------------------------------------------------------------------------
/src/utils/polyfill.js:
--------------------------------------------------------------------------------
1 | // fix for ie
2 | if (!window.console) {
3 | window.console = {}
4 | }
5 | if (!console.log) {
6 | console.log = function log() {}
7 | }
8 | if (typeof Object.assign !== 'function') {
9 | (function () {
10 | Object.assign = function (target) {
11 | if (target === undefined || target === null) {
12 | throw new TypeError('Cannot convert undefined or null to object')
13 | }
14 |
15 | const output = Object(target)
16 | for (let index = 1; index < arguments.length; index++) {
17 | const source = arguments[index]
18 | if (source !== undefined && source !== null) {
19 | for (const nextKey in source) {
20 | // eslint-disable-next-line no-prototype-builtins
21 | if (source.hasOwnProperty(nextKey)) {
22 | output[nextKey] = source[nextKey]
23 | }
24 | }
25 | }
26 | }
27 | return output
28 | }
29 | }())
30 | }
31 | if (!Array.from) {
32 | Array.from = function (el) {
33 | return Array.apply(this, el)
34 | }
35 | }
36 | // Production steps of ECMA-262, Edition 5, 15.4.4.18
37 | // Reference: http://es5.github.io/#x15.4.4.18
38 | if (!Array.prototype.forEach) {
39 | /* eslint-disable no-extend-native */
40 | Array.prototype.forEach = function forEach(callback) {
41 | let T
42 | let k
43 |
44 | if (this == null) {
45 | throw new TypeError('this is null or not defined')
46 | }
47 |
48 | // 1. Let O be the result of calling toObject() passing the
49 | // |this| value as the argument.
50 | const O = Object(this)
51 |
52 | // 2. Let lenValue be the result of calling the Get() internal
53 | // method of O with the argument "length".
54 | // 3. Let len be toUint32(lenValue).
55 | const len = O.length >>> 0
56 |
57 | // 4. If isCallable(callback) is false, throw a TypeError exception.
58 | // See: http://es5.github.com/#x9.11
59 | if (typeof callback !== 'function') {
60 | throw new TypeError(`${callback} is not a function`)
61 | }
62 |
63 | // 5. If thisArg was supplied, let T be thisArg; else let
64 | // T be undefined.
65 | if (arguments.length > 1) {
66 | /* eslint-disable prefer-destructuring */
67 | T = arguments[1]
68 | }
69 |
70 | // 6. Let k be 0.
71 | k = 0
72 |
73 | // 7. Repeat while k < len.
74 | while (k < len) {
75 | let kValue
76 |
77 | // a. Let Pk be ToString(k).
78 | // This is implicit for LHS operands of the in operator.
79 | // b. Let kPresent be the result of calling the HasProperty
80 | // internal method of O with argument Pk.
81 | // This step can be combined with c.
82 | // c. If kPresent is true, then
83 | if (k in O) {
84 | // i. Let kValue be the result of calling the Get internal
85 | // method of O with argument Pk.
86 | kValue = O[k]
87 |
88 | // ii. Call the Call internal method of callback with T as
89 | // the this value and argument list containing kValue, k, and O.
90 | callback.call(T, kValue, k, O)
91 | }
92 | // d. Increase k by 1.
93 | k++
94 | }
95 | // 8. return undefined.
96 | }
97 | }
98 |
99 | // Production steps of ECMA-262, Edition 5, 15.4.4.19
100 | // Reference: http://es5.github.io/#x15.4.4.19
101 | if (!Array.prototype.map) {
102 | Array.prototype.map = function map(callback/* , thisArg */) {
103 | let T
104 | let k
105 |
106 | if (this == null) {
107 | throw new TypeError('this is null or not defined')
108 | }
109 |
110 | const O = Object(this)
111 |
112 | const len = O.length >>> 0
113 |
114 | if (typeof callback !== 'function') {
115 | throw new TypeError(`${callback} is not a function`)
116 | }
117 |
118 | if (arguments.length > 1) {
119 | T = arguments[1]
120 | }
121 |
122 | const A = new Array(len)
123 |
124 | k = 0
125 |
126 | while (k < len) {
127 | let kValue
128 | let mappedValue
129 |
130 | if (k in O) {
131 | kValue = O[k]
132 |
133 | mappedValue = callback.call(T, kValue, k, O)
134 |
135 | A[k] = mappedValue
136 | }
137 | k++
138 | }
139 |
140 | return A
141 | }
142 | }
143 |
144 | if (!Array.prototype.indexOf) {
145 | Array.prototype.indexOf = function indexOf(member, startFrom) {
146 | /*
147 | In non-strict mode, if the `this` variable is null or undefined, then it is
148 | set to the window object. Otherwise, `this` is automatically converted to an
149 | object. In strict mode, if the `this` variable is null or undefined, a
150 | `TypeError` is thrown.
151 | */
152 | if (this == null) {
153 | throw new TypeError(`Array.prototype.indexOf() - can't convert \`${this}\` to object`)
154 | }
155 |
156 | /* eslint-disable no-restricted-globals, no-new-object */
157 | let index = isFinite(startFrom) ? Math.floor(startFrom) : 0
158 | const that = this instanceof Object ? this : new Object(this)
159 | const length = isFinite(that.length) ? Math.floor(that.length) : 0
160 |
161 | if (index >= length) {
162 | return -1
163 | }
164 |
165 | if (index < 0) {
166 | index = Math.max(length + index, 0)
167 | }
168 |
169 | if (member === undefined) {
170 | /*
171 | Since `member` is undefined, keys that don't exist will have the same
172 | value as `member`, and thus do need to be checked.
173 | */
174 | do {
175 | if (index in that && that[index] === undefined) {
176 | return index
177 | }
178 | } while (++index < length)
179 | } else {
180 | do {
181 | if (that[index] === member) {
182 | return index
183 | }
184 | } while (++index < length)
185 | }
186 |
187 | return -1
188 | }
189 | }
190 |
191 | // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
192 | if (!Object.keys) {
193 | Object.keys = (function keys() {
194 | const hasOwnProperty = Object.prototype.hasOwnProperty
195 | /* eslint-disable no-prototype-builtins */
196 | const hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString')
197 | const dontEnums = [
198 | 'toString',
199 | 'toLocaleString',
200 | 'valueOf',
201 | 'hasOwnProperty',
202 | 'isPrototypeOf',
203 | 'propertyIsEnumerable',
204 | 'constructor',
205 | ]
206 | const dontEnumsLength = dontEnums.length
207 |
208 | return function cb(obj) {
209 | if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
210 | throw new TypeError('Object.keys called on non-object')
211 | }
212 |
213 | const result = []
214 | let prop
215 | let i
216 |
217 | for (prop in obj) {
218 | if (hasOwnProperty.call(obj, prop)) {
219 | result.push(prop)
220 | }
221 | }
222 |
223 | if (hasDontEnumBug) {
224 | for (i = 0; i < dontEnumsLength; i++) {
225 | if (hasOwnProperty.call(obj, dontEnums[i])) {
226 | result.push(dontEnums[i])
227 | }
228 | }
229 | }
230 | return result
231 | }
232 | }())
233 | }
234 |
--------------------------------------------------------------------------------
/static/image/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trustdecision/trustdevice-js/17a8557d94edb5a2a36378262245b81ac270b057/static/image/logo_dark.png
--------------------------------------------------------------------------------
/static/image/logo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trustdecision/trustdevice-js/17a8557d94edb5a2a36378262245b81ac270b057/static/image/logo_light.png
--------------------------------------------------------------------------------
/static/image/trustdevice_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trustdecision/trustdevice-js/17a8557d94edb5a2a36378262245b81ac270b057/static/image/trustdevice_card.png
--------------------------------------------------------------------------------