diff --git a/package-lock.json b/package-lock.json index 2241a15..5632a0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.10.1", "@tailwindcss/vite": "^4.1.16", + "active-win": "^7.2.0", "axios": "^1.13.2", "chalk": "^5.6.2", "electron-store": "^11.0.2", @@ -22,11 +23,13 @@ "react-router-dom": "^7.9.5", "recharts": "^2.12.7", "sharp": "^0.34.4", - "tailwindcss": "^4.1.16" + "socket.io-client": "^4.8.1", + "tailwindcss": "^4.1.16", + "windowmanager": "^0.14.8" }, "devDependencies": { "@playwright/test": "^1.47.2", - "@types/node": "^22.5.5", + "@types/node": "^22.19.0", "@types/os-utils": "^0.0.4", "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", @@ -2300,6 +2303,12 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -2758,14 +2767,22 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.5.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", - "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "version": "22.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", + "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", "devOptional": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/os-utils": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@types/os-utils/-/os-utils-0.0.4.tgz", @@ -2987,6 +3004,24 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "devOptional": true }, + "node_modules/active-win": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/active-win/-/active-win-7.2.0.tgz", + "integrity": "sha512-36fAH3MZinQ4dHQVa607P2woABkhgVxvSLBVGI7eK/M4Tx8LS+Gc+5fyx78MkAlM+RGV7wV9YnIx0lEuHERVZA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "optionalDependencies": { + "ffi-napi": "^4.0.3", + "ref-napi": "^3.0.2", + "ref-struct-napi": "^1.1.1", + "ref-wchar-napi": "^1.0.3" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -4526,7 +4561,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -5257,6 +5291,28 @@ "once": "^1.4.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -5572,6 +5628,32 @@ "pend": "~1.2.0" } }, + "node_modules/ffi-napi": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ffi-napi/-/ffi-napi-4.0.3.tgz", + "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "get-uv-event-loop-napi-h": "^1.0.5", + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.1", + "ref-napi": "^2.0.1 || ^3.0.2", + "ref-struct-di": "^1.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ffi-napi/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "license": "MIT", + "optional": true + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -5869,6 +5951,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-symbol-from-current-process-h": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz", + "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", + "license": "MIT", + "optional": true + }, + "node_modules/get-uv-event-loop-napi-h": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz", + "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", + "license": "MIT", + "optional": true, + "dependencies": { + "get-symbol-from-current-process-h": "^1.0.1" + } + }, "node_modules/get-windows": { "version": "9.2.3", "resolved": "https://registry.npmjs.org/get-windows/-/get-windows-9.2.3.tgz", @@ -6528,6 +6627,21 @@ "ms": "^2.0.0" } }, + "node_modules/iconv": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/iconv/-/iconv-2.3.5.tgz", + "integrity": "sha512-U5ajDbtDfadp7pvUMC0F2XbkP5vQn9Xrwa6UptePl+cK8EILxapAt3sXers9B3Gxagk+zVjL2ELKuzQvyqOwug==", + "hasInstallScript": true, + "license": "ISC", + "optional": true, + "dependencies": { + "nan": "^2.14.0", + "safer-buffer": "^2.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -7981,8 +8095,14 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nan": { + "version": "2.23.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.1.tgz", + "integrity": "sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==", + "license": "MIT", + "optional": true }, "node_modules/nanoid": { "version": "3.3.7", @@ -8114,6 +8234,18 @@ "node": "^12.13 || ^14.13 || >=16" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -9134,6 +9266,129 @@ "redux": "^5.0.0" } }, + "node_modules/ref-napi": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz", + "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "get-symbol-from-current-process-h": "^1.0.2", + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.1" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/ref-napi/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "license": "MIT", + "optional": true + }, + "node_modules/ref-struct-di": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ref-struct-di/-/ref-struct-di-1.1.1.tgz", + "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^3.1.0" + } + }, + "node_modules/ref-struct-di/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/ref-struct-napi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ref-struct-napi/-/ref-struct-napi-1.1.1.tgz", + "integrity": "sha512-YgS5/d7+kT5zgtySYI5ieH0hREdv+DabgDvoczxsui0f9VLm0rrDcWEj4DHKehsH+tJnVMsLwuyctWgvdEcVRw==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "2", + "ref-napi": "^1.4.2" + } + }, + "node_modules/ref-struct-napi/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/ref-struct-napi/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "optional": true + }, + "node_modules/ref-struct-napi/node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "license": "MIT", + "optional": true + }, + "node_modules/ref-struct-napi/node_modules/ref-napi": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-1.5.2.tgz", + "integrity": "sha512-hwyNmWpUkt1bDWDW4aiwCoC+SJfJO69UIdjqssNqdaS0sYJpgqzosGg/rLtk69UoQ8drZdI9yyQefM7eEMM3Gw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^3.1.0", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.1" + }, + "engines": { + "node": ">= 6.0" + } + }, + "node_modules/ref-struct-napi/node_modules/ref-napi/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/ref-struct-napi/node_modules/ref-napi/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/ref-wchar-napi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ref-wchar-napi/-/ref-wchar-napi-1.0.3.tgz", + "integrity": "sha512-rx8b0pVfz/jShQPS288Rox/A0DQkcw6La+4YC3FgV4F9KUQ/VTxFrVpKOKGuj5yBvVoHdejbX6/BlHSkKHj9Lg==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv": "2", + "ref-napi": "^3.0.1" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -9388,7 +9643,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "node_modules/sanitize-filename": { "version": "1.6.3", @@ -9688,6 +9943,34 @@ "npm": ">= 3.0.0" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -10354,7 +10637,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "devOptional": true + "dev": true }, "node_modules/unique-filename": { "version": "2.0.1", @@ -10746,6 +11029,12 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/windowmanager": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/windowmanager/-/windowmanager-0.14.8.tgz", + "integrity": "sha512-USYABhvy9hce52h9p6h+8LSGhA0ypm/wuydnUm0nAaukPnWyfGByZs+eJAzfE4udVLDykIdZX3YzMk/lTHkgKw==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -10853,6 +11142,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "devOptional": true }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -10862,6 +11172,14 @@ "node": ">=8.0" } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 885d119..ce936bb 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.10.1", "@tailwindcss/vite": "^4.1.16", + "active-win": "^7.2.0", "axios": "^1.13.2", "chalk": "^5.6.2", "electron-store": "^11.0.2", @@ -32,11 +33,13 @@ "react-router-dom": "^7.9.5", "recharts": "^2.12.7", "sharp": "^0.34.4", - "tailwindcss": "^4.1.16" + "socket.io-client": "^4.8.1", + "tailwindcss": "^4.1.16", + "windowmanager": "^0.14.8" }, "devDependencies": { "@playwright/test": "^1.47.2", - "@types/node": "^22.5.5", + "@types/node": "^22.19.0", "@types/os-utils": "^0.0.4", "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 14ef2b8..33f0d6e 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -1,10 +1,8 @@ import { ipcMain, BrowserWindow } from "electron"; import { getToken, saveToken, clearToken } from "../utils/tokenHelper.js"; -import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling } from "../utils/utilityFunctions.js"; +import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling, getRunningAppsList, captureSpecificApp } from "../utils/utilityFunctions.js"; import api from "../api/axiosClient.js"; import { getMainWindow } from '../main.js'; -import { exec } from 'child_process'; - export const registerIPCHandlers = (mainWindow: BrowserWindow) => { console.log("⚙️ Registering IPC Handlers..."); @@ -53,18 +51,25 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => { const mainWindow = getMainWindow() console.log("toggle-close-btn Called", mainWindow) if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.setClosable(shouldEnable); + mainWindow.setClosable(shouldEnable); } -}); + }); -ipcMain.handle('resize-window', (event, width: number, height: number) => { + ipcMain.handle('resize-window', (event, width: number, height: number) => { const mainWindow = getMainWindow() console.log("toggle-close-btn Called", mainWindow) // if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.setSize(width, height); + mainWindow.setSize(width, height); // } }) - // ✅ Start background polling when main window is ready }; -// startWindowPolling(mainWindow); + +ipcMain.handle('get-running-apps', async () => { + return await getRunningAppsList() +}); + +ipcMain.handle('capture-specific-window', async (event, appTitle: string) => { + console.log(appTitle, "IPC HANDLE") + return await captureSpecificApp(appTitle) +}); diff --git a/src/electron/main.ts b/src/electron/main.ts index b05c915..e9d20ab 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -8,6 +8,13 @@ import { registerIPCHandlers } from './ipc/ipcHandler.js'; let mainWindow:any = null; +app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { + // Prevent the default behavior of showing a certificate error dialog + event.preventDefault(); + // Allow the certificate, effectively ignoring the error + callback(true); +}); + app.on('ready', async () => { console.log("ready"); diff --git a/src/electron/preload.cts b/src/electron/preload.cts index ce7e2de..6aebf39 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -10,6 +10,8 @@ contextBridge.exposeInMainWorld('electron', { getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'), toggleCloseBtn: (shouldEnable: boolean) => ipcRenderer.invoke('toggle-close-btn', shouldEnable), resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height), + getRunningApps: () => ipcRenderer.invoke('get-running-apps'), + captureAppScreenShot: (appTitle: string) : Promise => ipcRenderer.invoke('capture-specific-window', appTitle), onWindowUpdate: (callback) => ipcRenderer.on("window:updated", (_, data) => callback(data)), } satisfies Window['electron']); diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index c420837..3ef1b6e 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -5,6 +5,7 @@ import axios from 'axios'; import sharp from 'sharp'; import { saveToken } from "./tokenHelper.js"; import { activeWindow } from "get-windows"; +import { exec } from "child_process"; // Blur using an offscreen canvas async function blurAndCompressBase64Image(base64: string): Promise { @@ -97,21 +98,81 @@ export const handleLogin = async (credentials: { email: string, password: string // Get active window info with timestamp export const getActiveApp = async () => { - const result = await activeWindow(); - const dataWithTime = { ...result, timestamp: new Date().toISOString() }; -// console.log("🪟 Active Window with Time:", dataWithTime); - return dataWithTime; + const result = await activeWindow(); + const dataWithTime = { ...result, timestamp: new Date().toISOString() }; + // console.log("🪟 Active Window with Time:", dataWithTime); + return dataWithTime; }; // Periodically send active window updates export const startWindowPolling = (win: BrowserWindow) => { - console.log("⏱️ Started active window polling (every 5s)"); - setInterval(async () => { + console.log("⏱️ Started active window polling (every 5s)"); + setInterval(async () => { + try { + const app = await getActiveApp(); + if (app) win.webContents.send("window:updated", app); + } catch (err) { + console.error("❌ Polling error:", err); + } + }, 5000); +}; + + +export const getRunningAppsList = async () => { try { - const app = await getActiveApp(); - if (app) win.webContents.send("window:updated", app); - } catch (err) { - console.error("❌ Polling error:", err); + // PowerShell command: Get only apps with visible windows (non-background) + const sources = await desktopCapturer.getSources({ + types: ['window', 'screen'], + thumbnailSize: { width: 150, height: 150 } // Optional: set thumbnail size + }); + + const app = sources.map(source => ({ + name: source.name, + pid: source.id, + // thumbnail: source.thumbnail // Can be used as screenshot + })); + return app + } catch (error) { + return [] } - }, 5000); -}; \ No newline at end of file +} + + +export const captureSpecificApp = async (windowTitle: string) : Promise => { + try { + const sources = await desktopCapturer.getSources({ + types: ['window', 'screen'], + thumbnailSize: { width: 650, height: 430 } + }); + const targetSource = sources.find(source => source.name === windowTitle); + if (targetSource) { + + let image = targetSource.thumbnail.toDataURL(); + const blurredBuffer = await blurAndCompressBase64Image(image); + + // Convert buffer → base64 string + const base64String = `data:image/png;base64,${blurredBuffer.toString('base64')}`; + + // ✅ Return the processed base64 instead of saving + return base64String; + + // const folderPath = "C:\\Users\\Public\\vatracker\\screenshots"; + + // // Ensure directory exists + // if (!fs.existsSync(folderPath)) { + // fs.mkdirSync(folderPath, { recursive: true }); + // } + + // const filePath = path.join(folderPath, `screenshot-${Date.now()}.png`); + // fs.writeFileSync(filePath, blurredBuffer); + + // console.log(`Screenshot saved to ${filePath}`); + } else { + console.log(`Window with title "${windowTitle}" not found.`); + return "Not Found" + } + } catch (error) { + console.error('Error capturing screenshot:', error); + return 'Failed to capture screenshot.'; + } +} \ No newline at end of file diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 242b7d7..563b546 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -1,4 +1,6 @@ import React, { useState, useEffect, useRef } from "react"; +import io, { Socket } from 'socket.io-client'; + import { Outlet, NavLink, @@ -93,6 +95,11 @@ const DashboardLayout: React.FC = ({ setUser }) => { useActiveAppTracker(isTracking, selectedProject?.id, selectedProject?.projectName); + const socketRef = useRef(null); + const intervalRef = useRef(null); + const [isConnected, setIsConnected] = useState(false); + // const [seclectedScreen, setSelectedScreen] = useState(null) + const [projectsData, setProjectsData] = useState([ { id: 1, projectName: "CRM Redesign", clientName: "Salesforce" }, { id: 2, projectName: "AI Assistant", clientName: "OpenAI" }, @@ -116,6 +123,77 @@ const DashboardLayout: React.FC = ({ setUser }) => { } }; + // Connect and start sending + const handleConnect = () => { + if (socketRef.current) { + console.warn("Already connected"); + return; + } + if(!selectedProject?.id) return + + console.log(selectedProject?.id, "SelectedProject ID") + socketRef.current = io("http://localhost:3000", { + transports: ["websocket"], + auth: { + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY5MGIzMjhkMDkxMDFmNzRjZjUyNGMwNyIsImVtYWlsIjoiYmhhZ3dhbi5yQGhpY2dsb2JhbHNvbHV0aW9ucy5jb20iLCJuYW1lIjoiUm9oaXQiLCJpYXQiOjE3NjI4NTgxOTMsImV4cCI6MTc2Mjk0NDU5M30.UlHy3RU9W5LOid7smG7WzyNT7dnqhQT4qqzRrN1axmU", + projectId: selectedProject?.id, + userType: "va" + }, + }); + + socketRef.current.on("connect", () => { + console.log("🟢 Connected:", socketRef.current?.id); + setIsConnected(true); + + // Start sending every 5 seconds + intervalRef.current = setInterval(async () => { + const apps = await window.electron.getRunningApps() + socketRef.current?.emit("getVisibleApps", { token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY5MGIzMjhkMDkxMDFmNzRjZjUyNGMwNyIsImVtYWlsIjoiYmhhZ3dhbi5yQGhpY2dsb2JhbHNvbHV0aW9ucy5jb20iLCJuYW1lIjoiUm9oaXQiLCJpYXQiOjE3NjI4NTgxOTMsImV4cCI6MTc2Mjk0NDU5M30.UlHy3RU9W5LOid7smG7WzyNT7dnqhQT4qqzRrN1axmU", apps }); + + // const base64String = await window.electron.captureAppScreenShot("Screen 1"); + // if (base64String) { + // socketRef.current?.emit("sendImage", {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY5MGIzMjhkMDkxMDFmNzRjZjUyNGMwNyIsImVtYWlsIjoiYmhhZ3dhbi5yQGhpY2dsb2JhbHNvbHV0aW9ucy5jb20iLCJuYW1lIjoiUm9oaXQiLCJpYXQiOjE3NjI4NTgxOTMsImV4cCI6MTc2Mjk0NDU5M30.UlHy3RU9W5LOid7smG7WzyNT7dnqhQT4qqzRrN1axmU", image: base64String, title: "Screen 1"}); + // } + }, 5000); + }); + + socketRef.current.on("appToCapture", async ({ appTitle }) => { + console.log("📸 Capture requested for:", appTitle); + + const base64String = await window.electron.captureAppScreenShot(appTitle); + if (base64String && socketRef.current) { + socketRef.current.emit("sendImage", { + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY5MGIzMjhkMDkxMDFmNzRjZjUyNGMwNyIsImVtYWlsIjoiYmhhZ3dhbi5yQGhpY2dsb2JhbHNvbHV0aW9ucy5jb20iLCJuYW1lIjoiUm9oaXQiLCJpYXQiOjE3NjI4NTgxOTMsImV4cCI6MTc2Mjk0NDU5M30.UlHy3RU9W5LOid7smG7WzyNT7dnqhQT4qqzRrN1axmU", + image: base64String, + title: appTitle + }); + } + }); + + socketRef.current.on("disconnect", () => { + console.log("🔴 Disconnected"); + setIsConnected(false); + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }); + }; + + const handleDisconnect = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + + if (socketRef.current) { + socketRef.current.disconnect(); + socketRef.current = null; + setIsConnected(false); + console.log("🔌 Disconnected manually"); + } + }; + const menuItems = [ { label: "Dashboard", path: "/dashboard", icon: , exact: true }, @@ -154,7 +232,6 @@ const DashboardLayout: React.FC = ({ setUser }) => { const currentSessionTime = Math.floor((now - startTimestamp) / 1000); const totalPausedTime = current.totalPausedTime || 0; - // FIX: Add accumulated time from previous sessions const calculatedElapsed = (current.accumulatedTime || 0) + currentSessionTime - totalPausedTime; @@ -276,7 +353,6 @@ const DashboardLayout: React.FC = ({ setUser }) => { const current = updated.get(selectedProject.id); if (!current) return updated; - // FIX: Store current elapsed time as accumulated time current.accumulatedTime = current.elapsedTime; current.isTracking = false; current.isPaused = false; @@ -359,6 +435,21 @@ const DashboardLayout: React.FC = ({ setUser }) => { {/* Header */}
{/* Left Section */} + {!isConnected ? ( + + ) : ( + + )}
{selectedProject ? ( isTracking ? ( diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx index f3842fa..1c13481 100644 --- a/src/ui/pages/Dashboard.tsx +++ b/src/ui/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react"; import { useOutletContext } from "react-router-dom"; @@ -87,12 +87,33 @@ const Dashboard: React.FC = () => { projectsData, } = useOutletContext(); + // interface AppsListInt { + // name: string; + // pid: string; + // title: string; + // } + + // const [runningApps, setRunningApps] = useState([]); + const selectedData = selectedProject ? projectTrackingMap.get(selectedProject.id) : null; - + // const getRunningApps = async () => { + // const res = await window.electron.getRunningApps() + // const appsList: AppsListInt[] = Array.isArray(res) ? res : []; + // setRunningApps(appsList) + // } + + // useEffect(() => { + // setInterval(() => { + // getRunningApps(); + // }, 5000) + // }, []) + + + return (
@@ -107,6 +128,32 @@ const Dashboard: React.FC = () => { > Screenshot Test + {/* { + runningApps && runningApps.map((obj) => { + return ( +
{ + console.log("Call ", obj.name) + await window.electron.captureAppScreenShot(obj.name) + }} + > + {obj.name} +
+ ) + }) + } */} + + + + + + + + + + +
{/* Widgets */} diff --git a/types.d.ts b/types.d.ts index 60eef43..0697002 100644 --- a/types.d.ts +++ b/types.d.ts @@ -32,6 +32,8 @@ interface Window { getActiveAppDetails: () => {}; toggleCloseBtn: (shouldEnable: boolean) => {}, resizeWindow: (width: number, height: number) => {}, + getRunningApps: () => {}, + captureAppScreenShot: (appTitle: string) => Promise, onWindowUpdate: (callback: (data: any) => void) => void; }; }