├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── dist └── fm.js ├── index.html ├── package.json ├── rollup.config.js ├── src ├── detection │ ├── audioRipple.js │ ├── canvas.js │ ├── cookie.js │ ├── debugger.js │ ├── disabledCookie.js │ ├── document.js │ ├── font.js │ ├── gpu.js │ ├── msMaxTouchPoints.js │ ├── navigator.js │ ├── screen.js │ ├── support.js │ ├── timezone.js │ ├── touchSupport.js │ └── userAgentData.js ├── index.js └── utils │ ├── md5.js │ └── polyfill.js └── static └── image ├── logo_dark.png ├── logo_light.png └── trustdevice_card.png /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["latest", { 4 | "es2015": { 5 | "modules": false 6 | } 7 | }] 8 | ], 9 | "plugins": ["external-helpers"] 10 | } 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /*.js 3 | /src/utils/md5.js 4 | 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint' 5 | }, 6 | env: { 7 | browser: true, 8 | }, 9 | globals: { 10 | _fmOpt: true, 11 | ActiveXObject: true, 12 | safari: true, 13 | // jest global var 14 | test: true, 15 | expect: true 16 | }, 17 | extends: ['airbnb-base'], 18 | // add your custom rules here 19 | rules: { 20 | 'no-unused-expressions': [0], 21 | 'no-bitwise': [0], 22 | 'no-param-reassign': [0], 23 | 'no-restricted-syntax': [0], 24 | 'prefer-rest-params': [0], 25 | 'no-empty': [0], 26 | 'no-caller': [0], 27 | 'no-restricted-properties': [0], 28 | 'class-methods-use-this': [0], 29 | 'no-underscore-dangle': [0], 30 | 'no-plusplus': [0], 31 | 'import/extensions': ['error', 'always', { 32 | js: 'never', 33 | }], 34 | // disallow reassignment of function parameters 35 | // disallow parameter object manipulation except for specific exclusions 36 | 'no-param-reassign': ['error', { 37 | props: false 38 | }], 39 | 'semi': ['error', 'never'], 40 | // allow optionalDependencies 41 | 'import/no-extraneous-dependencies': ['error', { 42 | optionalDependencies: ['test/unit/index.js'] 43 | }], 44 | // allow debugger during development 45 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 46 | } 47 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea 3 | .vscode 4 | .npmignore 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 trustdecision 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | trustdevice logo 7 | 8 | 9 |

10 | 11 | ## TrustDevice-JS 12 | 13 |

14 | 15 | Current NPM version 16 | 17 | 18 | Monthly downloads from NPM 19 | 20 | 21 | Monthly downloads from jsDelivr 22 | 23 |

24 | 25 | A lightweight library for determining device uniqueness and risk identification. 26 | 27 | Create a device identifier based on basic device information. 28 | 29 | Will remain the same after uninstalling and reinstalling or clearing browser data. 30 | 31 | 🪧Attention🪧:The functions of the basic version of github are relatively limited. 32 | 33 | You can visit [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-5fkjqqnkoajcs?sr=0-1&ref_=beagle&applicationId=AWSMPContessa) or [TrustDecision](https://trustdecision.com/solutions/device-fingerprint) to learn about our professional version. 34 | 35 | [Open Source Demo](https://statictest.tongdun.net/trustdevice/index.html) 36 | 37 | ### Browser support 38 | 39 | - Internet Explorer 9+ 40 | - Edge 12+ 41 | - Chrome 33+ 42 | - Firefox 29+ 43 | - Desktop Safari 7.1+ 44 | - Mobile Safari 8+ 45 | - Android Browser 4.4+ 46 | 47 | ## Integration Description 48 | 49 | ```html 50 | 67 | ``` 68 | ## Open Source Features 69 | 70 | + Basic device ID, consistent when uninstalling applications and reinstalling. 71 | + Basic equipment information, which can be used for simple data analysis. 72 | + Basic risk identification ability 73 | 74 | | RiskLabel | Risk Description | 75 | | --------- | --------------------------- | 76 | | disabledCookie | Cookies are set to disable. | 77 | | debugger | The attacker or developer opens the browser developer tool for debugging. | 78 | 79 | ## Open Source VS Pro 80 | | Ability | Open Source | Pro | 81 | | :-------: | :-------: | :-------------------------: | 82 | | 100%open source | yes | no | 83 | | Device ID | Basic | Extremely stable | 84 | | Device Risk Label | Basic | Extremely rich | 85 | | Device Details | Basic | Extremely rich | 86 | | IP Location | - | ✓ | 87 | | Device Risk Score | - | ✓ | 88 | | Environment Risk Evaluation | - | ✓ | 89 | | Fraud Tools Detection | - | ✓| 90 | | Behavioral Activity Capturing | - | ✓ | 91 | 92 | ## Pro Introduction 93 | 94 | TrustDecision TrustDevice has the leading device fingerprint technology, which has been integrated by more than 10000 global leading brands, protecting the entire customer journey. 95 | 96 |

97 | 98 | trustdevice card 99 | 100 |

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 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
TypeSceneResultTrusDevice ProFingerprintSeon
Device Fingerprint CompatibilityIE9 and aboveAble to collect device info and generate device ID❌ (not supported by IE)❌ (IE10 and below are not supported)
Device fingerprint uniquenessAccess web application twice using browser on the same deviceDevice fingerprints/ID matches
Access web application using browser on two different devicesDevice fingerprint/ID should not match. Each device to have its own unique device fingeprint/ID
Device Fingerprint StabilityClears the browser cache and cookiesDevice fingerprints/ID still matches before and after clearing
Incognito and non-incognito mode on the same device browserDevice fingerprints/ID match
The same device browser before and after User-Agent modificationDevice fingerprints/ID match
Before and after browser upgrade on the same deviceDevice fingerprints/ID match
Device Fingerprint Risk IdentificationWeb crawlers (bot attacks)Ability to identify web crawlers (bot attacks)
Incognito modeAbility to recognize that the browser is in incognito mode/private browsing
Headless browser (Chrome Headless、phantomJS、selenium、puppeteer)Able to identify the risk of use of headless browser
Abnormal user-agentAbility to identify UA anomalies
JS is debuggedCan identify debugging behavior on JS
JS is tampered withCan identify tampering
Do not use cookiesAbility to recognize the prohibition of the use of cookies
Browser parameters have been tampered withCan identify browser environment tampering
Common browser cheating plug-insCan identify common cheating plug-in risks
Security and StabilityCode ProtectionThe 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)
DowngradeIntercepting and sending collection requests in the Web environment can still generate device fingerprints normally
Anti packet captureWeb (including iOS and android web) have the ability to prevent packet capture
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 | TrustDecision Logo 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 --------------------------------------------------------------------------------