diff --git a/package.json b/package.json index 3203ace..1bff1d7 100644 --- a/package.json +++ b/package.json @@ -221,6 +221,7 @@ } ], "workspaces": [ + "src/gpu-detect", "src/ua-client-hints", "src/user-agent-helpers" ] diff --git a/script/build-module.js b/script/build-module.js index d3c7935..2c0c458 100755 --- a/script/build-module.js +++ b/script/build-module.js @@ -47,6 +47,12 @@ const modules = [ title : 'ua-parser-js/extensions', replacements : [] }, + { + src : 'src/gpu-detect/gpu-detect.js', + dest : 'src/gpu-detect/gpu-detect.mjs', + title : '@ua-parser-js/gpu-detect', + replacements : [] + }, { src : 'src/user-agent-helpers/user-agent-helpers.js', dest : 'src/user-agent-helpers/user-agent-helpers.mjs', diff --git a/src/gpu-detect/gpu-detect.d.ts b/src/gpu-detect/gpu-detect.d.ts new file mode 100644 index 0000000..bccc81b --- /dev/null +++ b/src/gpu-detect/gpu-detect.d.ts @@ -0,0 +1,3 @@ +export class GPUDetect { + static getGPU: { vendor: string, model: string } +} \ No newline at end of file diff --git a/src/gpu-detect/gpu-detect.js b/src/gpu-detect/gpu-detect.js new file mode 100644 index 0000000..326dbcf --- /dev/null +++ b/src/gpu-detect/gpu-detect.js @@ -0,0 +1,116 @@ +////////////////////////////////////////////// +/* Extracts GPU information from user-agent + https://github.com/faisalman/ua-parser-js + Author: Faisal Salman + MIT License */ +///////////////////////////////////////////// + +/*jshint esversion: 11 */ + +const rendererMap = [ + [[ + /(intel).*\b(hd\sgraphics\s\d{4}|iris(?:\spro)|gma\s\w+)/i, // Intel + /(nvidia)\s(geforce\s(?:gtx?\s)\d\w+|quadro)/i, // NVIDIA + /\b(sis)\s(\w+)/i // SiS + ], ['vendor', 'model']], + + [[ + /\b(radeon[\shdr\d]+\w{4,5})/i // ATI/AMD + ], ['model', ['vendor', 'AMD']]], + + [[ + /(adreno\s(?:\(tm\)\s)\w+)/i // Qualcomm + ], [['model', /\(tm\)\s/i, ''], ['vendor', 'Qualcomm']]] +]; + +const vendorMap = [ + [[ + /\b(amd|apple|arm|ati|img|intel|nvidia|qualcomm|samsung|sis)\b/i + ], ['vendor']] +]; + +class RegexMap { + + static parse(str, mapper) { + let res = {}; + if (typeof str === 'string') { + for (const [regs, props] of mapper) { + if (!Array.isArray(regs)) { + throw new Error('RegexMap: Expect Array of RegExp'); + } + if (!Array.isArray(props)) { + throw new Error('RegexMap: Expect Array for Properties Mapping'); + } + for (const reg of regs) { + if (!reg instanceof RegExp) { + throw new Error('RegexMap: Expect RegExp Instance'); + } + const matches = reg.exec(str); + if (matches) { + props.forEach((prop, idx) => { + const val = matches[idx+1]; + if (Array.isArray(prop)) { + const key = prop[0]; + if (typeof key !== 'string') { + throw new Error('RegexMap: Expect String Input'); + } + if (prop.length == 2) { + if (typeof prop[1] === 'string') { + res[key] = prop[1]; + } else if (typeof prop[1] === 'function') { + res[key] = prop[1].call(res, val); + } + } else if (prop.length == 3) { + if (prop[1] instanceof RegExp) { + res[key] = val.replace(prop[1], prop[2]); + } else if (typeof prop[1] === 'function') { + res[key] = prop[1].call(res, val, prop[2]); + } + } else if (prop.length == 4) { + res[key] = prop[3].call(res, val.replace(prop[1], prop[2])); + } else { + res[key] = val; + } + } else if (typeof prop === 'string') { + res[prop] = val; + } + }); + if (res) return res; + } + } + } + } + return res; + }; +} + +class GPUDetect { + + static getGPU (strRenderer, strVendor) { + + let gpuInfo = { vendor : undefined, model : undefined }; + + if (typeof strRenderer !== 'string') { + if (globalThis.document) { + const canvas = document.createElement('canvas'); + const gl = canvas.getContext('webgl2') || + canvas.getContext('webgl') || + canvas.getContext('experimental-webgl'); + if (gl) { + const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); + strVendor = gl.getParameter(debugInfo?.UNMASKED_VENDOR_WEBGL); + strRenderer = gl.getParameter(debugInfo?.UNMASKED_RENDERER_WEBGL); + } + } + } + if (strRenderer || strVendor) { + ({ vendor : gpuInfo.vendor, model : gpuInfo.model } = RegexMap.parse(strRenderer, rendererMap)); + gpuInfo.vendor = gpuInfo.vendor ?? RegexMap.parse(strVendor, rendererMap)?.vendor ?? RegexMap.parse(strVendor, vendorMap)?.vendor; + } + return gpuInfo; + } +} + +module.exports = { + GPUDetect +} \ No newline at end of file diff --git a/src/gpu-detect/package.json b/src/gpu-detect/package.json new file mode 100644 index 0000000..c3ffaf9 --- /dev/null +++ b/src/gpu-detect/package.json @@ -0,0 +1,25 @@ +{ + "title": "User-Agent GPU Info", + "name": "@ua-parser-js/gpu-detect", + "version": "0.0.1", + "author": "Faisal Salman ", + "description": "Extracts GPU information from user-agent", + "main": "gpu-detect.js", + "module": "gpu-detect.mjs", + "scripts": { + "test": "mocha ../../test/mocha-test-helpers" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/faisalman/ua-parser-js.git" + }, + "keywords": [ + "ua-parser-js", + "gpu-detection" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/faisalman/ua-parser-js/issues" + }, + "homepage": "https://github.com/faisalman/ua-parser-js#readme" +} diff --git a/src/gpu-detect/readme.md b/src/gpu-detect/readme.md new file mode 100644 index 0000000..86b9fa4 --- /dev/null +++ b/src/gpu-detect/readme.md @@ -0,0 +1,16 @@ +# @ua-parser-js/gpu-detect + +This is a [UAParser.js](https://github.com/faisalman/ua-parser-js) module that extracts GPU information from user-agent. + +```sh +npm i @ua-parser-js/gpu-detect +``` + +## Code Example + +// in browser environment +const { vendor, model } = GPUDetect.getGPU(); + +// in non-browser environment +const { vendor, model } = GPUDetect.getGPU("AMD Radeon"); +``` \ No newline at end of file diff --git a/src/gpu-detect/test/index.js b/src/gpu-detect/test/index.js new file mode 100644 index 0000000..76b7e70 --- /dev/null +++ b/src/gpu-detect/test/index.js @@ -0,0 +1,4 @@ +const { GPUDetect } = require('../gpu-detect.js'); + +console.log(GPUDetect.getGPU('AMD Radeon R9 M295X OpenGL Engine')); +console.log(GPUDetect.getGPU('','ATI Technologies Inc.')); \ No newline at end of file