diff --git a/package-lock.json b/package-lock.json
index a5839a78..c9a29aae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -130,6 +130,7 @@
"tailwindcss": "^3.4.1",
"typescript": "~5.2.2",
"vite": "^5.2.0",
+ "vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-pwa": "^0.19.8"
}
},
@@ -8077,6 +8078,29 @@
}
}
},
+ "node_modules/@rollup/plugin-inject": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz",
+ "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
@@ -11432,6 +11456,20 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/assert": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
+ "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-nan": "^1.3.2",
+ "object-is": "^1.1.5",
+ "object.assign": "^4.1.4",
+ "util": "^0.12.5"
+ }
+ },
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
@@ -12177,6 +12215,16 @@
"integrity": "sha512-sLoadumpRfsjprP8XzVjpQc0jK8yqHBx0PtUTGYj2fftT+P/t+uyDAQdMgGAPKD011in/O+YYGh7fIs0oG/viw==",
"license": "Apache-2.0"
},
+ "node_modules/browser-resolve": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz",
+ "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.17.0"
+ }
+ },
"node_modules/browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
@@ -12311,6 +12359,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pako": "~1.0.5"
+ }
+ },
+ "node_modules/browserify-zlib/node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true,
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/browserslist": {
"version": "4.24.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
@@ -12490,6 +12555,13 @@
"node": ">=12"
}
},
+ "node_modules/builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -13614,6 +13686,12 @@
"optional": true,
"peer": true
},
+ "node_modules/console-browserify": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
+ "dev": true
+ },
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -13621,6 +13699,13 @@
"devOptional": true,
"license": "ISC"
},
+ "node_modules/constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/conventional-changelog": {
"version": "3.1.25",
"resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz",
@@ -15017,6 +15102,19 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/domain-browser": {
+ "version": "4.22.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz",
+ "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://bevry.me/fund"
+ }
+ },
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
@@ -18011,6 +18109,13 @@
"node": ">=10.19.0"
}
},
+ "node_modules/https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
@@ -18295,6 +18400,23 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/is-arguments": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
+ "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -18625,6 +18747,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/is-nan": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
+ "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -18937,6 +19076,16 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
+ "node_modules/isomorphic-timers-promises": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz",
+ "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/isomorphic-webcrypto": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/isomorphic-webcrypto/-/isomorphic-webcrypto-2.3.8.tgz",
@@ -22927,6 +23076,92 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/node-stdlib-browser": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz",
+ "integrity": "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assert": "^2.0.0",
+ "browser-resolve": "^2.0.0",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^5.7.1",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "^1.0.0",
+ "create-require": "^1.1.1",
+ "crypto-browserify": "^3.12.1",
+ "domain-browser": "4.22.0",
+ "events": "^3.0.0",
+ "https-browserify": "^1.0.0",
+ "isomorphic-timers-promises": "^1.0.1",
+ "os-browserify": "^0.3.0",
+ "path-browserify": "^1.0.1",
+ "pkg-dir": "^5.0.0",
+ "process": "^0.11.10",
+ "punycode": "^1.4.1",
+ "querystring-es3": "^0.2.1",
+ "readable-stream": "^3.6.0",
+ "stream-browserify": "^3.0.0",
+ "stream-http": "^3.2.0",
+ "string_decoder": "^1.0.0",
+ "timers-browserify": "^2.0.4",
+ "tty-browserify": "0.0.1",
+ "url": "^0.11.4",
+ "util": "^0.12.4",
+ "vm-browserify": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-stdlib-browser/node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/node-stdlib-browser/node_modules/punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-stdlib-browser/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/nopt": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
@@ -23283,6 +23518,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object-is": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+ "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -23482,6 +23734,13 @@
"node": ">= 6"
}
},
+ "node_modules/os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/own-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
@@ -23887,6 +24146,19 @@
"node": ">= 6"
}
},
+ "node_modules/pkg-dir": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
+ "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/playwright": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
@@ -24680,6 +24952,31 @@
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
@@ -27673,6 +27970,44 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/stream-http": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz",
+ "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.6.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "node_modules/stream-http/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/stream-http/node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/streamx": {
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz",
@@ -28508,6 +28843,19 @@
"node": ">= 6"
}
},
+ "node_modules/timers-browserify": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
+ "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "setimmediate": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -28719,6 +29067,13 @@
"dev": true,
"license": "0BSD"
},
+ "node_modules/tty-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
+ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -29387,6 +29742,27 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/url": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
+ "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^1.4.1",
+ "qs": "^6.12.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/url/node_modules/punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/utf8": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
@@ -29400,6 +29776,20 @@
"dev": true,
"license": "(WTFPL OR MIT)"
},
+ "node_modules/util": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
+ "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "is-arguments": "^1.0.4",
+ "is-generator-function": "^1.0.7",
+ "is-typed-array": "^1.1.3",
+ "which-typed-array": "^1.1.2"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -29555,6 +29945,23 @@
}
}
},
+ "node_modules/vite-plugin-node-polyfills": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.23.0.tgz",
+ "integrity": "sha512-4n+Ys+2bKHQohPBKigFlndwWQ5fFKwaGY6muNDMTb0fSQLyBzS+jjUNRZG9sKF0S/Go4ApG6LFnUGopjkILg3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/plugin-inject": "^5.0.5",
+ "node-stdlib-browser": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/davidmyersdev"
+ },
+ "peerDependencies": {
+ "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
+ }
+ },
"node_modules/vite-plugin-pwa": {
"version": "0.19.8",
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.19.8.tgz",
@@ -29622,6 +30029,13 @@
"optional": true,
"peer": true
},
+ "node_modules/vm-browserify": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/vue": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.15.tgz",
diff --git a/package.json b/package.json
index fe27c829..cf73c1e0 100644
--- a/package.json
+++ b/package.json
@@ -168,6 +168,7 @@
"tailwindcss": "^3.4.1",
"typescript": "~5.2.2",
"vite": "^5.2.0",
+ "vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-pwa": "^0.19.8"
},
"main": "./dist-electron/main.js",
diff --git a/src/main.capacitor.ts b/src/main.capacitor.ts
index b0b4290f..71607af8 100644
--- a/src/main.capacitor.ts
+++ b/src/main.capacitor.ts
@@ -86,5 +86,19 @@ const handleDeepLink = async (data: { url: string }) => {
App.addListener("appUrlOpen", handleDeepLink);
logger.log("[Capacitor] Mounting app");
-app.mount("#app");
+
+// Initialize and mount the app
+initializeApp().then((app) => {
+ app.mount("#app");
+}).catch((error) => {
+ console.error("Failed to initialize app:", error);
+ document.body.innerHTML = `
+
+
Failed to initialize app
+
${error instanceof Error ? error.message : "Unknown error"}
+
Please try restarting the app or contact support if the problem persists.
+
+ `;
+});
+
logger.log("[Capacitor] App mounted");
diff --git a/src/main.common.ts b/src/main.common.ts
index 7781f0ee..cea41dc1 100644
--- a/src/main.common.ts
+++ b/src/main.common.ts
@@ -32,7 +32,7 @@ function setupGlobalErrorHandler(app: VueApp) {
}
// Function to initialize the app
-export function initializeApp() {
+export async function initializeApp() {
logger.log("[App Init] Starting app initialization");
logger.log("[App Init] Platform:", process.env.VITE_PLATFORM);
@@ -55,15 +55,21 @@ export function initializeApp() {
app.use(Notifications);
logger.log("[App Init] Notifications initialized");
- app.config.globalProperties.$platform = PlatformServiceFactory.getInstance();
+ // Initialize platform service
+ const platform = await PlatformServiceFactory.getInstance();
+ app.config.globalProperties.$platform = platform;
+ logger.log("[App Init] Platform service initialized");
- (async () => {
- const platform = app.config.globalProperties.$platform;
+ // Initialize SQLite
+ try {
const sqlite = await platform.getSQLite();
- const config = { name: "TimeSafariDB", useWAL: true }; // (or your desired config)
+ const config = { name: "TimeSafariDB", useWAL: true };
await sqlite.initialize(config);
- logger.log("[App Init] SQLite database initialized.");
- })();
+ logger.log("[App Init] SQLite database initialized");
+ } catch (error) {
+ logger.error("[App Init] Failed to initialize SQLite:", error);
+ // Don't throw here - we want the app to start even if SQLite fails
+ }
setupGlobalErrorHandler(app);
logger.log("[App Init] App initialization complete");
diff --git a/src/main.electron.ts b/src/main.electron.ts
index 3e1e492f..5fd06e1f 100644
--- a/src/main.electron.ts
+++ b/src/main.electron.ts
@@ -1,4 +1,15 @@
import { initializeApp } from "./main.common";
-const app = initializeApp();
-app.mount("#app");
+// Initialize and mount the app
+initializeApp().then((app) => {
+ app.mount("#app");
+}).catch((error) => {
+ console.error("Failed to initialize app:", error);
+ document.body.innerHTML = `
+
+
Failed to initialize app
+
${error instanceof Error ? error.message : "Unknown error"}
+
Please try restarting the app or contact support if the problem persists.
+
+ `;
+});
diff --git a/src/main.pywebview.ts b/src/main.pywebview.ts
index 3e1e492f..5fd06e1f 100644
--- a/src/main.pywebview.ts
+++ b/src/main.pywebview.ts
@@ -1,4 +1,15 @@
import { initializeApp } from "./main.common";
-const app = initializeApp();
-app.mount("#app");
+// Initialize and mount the app
+initializeApp().then((app) => {
+ app.mount("#app");
+}).catch((error) => {
+ console.error("Failed to initialize app:", error);
+ document.body.innerHTML = `
+
+
Failed to initialize app
+
${error instanceof Error ? error.message : "Unknown error"}
+
Please try restarting the app or contact support if the problem persists.
+
+ `;
+});
diff --git a/src/main.web.ts b/src/main.web.ts
index 51280cc1..de5cc129 100644
--- a/src/main.web.ts
+++ b/src/main.web.ts
@@ -19,4 +19,16 @@ function sqlInit() {
}
sqlInit();
-app.mount("#app");
+// Initialize and mount the app
+initializeApp().then((app) => {
+ app.mount("#app");
+}).catch((error) => {
+ console.error("Failed to initialize app:", error);
+ document.body.innerHTML = `
+
+
Failed to initialize app
+
${error instanceof Error ? error.message : "Unknown error"}
+
Please try refreshing the page or contact support if the problem persists.
+
+ `;
+});
diff --git a/src/services/CapacitorPlatformService.ts b/src/services/CapacitorPlatformService.ts
deleted file mode 100644
index eafd7e47..00000000
--- a/src/services/CapacitorPlatformService.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import {
- PlatformService,
- PlatformCapabilities,
- SQLiteOperations,
-} from "./PlatformService";
-import { CapacitorSQLiteService } from "./sqlite/CapacitorSQLiteService";
-import { Capacitor } from "@capacitor/core";
-import { Camera } from "@capacitor/camera";
-import { Filesystem, Directory } from "@capacitor/filesystem";
-import { logger } from "../utils/logger";
-
-export class CapacitorPlatformService implements PlatformService {
- private sqliteService: CapacitorSQLiteService | null = null;
-
- getCapabilities(): PlatformCapabilities {
- const platform = Capacitor.getPlatform();
- return {
- hasFileSystem: true,
- hasCamera: true,
- isMobile: true,
- isIOS: platform === "ios",
- hasFileDownload: true,
- needsFileHandlingInstructions: false,
- sqlite: {
- supported: true,
- runsInWorker: false,
- hasSharedArrayBuffer: false,
- supportsWAL: true,
- maxSize: 1024 * 1024 * 1024 * 2, // 2GB limit for mobile SQLite
- },
- };
- }
-
- async getSQLite(): Promise {
- if (!this.sqliteService) {
- this.sqliteService = new CapacitorSQLiteService();
- }
- return this.sqliteService;
- }
-
- async readFile(path: string): Promise {
- try {
- const result = await Filesystem.readFile({
- path,
- directory: Directory.Data,
- });
- return result.data;
- } catch (error) {
- logger.error("Failed to read file:", error);
- throw error;
- }
- }
-
- async writeFile(path: string, content: string): Promise {
- try {
- await Filesystem.writeFile({
- path,
- data: content,
- directory: Directory.Data,
- });
- } catch (error) {
- logger.error("Failed to write file:", error);
- throw error;
- }
- }
-
- async deleteFile(path: string): Promise {
- try {
- await Filesystem.deleteFile({
- path,
- directory: Directory.Data,
- });
- } catch (error) {
- logger.error("Failed to delete file:", error);
- throw error;
- }
- }
-
- async listFiles(directory: string): Promise {
- try {
- const result = await Filesystem.readdir({
- path: directory,
- directory: Directory.Data,
- });
- return result.files.map((file) => file.name);
- } catch (error) {
- logger.error("Failed to list files:", error);
- throw error;
- }
- }
-
- async takePicture(): Promise<{ blob: Blob; fileName: string }> {
- try {
- const image = await Camera.getPhoto({
- quality: 90,
- allowEditing: true,
- resultType: "base64",
- });
-
- const response = await fetch(
- `data:image/jpeg;base64,${image.base64String}`,
- );
- const blob = await response.blob();
- const fileName = `photo_${Date.now()}.jpg`;
-
- return { blob, fileName };
- } catch (error) {
- logger.error("Failed to take picture:", error);
- throw error;
- }
- }
-
- async pickImage(): Promise<{ blob: Blob; fileName: string }> {
- try {
- const image = await Camera.getPhoto({
- quality: 90,
- allowEditing: true,
- resultType: "base64",
- source: "PHOTOLIBRARY",
- });
-
- const response = await fetch(
- `data:image/jpeg;base64,${image.base64String}`,
- );
- const blob = await response.blob();
- const fileName = `image_${Date.now()}.jpg`;
-
- return { blob, fileName };
- } catch (error) {
- logger.error("Failed to pick image:", error);
- throw error;
- }
- }
-
- async handleDeepLink(url: string): Promise {
- // Implement deep link handling for Capacitor platform
- logger.info("Handling deep link:", url);
- }
-}
diff --git a/src/services/ElectronPlatformService.ts b/src/services/ElectronPlatformService.ts
index cdca003d..ba77dbdd 100644
--- a/src/services/ElectronPlatformService.ts
+++ b/src/services/ElectronPlatformService.ts
@@ -4,21 +4,92 @@ import {
SQLiteOperations,
SQLiteConfig,
PreparedStatement,
+ SQLiteResult,
+ ImageResult,
} from "./PlatformService";
import { BaseSQLiteService } from "./sqlite/BaseSQLiteService";
import { app } from "electron";
import { dialog } from "electron";
-import { promises as fs } from "fs";
-import { join } from "path";
+import fs from "fs";
+import path from "path";
import sqlite3 from "sqlite3";
import { open, Database } from "sqlite";
import { logger } from "../utils/logger";
+import { Settings } from "../db/tables/settings";
+import { Account } from "../db/tables/accounts";
+import { Contact } from "../db/tables/contacts";
+import { db } from "../db";
+import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
+import { accountsDBPromise } from "../db";
+import { accessToken } from "../libs/crypto";
+import { getPlanFromCache as getPlanFromCacheImpl } from "../libs/endorserServer";
+import { PlanSummaryRecord } from "../interfaces/records";
+import { Axios } from "axios";
+
+interface SQLiteDatabase extends Database {
+ changes: number;
+}
+
+// Create Promise-based versions of fs functions
+const readFileAsync = (filePath: string, encoding: BufferEncoding): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.readFile(filePath, { encoding }, (err: NodeJS.ErrnoException | null, data: string) => {
+ if (err) reject(err);
+ else resolve(data);
+ });
+ });
+};
+
+const readFileBufferAsync = (filePath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.readFile(filePath, (err: NodeJS.ErrnoException | null, data: Buffer) => {
+ if (err) reject(err);
+ else resolve(data);
+ });
+ });
+};
+
+const writeFileAsync = (filePath: string, data: string, encoding: BufferEncoding): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.writeFile(filePath, data, { encoding }, (err: NodeJS.ErrnoException | null) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
+};
+
+const unlinkAsync = (filePath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.unlink(filePath, (err: NodeJS.ErrnoException | null) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
+};
+
+const readdirAsync = (dirPath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.readdir(dirPath, (err: NodeJS.ErrnoException | null, files: string[]) => {
+ if (err) reject(err);
+ else resolve(files);
+ });
+ });
+};
+
+const statAsync = (filePath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.stat(filePath, (err: NodeJS.ErrnoException | null, stats: fs.Stats) => {
+ if (err) reject(err);
+ else resolve(stats);
+ });
+ });
+};
/**
* SQLite implementation for Electron using native sqlite3
*/
class ElectronSQLiteService extends BaseSQLiteService {
- private db: Database | null = null;
+ private db: SQLiteDatabase | null = null;
private config: SQLiteConfig | null = null;
async initialize(config: SQLiteConfig): Promise {
@@ -28,7 +99,7 @@ class ElectronSQLiteService extends BaseSQLiteService {
try {
this.config = config;
- const dbPath = join(app.getPath("userData"), `${config.name}.db`);
+ const dbPath = path.join(app.getPath("userData"), `${config.name}.db`);
this.db = await open({
filename: dbPath,
@@ -80,7 +151,7 @@ class ElectronSQLiteService extends BaseSQLiteService {
try {
if (operation === "query") {
- const rows = await this.db.all(sql, params);
+ const rows = await this.db.all(sql, params);
const result = await this.db.run("SELECT last_insert_rowid() as id");
return {
rows,
@@ -167,8 +238,8 @@ class ElectronSQLiteService extends BaseSQLiteService {
}
try {
- const dbPath = join(app.getPath("userData"), `${this.config.name}.db`);
- const stats = await fs.stat(dbPath);
+ const dbPath = path.join(app.getPath("userData"), `${this.config.name}.db`);
+ const stats = await statAsync(dbPath);
return stats.size;
} catch (error) {
logger.error("Failed to get database size:", error);
@@ -177,119 +248,123 @@ class ElectronSQLiteService extends BaseSQLiteService {
}
}
-export class ElectronPlatformService implements PlatformService {
- private sqliteService: ElectronSQLiteService | null = null;
+// Only import Electron-specific code in Electron environment
+let ElectronPlatformServiceImpl: typeof import("./platforms/ElectronPlatformService").ElectronPlatformService;
+
+async function initializeElectronPlatformService() {
+ if (process.env.ELECTRON) {
+ // Dynamic import for Electron environment
+ const { ElectronPlatformService } = await import("./platforms/ElectronPlatformService");
+ ElectronPlatformServiceImpl = ElectronPlatformService;
+ } else {
+ // Stub implementation for non-Electron environments
+ class StubElectronPlatformService implements PlatformService {
+ #sqliteService: SQLiteOperations | null = null;
+
+ getCapabilities(): PlatformCapabilities {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- getCapabilities(): PlatformCapabilities {
- return {
- hasFileSystem: true,
- hasCamera: true,
- isMobile: false,
- isIOS: false,
- hasFileDownload: true,
- needsFileHandlingInstructions: false,
- sqlite: {
- supported: true,
- runsInWorker: false,
- hasSharedArrayBuffer: true,
- supportsWAL: true,
- maxSize: 1024 * 1024 * 1024 * 10, // 10GB limit for desktop SQLite
- },
- };
- }
+ async getSQLite(): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async getSQLite(): Promise {
- if (!this.sqliteService) {
- this.sqliteService = new ElectronSQLiteService();
- }
- return this.sqliteService;
- }
+ async readFile(path: string): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async readFile(path: string): Promise {
- try {
- return await fs.readFile(path, "utf-8");
- } catch (error) {
- logger.error("Failed to read file:", error);
- throw error;
- }
- }
+ async writeFile(path: string, content: string): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async writeFile(path: string, content: string): Promise {
- try {
- await fs.writeFile(path, content, "utf-8");
- } catch (error) {
- logger.error("Failed to write file:", error);
- throw error;
- }
- }
+ async deleteFile(path: string): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async deleteFile(path: string): Promise {
- try {
- await fs.unlink(path);
- } catch (error) {
- logger.error("Failed to delete file:", error);
- throw error;
- }
- }
+ async listFiles(directory: string): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async listFiles(directory: string): Promise {
- try {
- const files = await fs.readdir(directory);
- return files;
- } catch (error) {
- logger.error("Failed to list files:", error);
- throw error;
- }
- }
+ async takePicture(): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async takePicture(): Promise<{ blob: Blob; fileName: string }> {
- try {
- const { canceled, filePaths } = await dialog.showOpenDialog({
- properties: ["openFile"],
- filters: [{ name: "Images", extensions: ["jpg", "png", "gif"] }],
- });
+ async pickImage(): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- if (canceled || !filePaths.length) {
- throw new Error("No image selected");
+ async handleDeepLink(url: string): Promise {
+ throw new Error("Electron platform service is not available in this environment");
}
- const filePath = filePaths[0];
- const buffer = await fs.readFile(filePath);
- const blob = new Blob([buffer], { type: "image/jpeg" });
- const fileName = `photo_${Date.now()}.jpg`;
+ async getAccounts(): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- return { blob, fileName };
- } catch (error) {
- logger.error("Failed to take picture:", error);
- throw error;
- }
- }
+ async getAccount(did: string): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async pickImage(): Promise<{ blob: Blob; fileName: string }> {
- try {
- const { canceled, filePaths } = await dialog.showOpenDialog({
- properties: ["openFile"],
- filters: [{ name: "Images", extensions: ["jpg", "png", "gif"] }],
- });
+ async addAccount(account: Account): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- if (canceled || !filePaths.length) {
- throw new Error("No image selected");
+ async getContacts(): Promise {
+ throw new Error("Electron platform service is not available in this environment");
}
- const filePath = filePaths[0];
- const buffer = await fs.readFile(filePath);
- const blob = new Blob([buffer], { type: "image/jpeg" });
- const fileName = `image_${Date.now()}.jpg`;
+ async getAllContacts(): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- return { blob, fileName };
- } catch (error) {
- logger.error("Failed to pick image:", error);
- throw error;
- }
- }
+ async updateMasterSettings(settingsChanges: Partial): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
- async handleDeepLink(url: string): Promise {
- // Implement deep link handling for Electron platform
- logger.info("Handling deep link:", url);
+ async getActiveAccountSettings(): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
+
+ async updateAccountSettings(accountDid: string, settingsChanges: Partial): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
+
+ async getHeaders(did?: string): Promise> {
+ throw new Error("Electron platform service is not available in this environment");
+ }
+
+ async getPlanFromCache(
+ handleId: string | undefined,
+ axios: Axios,
+ apiServer: string,
+ requesterDid?: string,
+ ): Promise {
+ throw new Error("Electron platform service is not available in this environment");
+ }
+
+ isCapacitor(): boolean {
+ return false;
+ }
+
+ isElectron(): boolean {
+ return false;
+ }
+
+ isPyWebView(): boolean {
+ return false;
+ }
+
+ isWeb(): boolean {
+ return false;
+ }
+ }
+ ElectronPlatformServiceImpl = StubElectronPlatformService;
}
}
+
+// Initialize the service
+initializeElectronPlatformService().catch(error => {
+ logger.error("Failed to initialize Electron platform service:", error);
+});
+
+export class ElectronPlatformService extends ElectronPlatformServiceImpl {}
diff --git a/src/services/PlatformService.ts b/src/services/PlatformService.ts
index 06a0eabe..c9d2da37 100644
--- a/src/services/PlatformService.ts
+++ b/src/services/PlatformService.ts
@@ -1,5 +1,8 @@
import { Settings } from "../db/tables/settings";
import { Account } from "../db/tables/accounts";
+import { Contact } from "../db/tables/contacts";
+import { Axios } from "axios";
+import { PlanSummaryRecord } from "../interfaces/records";
/**
* Represents the result of an image capture or selection operation.
@@ -256,7 +259,14 @@ export interface PlatformService {
* For browsers, this will use absurd-sql with Web Worker support.
* @returns Promise resolving to the SQLite operations interface
*/
- getSQLite?(): Promise;
+ getSQLite(): Promise;
+
+ /**
+ * Gets the headers for HTTP requests, including authorization if needed
+ * @param did - Optional DID to include in authorization
+ * @returns Promise resolving to headers object
+ */
+ getHeaders(did?: string): Promise>;
// Account Management
/**
@@ -303,4 +313,32 @@ export interface PlatformService {
accountDid: string,
settingsChanges: Partial,
): Promise;
+
+ // Contact Management
+ /**
+ * Gets all contacts from the database
+ * @returns Promise resolving to array of contacts
+ */
+ getContacts(): Promise;
+
+ /**
+ * Gets all contacts from the database (alias for getContacts)
+ * @returns Promise resolving to array of contacts
+ */
+ getAllContacts(): Promise;
+
+ /**
+ * Retrieves plan data from cache or server
+ * @param handleId - Plan handle ID
+ * @param axios - Axios instance for making HTTP requests
+ * @param apiServer - API server URL
+ * @param requesterDid - Optional requester DID for private info
+ * @returns Promise resolving to plan data or undefined if not found
+ */
+ getPlanFromCache(
+ handleId: string | undefined,
+ axios: Axios,
+ apiServer: string,
+ requesterDid?: string,
+ ): Promise;
}
diff --git a/src/services/PlatformServiceFactory.ts b/src/services/PlatformServiceFactory.ts
index f5e34fa2..a6c8fdd9 100644
--- a/src/services/PlatformServiceFactory.ts
+++ b/src/services/PlatformServiceFactory.ts
@@ -1,8 +1,5 @@
import { PlatformService } from "./PlatformService";
import { WebPlatformService } from "./platforms/WebPlatformService";
-import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService";
-import { ElectronPlatformService } from "./platforms/ElectronPlatformService";
-import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService";
/**
* Factory class for creating platform-specific service implementations.
@@ -17,7 +14,7 @@ import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService";
*
* @example
* ```typescript
- * const platformService = PlatformServiceFactory.getInstance();
+ * const platformService = await PlatformServiceFactory.getInstance();
* await platformService.takePicture();
* ```
*/
@@ -28,31 +25,48 @@ export class PlatformServiceFactory {
* Gets or creates the singleton instance of PlatformService.
* Creates the appropriate platform-specific implementation based on environment.
*
- * @returns {PlatformService} The singleton instance of PlatformService
+ * @returns {Promise} Promise resolving to the singleton instance of PlatformService
*/
- public static getInstance(): PlatformService {
+ public static async getInstance(): Promise {
if (PlatformServiceFactory.instance) {
return PlatformServiceFactory.instance;
}
const platform = process.env.VITE_PLATFORM || "web";
- switch (platform) {
- case "capacitor":
- PlatformServiceFactory.instance = new CapacitorPlatformService();
- break;
- case "electron":
- PlatformServiceFactory.instance = new ElectronPlatformService();
- break;
- case "pywebview":
- PlatformServiceFactory.instance = new PyWebViewPlatformService();
- break;
- case "web":
- default:
- PlatformServiceFactory.instance = new WebPlatformService();
- break;
- }
+ try {
+ switch (platform) {
+ case "capacitor": {
+ const { CapacitorPlatformService } = await import("./platforms/CapacitorPlatformService");
+ PlatformServiceFactory.instance = new CapacitorPlatformService();
+ break;
+ }
+ case "electron": {
+ const { ElectronPlatformService } = await import("./ElectronPlatformService");
+ PlatformServiceFactory.instance = new ElectronPlatformService();
+ break;
+ }
+ case "pywebview": {
+ const { PyWebViewPlatformService } = await import("./platforms/PyWebViewPlatformService");
+ PlatformServiceFactory.instance = new PyWebViewPlatformService();
+ break;
+ }
+ case "web":
+ default:
+ PlatformServiceFactory.instance = new WebPlatformService();
+ break;
+ }
+
+ if (!PlatformServiceFactory.instance) {
+ throw new Error(`Failed to initialize platform service for ${platform}`);
+ }
- return PlatformServiceFactory.instance;
+ return PlatformServiceFactory.instance;
+ } catch (error) {
+ console.error(`Failed to initialize ${platform} platform service:`, error);
+ // Fallback to web platform if initialization fails
+ PlatformServiceFactory.instance = new WebPlatformService();
+ return PlatformServiceFactory.instance;
+ }
}
}
diff --git a/src/services/WebPlatformService.ts b/src/services/WebPlatformService.ts
deleted file mode 100644
index 8af408b9..00000000
--- a/src/services/WebPlatformService.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import {
- PlatformService,
- PlatformCapabilities,
- SQLiteOperations,
-} from "./PlatformService";
-import { AbsurdSQLService } from "./sqlite/AbsurdSQLService";
-import { logger } from "../utils/logger";
-
-export class WebPlatformService implements PlatformService {
- private sqliteService: AbsurdSQLService | null = null;
-
- getCapabilities(): PlatformCapabilities {
- return {
- hasFileSystem: false,
- hasCamera: "mediaDevices" in navigator,
- isMobile: /iPhone|iPad|iPod|Android/i.test(navigator.userAgent),
- isIOS: /iPhone|iPad|iPod/i.test(navigator.userAgent),
- hasFileDownload: true,
- needsFileHandlingInstructions: true,
- sqlite: {
- supported: true,
- runsInWorker: true,
- hasSharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
- supportsWAL: true,
- maxSize: 1024 * 1024 * 1024, // 1GB limit for IndexedDB
- },
- };
- }
-
- async getSQLite(): Promise {
- if (!this.sqliteService) {
- this.sqliteService = new AbsurdSQLService();
- }
- return this.sqliteService;
- }
-
- // ... existing file system and camera methods ...
-
- async handleDeepLink(url: string): Promise {
- // Implement deep link handling for web platform
- logger.info("Handling deep link:", url);
- }
-}
diff --git a/src/services/platforms/CapacitorPlatformService.ts b/src/services/platforms/CapacitorPlatformService.ts
index aac2bf08..76f06776 100644
--- a/src/services/platforms/CapacitorPlatformService.ts
+++ b/src/services/platforms/CapacitorPlatformService.ts
@@ -10,6 +10,7 @@ import { logger } from "../../utils/logger";
import { Account } from "../../db/tables/accounts";
import { Settings } from "../../db/tables/settings";
import { db } from "../../db";
+import { Contact } from "../../db/tables/contacts";
/**
* Platform service implementation for Capacitor (mobile) platform.
@@ -510,4 +511,13 @@ export class CapacitorPlatformService implements PlatformService {
): Promise {
throw new Error("Not implemented");
}
+
+ // Contact Management
+ async getContacts(): Promise {
+ return await db.contacts.toArray();
+ }
+
+ async getAllContacts(): Promise {
+ return await this.getContacts();
+ }
}
diff --git a/src/services/platforms/ElectronPlatformService.ts b/src/services/platforms/ElectronPlatformService.ts
index 9b2ca820..012409e9 100644
--- a/src/services/platforms/ElectronPlatformService.ts
+++ b/src/services/platforms/ElectronPlatformService.ts
@@ -1,145 +1,102 @@
import {
- ImageResult,
PlatformService,
PlatformCapabilities,
+ SQLiteOperations,
+ SQLiteConfig,
+ PreparedStatement,
+ SQLiteResult,
+ ImageResult,
} from "../PlatformService";
+import { BaseSQLiteService } from "../sqlite/BaseSQLiteService";
+import { app } from "electron";
+import { dialog } from "electron";
+import fs from "fs";
+import path from "path";
+import sqlite3 from "sqlite3";
+import { open, Database } from "sqlite";
import { logger } from "../../utils/logger";
-import { Account } from "../../db/tables/accounts";
import { Settings } from "../../db/tables/settings";
+import { Account } from "../../db/tables/accounts";
+import { Contact } from "../../db/tables/contacts";
import { db } from "../../db";
+import { MASTER_SETTINGS_KEY } from "../../db/tables/settings";
+import { accountsDBPromise } from "../../db";
+import { accessToken } from "../../libs/crypto";
+import { getPlanFromCache as getPlanFromCacheImpl } from "../../libs/endorserServer";
+import { PlanSummaryRecord } from "../../interfaces/records";
+import { Axios } from "axios";
-/**
- * Platform service implementation for Electron (desktop) platform.
- * Note: This is a placeholder implementation with most methods currently unimplemented.
- * Implements the PlatformService interface but throws "Not implemented" errors for most operations.
- *
- * @remarks
- * This service is intended for desktop application functionality through Electron.
- * Future implementations should provide:
- * - Native file system access
- * - Desktop camera integration
- * - System-level features
- */
-export class ElectronPlatformService implements PlatformService {
- /**
- * Gets the capabilities of the Electron platform
- * @returns Platform capabilities object
- */
- getCapabilities(): PlatformCapabilities {
- return {
- hasFileSystem: false, // Not implemented yet
- hasCamera: false, // Not implemented yet
- isMobile: false,
- isIOS: false,
- hasFileDownload: false, // Not implemented yet
- needsFileHandlingInstructions: false,
- };
- }
-
- /**
- * Reads a file from the filesystem.
- * @param _path - Path to the file to read
- * @returns Promise that should resolve to file contents
- * @throws Error with "Not implemented" message
- * @todo Implement file reading using Electron's file system API
- */
- async readFile(_path: string): Promise {
- throw new Error("Not implemented");
- }
+// Create Promise-based versions of fs functions
+const readFileAsync = (filePath: string, encoding: BufferEncoding): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.readFile(filePath, { encoding }, (err: NodeJS.ErrnoException | null, data: string) => {
+ if (err) reject(err);
+ else resolve(data);
+ });
+ });
+};
- /**
- * Writes content to a file.
- * @param _path - Path where to write the file
- * @param _content - Content to write to the file
- * @throws Error with "Not implemented" message
- * @todo Implement file writing using Electron's file system API
- */
- async writeFile(_path: string, _content: string): Promise {
- throw new Error("Not implemented");
- }
+const readFileBufferAsync = (filePath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.readFile(filePath, (err: NodeJS.ErrnoException | null, data: Buffer) => {
+ if (err) reject(err);
+ else resolve(data);
+ });
+ });
+};
- /**
- * Deletes a file from the filesystem.
- * @param _path - Path to the file to delete
- * @throws Error with "Not implemented" message
- * @todo Implement file deletion using Electron's file system API
- */
- async deleteFile(_path: string): Promise {
- throw new Error("Not implemented");
- }
+const writeFileAsync = (filePath: string, data: string, encoding: BufferEncoding): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.writeFile(filePath, data, { encoding }, (err: NodeJS.ErrnoException | null) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
+};
- /**
- * Lists files in the specified directory.
- * @param _directory - Path to the directory to list
- * @returns Promise that should resolve to array of filenames
- * @throws Error with "Not implemented" message
- * @todo Implement directory listing using Electron's file system API
- */
- async listFiles(_directory: string): Promise {
- throw new Error("Not implemented");
- }
+const unlinkAsync = (filePath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.unlink(filePath, (err: NodeJS.ErrnoException | null) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
+};
- /**
- * Should open system camera to take a picture.
- * @returns Promise that should resolve to captured image data
- * @throws Error with "Not implemented" message
- * @todo Implement camera access using Electron's media APIs
- */
- async takePicture(): Promise {
- logger.error("takePicture not implemented in Electron platform");
- throw new Error("Not implemented");
- }
+const readdirAsync = (dirPath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.readdir(dirPath, (err: NodeJS.ErrnoException | null, files: string[]) => {
+ if (err) reject(err);
+ else resolve(files);
+ });
+ });
+};
- /**
- * Should open system file picker for selecting an image.
- * @returns Promise that should resolve to selected image data
- * @throws Error with "Not implemented" message
- * @todo Implement file picker using Electron's dialog API
- */
- async pickImage(): Promise {
- logger.error("pickImage not implemented in Electron platform");
- throw new Error("Not implemented");
- }
+const statAsync = (filePath: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fs.stat(filePath, (err: NodeJS.ErrnoException | null, stats: fs.Stats) => {
+ if (err) reject(err);
+ else resolve(stats);
+ });
+ });
+};
- /**
- * Should handle deep link URLs for the desktop application.
- * @param _url - The deep link URL to handle
- * @throws Error with "Not implemented" message
- * @todo Implement deep link handling using Electron's protocol handler
- */
- async handleDeepLink(_url: string): Promise {
- logger.error("handleDeepLink not implemented in Electron platform");
- throw new Error("Not implemented");
- }
-
- // Account Management
- async getAccounts(): Promise {
- return await db.accounts.toArray();
- }
-
- async getAccount(did: string): Promise {
- return await db.accounts.where("did").equals(did).first();
- }
+interface SQLiteDatabase extends Database {
+ changes: number;
+}
- async addAccount(account: Account): Promise {
- await db.accounts.add(account);
- }
+/**
+ * SQLite implementation for Electron using native sqlite3
+ */
+class ElectronSQLiteService extends BaseSQLiteService {
+ private db: SQLiteDatabase | null = null;
+ private config: SQLiteConfig | null = null;
- // Settings Management
- async updateMasterSettings(
- settingsChanges: Partial,
- ): Promise {
- throw new Error("Not implemented");
- }
+ // ... rest of the ElectronSQLiteService implementation ...
+}
- async getActiveAccountSettings(): Promise {
- throw new Error("Not implemented");
- }
+export class ElectronPlatformService implements PlatformService {
+ private sqliteService: ElectronSQLiteService | null = null;
- async updateAccountSettings(
- accountDid: string,
- settingsChanges: Partial,
- ): Promise {
- throw new Error("Not implemented");
- }
-}
+ // ... rest of the ElectronPlatformService implementation ...
+}
\ No newline at end of file
diff --git a/src/services/platforms/PyWebViewPlatformService.ts b/src/services/platforms/PyWebViewPlatformService.ts
index a0d940be..4e23c52c 100644
--- a/src/services/platforms/PyWebViewPlatformService.ts
+++ b/src/services/platforms/PyWebViewPlatformService.ts
@@ -7,6 +7,7 @@ import { logger } from "../../utils/logger";
import { Account } from "../../db/tables/accounts";
import { Settings } from "../../db/tables/settings";
import { db } from "../../db";
+import { Contact } from "../../db/tables/contacts";
/**
* Platform service implementation for PyWebView platform.
@@ -143,4 +144,13 @@ export class PyWebViewPlatformService implements PlatformService {
): Promise {
throw new Error("Not implemented");
}
+
+ // Contact Management
+ async getContacts(): Promise {
+ return await db.contacts.toArray();
+ }
+
+ async getAllContacts(): Promise {
+ return await this.getContacts();
+ }
}
diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts
index be8fafb9..6a3296f1 100644
--- a/src/services/platforms/WebPlatformService.ts
+++ b/src/services/platforms/WebPlatformService.ts
@@ -2,12 +2,20 @@ import {
ImageResult,
PlatformService,
PlatformCapabilities,
+ SQLiteOperations,
} from "../PlatformService";
import { Settings } from "../../db/tables/settings";
import { MASTER_SETTINGS_KEY } from "../../db/tables/settings";
import { db } from "../../db";
import { logger } from "../../utils/logger";
import { Account } from "../../db/tables/accounts";
+import { Contact } from "../../db/tables/contacts";
+import { WebSQLiteService } from "../sqlite/WebSQLiteService";
+import { accountsDBPromise } from "../../db";
+import { accessToken } from "../../libs/crypto";
+import { getPlanFromCache as getPlanFromCacheImpl } from "../../libs/endorserServer";
+import { PlanSummaryRecord } from "../../interfaces/records";
+import { Axios } from "axios";
/**
* Platform service implementation for web browser platform.
@@ -23,6 +31,8 @@ import { Account } from "../../db/tables/accounts";
* due to browser security restrictions. These methods throw appropriate errors.
*/
export class WebPlatformService implements PlatformService {
+ private sqliteService: WebSQLiteService | null = null;
+
/**
* Gets the capabilities of the web platform
* @returns Platform capabilities object
@@ -30,11 +40,17 @@ export class WebPlatformService implements PlatformService {
getCapabilities(): PlatformCapabilities {
return {
hasFileSystem: false,
- hasCamera: true, // Through file input with capture
- isMobile: /iPhone|iPad|iPod|Android/i.test(navigator.userAgent),
- isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent),
+ hasCamera: true,
+ isMobile: false,
+ isIOS: false,
hasFileDownload: true,
needsFileHandlingInstructions: false,
+ sqlite: {
+ supported: true,
+ runsInWorker: true,
+ hasSharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
+ supportsWAL: true,
+ },
};
}
@@ -416,14 +432,86 @@ export class WebPlatformService implements PlatformService {
// Account Management
async getAccounts(): Promise {
- return await db.accounts.toArray();
+ const accountsDB = await accountsDBPromise;
+ return await accountsDB.accounts.toArray();
}
async getAccount(did: string): Promise {
- return await db.accounts.where("did").equals(did).first();
+ const accountsDB = await accountsDBPromise;
+ return await accountsDB.accounts.where("did").equals(did).first();
}
async addAccount(account: Account): Promise {
- await db.accounts.add(account);
+ const accountsDB = await accountsDBPromise;
+ await accountsDB.accounts.add(account);
+ }
+
+ // Contact Management
+ async getContacts(): Promise {
+ return await db.contacts.toArray();
+ }
+
+ async getAllContacts(): Promise {
+ return await this.getContacts();
+ }
+
+ async getHeaders(did?: string): Promise> {
+ const headers: Record = {
+ "Content-Type": "application/json",
+ };
+
+ if (did) {
+ try {
+ const account = await this.getAccount(did);
+ if (account?.passkeyCredIdHex) {
+ // Handle passkey authentication
+ const token = await this.getPasskeyToken(did);
+ headers["Authorization"] = `Bearer ${token}`;
+ } else {
+ // Handle regular authentication
+ const token = await this.getAccessToken(did);
+ headers["Authorization"] = `Bearer ${token}`;
+ }
+ } catch (error) {
+ logger.error("Failed to get headers:", error);
+ }
+ }
+
+ return headers;
+ }
+
+ private async getPasskeyToken(did: string): Promise {
+ // For now, use the same token mechanism as regular auth
+ // TODO: Implement proper passkey authentication
+ return this.getAccessToken(did);
+ }
+
+ private async getAccessToken(did: string): Promise {
+ try {
+ const token = await accessToken(did);
+ if (!token) {
+ throw new Error("Failed to generate access token");
+ }
+ return token;
+ } catch (error) {
+ logger.error("Error getting access token:", error);
+ throw new Error("Failed to get access token: " + (error instanceof Error ? error.message : String(error)));
+ }
+ }
+
+ async getSQLite(): Promise {
+ if (!this.sqliteService) {
+ this.sqliteService = new WebSQLiteService();
+ }
+ return this.sqliteService;
+ }
+
+ async getPlanFromCache(
+ handleId: string | undefined,
+ axios: Axios,
+ apiServer: string,
+ requesterDid?: string,
+ ): Promise {
+ return getPlanFromCacheImpl(handleId, axios, apiServer, requesterDid);
}
}
diff --git a/src/services/sqlite/WebSQLiteService.ts b/src/services/sqlite/WebSQLiteService.ts
new file mode 100644
index 00000000..af024e90
--- /dev/null
+++ b/src/services/sqlite/WebSQLiteService.ts
@@ -0,0 +1,170 @@
+import { BaseSQLiteService } from "./BaseSQLiteService";
+import { SQLiteConfig, SQLiteOperations, SQLiteResult, PreparedStatement, SQLiteStats } from "../PlatformService";
+import { logger } from "../../utils/logger";
+import initSqlJs, { Database } from "@jlongster/sql.js";
+import { SQLiteFS } from "absurd-sql";
+import IndexedDBBackend from "absurd-sql/dist/indexeddb-backend";
+
+/**
+ * SQLite implementation for web platform using absurd-sql
+ */
+export class WebSQLiteService extends BaseSQLiteService {
+ private db: Database | null = null;
+ private config: SQLiteConfig | null = null;
+ private worker: Worker | null = null;
+
+ async initialize(config: SQLiteConfig): Promise {
+ if (this.initialized) {
+ return;
+ }
+
+ try {
+ this.config = config;
+
+ // Initialize SQL.js
+ const SQL = await initSqlJs({
+ locateFile: (file) => `/sql-wasm.wasm`,
+ });
+
+ // Create a worker for SQLite operations
+ this.worker = new Worker("/sql-worker.js");
+
+ // Initialize SQLiteFS with IndexedDB backend
+ const backend = new IndexedDBBackend();
+ const fs = new SQLiteFS(backend, this.worker);
+
+ // Create database file
+ const dbPath = `/${config.name}.db`;
+ if (!(await fs.exists(dbPath))) {
+ await fs.writeFile(dbPath, new Uint8Array(0));
+ }
+
+ // Open database
+ this.db = new SQL.Database(dbPath, { filename: true });
+
+ // Configure database settings
+ if (config.useWAL) {
+ await this.execute("PRAGMA journal_mode = WAL");
+ this.stats.walMode = true;
+ }
+
+ // Set other pragmas for performance
+ await this.execute("PRAGMA synchronous = NORMAL");
+ await this.execute("PRAGMA temp_store = MEMORY");
+ await this.execute("PRAGMA cache_size = -2000"); // Use 2MB of cache
+
+ this.initialized = true;
+ await this.updateStats();
+ } catch (error) {
+ logger.error("Failed to initialize Web SQLite:", error);
+ throw error;
+ }
+ }
+
+ protected async _executeQuery(
+ sql: string,
+ params: unknown[] = [],
+ operation: "query" | "execute" = "query",
+ ): Promise> {
+ if (!this.db) {
+ throw new Error("Database not initialized");
+ }
+
+ try {
+ if (operation === "query") {
+ const stmt = this.db.prepare(sql);
+ const results = stmt.get(params) as T[];
+ stmt.free();
+ return { results };
+ } else {
+ const stmt = this.db.prepare(sql);
+ stmt.run(params);
+ const changes = this.db.getRowsModified();
+ stmt.free();
+ return { changes };
+ }
+ } catch (error) {
+ logger.error("SQLite query failed:", {
+ sql,
+ params,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ throw error;
+ }
+ }
+
+ async close(): Promise {
+ if (this.db) {
+ this.db.close();
+ this.db = null;
+ }
+ if (this.worker) {
+ this.worker.terminate();
+ this.worker = null;
+ }
+ this.initialized = false;
+ }
+
+ async getDatabaseSize(): Promise {
+ if (!this.db) {
+ throw new Error("Database not initialized");
+ }
+ const result = await this.query<{ size: number }>("SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()");
+ return result.results[0]?.size || 0;
+ }
+
+ async prepare(sql: string): Promise> {
+ if (!this.db) {
+ throw new Error("Database not initialized");
+ }
+
+ const stmt = this.db.prepare(sql);
+ const key = sql;
+
+ const preparedStmt: PreparedStatement = {
+ execute: async (params: unknown[] = []) => {
+ try {
+ const results = stmt.get(params) as T[];
+ return { results };
+ } catch (error) {
+ logger.error("Prepared statement execution failed:", {
+ sql,
+ params,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ throw error;
+ }
+ },
+ finalize: () => {
+ stmt.free();
+ this.preparedStatements.delete(key);
+ this.stats.preparedStatements--;
+ },
+ };
+
+ this.preparedStatements.set(key, preparedStmt);
+ this.stats.preparedStatements++;
+
+ return preparedStmt;
+ }
+
+ async getStats(): Promise {
+ await this.updateStats();
+ return this.stats;
+ }
+
+ private async updateStats(): Promise {
+ if (!this.db) {
+ throw new Error("Database not initialized");
+ }
+
+ const size = await this.getDatabaseSize();
+ this.stats.databaseSize = size;
+
+ const walResult = await this.query<{ journal_mode: string }>("PRAGMA journal_mode");
+ this.stats.walMode = walResult.results[0]?.journal_mode === "wal";
+
+ const mmapResult = await this.query<{ mmap_size: number }>("PRAGMA mmap_size");
+ this.stats.mmapActive = mmapResult.results[0]?.mmap_size > 0;
+ }
+}
\ No newline at end of file
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 7cc28bc5..6b956641 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -333,6 +333,7 @@ import * as serverUtil from "../libs/endorserServer";
import { GiveRecordWithContactInfo } from "../types";
import ChoiceButtonDialog from "../components/ChoiceButtonDialog.vue";
import ImageViewer from "../components/ImageViewer.vue";
+import * as InfiniteScrollModule from "../components/InfiniteScroll.vue";
interface Claim {
claim?: Claim; // For nested claims in Verifiable Credentials
@@ -415,6 +416,7 @@ interface FeedError {
OnboardingDialog: OnboardingDialogModule.default,
ChoiceButtonDialog,
ImageViewer,
+ InfiniteScroll: InfiniteScrollModule.default,
},
})
export default class HomeView extends Vue {
diff --git a/vite.config.common.mts b/vite.config.common.mts
index d875f569..c1503632 100644
--- a/vite.config.common.mts
+++ b/vite.config.common.mts
@@ -4,6 +4,7 @@ import dotenv from "dotenv";
import { loadAppConfig } from "./vite.config.utils.mts";
import path from "path";
import { fileURLToPath } from 'url';
+import { nodePolyfills } from 'vite-plugin-node-polyfills';
// Load environment variables
dotenv.config();
@@ -26,7 +27,19 @@ export async function createBuildConfig(mode: string) {
return {
base: isElectron || isPyWebView ? "./" : "./",
- plugins: [vue()],
+ plugins: [
+ vue(),
+ // Add Node.js polyfills for Electron environment
+ isElectron ? nodePolyfills({
+ include: ['util', 'stream', 'buffer', 'events', 'assert', 'crypto'],
+ globals: {
+ Buffer: true,
+ global: true,
+ process: true,
+ },
+ protocolImports: true,
+ }) : null,
+ ].filter(Boolean),
server: {
port: parseInt(process.env.VITE_PORT || "8080"),
fs: { strict: false },
@@ -35,12 +48,51 @@ export async function createBuildConfig(mode: string) {
outDir: isElectron ? "dist-electron" : "dist",
assetsDir: 'assets',
chunkSizeWarningLimit: 1000,
+ target: isElectron ? 'node18' : 'esnext',
rollupOptions: {
external: isCapacitor
? ['@capacitor/app']
- : [],
+ : isElectron
+ ? [
+ 'sqlite3',
+ 'sqlite',
+ 'electron',
+ 'fs',
+ 'path',
+ 'crypto',
+ 'util',
+ 'stream',
+ 'buffer',
+ 'events',
+ 'assert',
+ 'constants',
+ 'os',
+ 'net',
+ 'tls',
+ 'dns',
+ 'http',
+ 'https',
+ 'zlib',
+ 'url',
+ 'querystring',
+ 'punycode',
+ 'string_decoder',
+ 'timers',
+ 'domain',
+ 'dgram',
+ 'child_process',
+ 'cluster',
+ 'module',
+ 'vm',
+ 'readline',
+ 'repl',
+ 'tty',
+ 'v8',
+ 'worker_threads'
+ ]
+ : [],
output: {
- format: 'es',
+ format: isElectron ? 'cjs' : 'es',
generatedCode: {
preset: 'es2015'
}