refactor: improve code quality and type safety

- Remove v-html usage in EntityIcon component
- Replace unsafe HTML injection with proper Vue component structure
- Add proper type definitions for claims and credentials
- Fix error handling with TimeSafariError type
- Clean up imports and remove unused variables
- Standardize string quotes and formatting
- Add proper type annotations for function parameters
- Improve error handling in feed processing
- Add proper null checks and type guards
- Fix linting warnings in logger utility
- Standardize code style across components
- Add proper JSDoc comments for type safety
- Improve error message handling and display

This commit focuses on improving code quality by removing unsafe practices,
adding proper type safety, and standardizing code style across the codebase.
This commit is contained in:
Matthew Raymer
2025-04-04 03:47:01 +00:00
parent b8c3517072
commit 6e2bdc69e9
15 changed files with 604 additions and 381 deletions

427
package-lock.json generated
View File

@@ -5217,9 +5217,9 @@
} }
}, },
"node_modules/@expo/cli": { "node_modules/@expo/cli": {
"version": "0.22.23", "version": "0.22.24",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.22.23.tgz", "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.22.24.tgz",
"integrity": "sha512-LXFKu2jnk9ClVD+kw0sJCQ89zei01wz2t4EJwc9P7EwYb8gabC8FtPyM/X7NIE5jtrnTLTUtjW5ovxQSBL7pJQ==", "integrity": "sha512-lhdenxBC8/x/vL39j79eXE09mOaqNNLmiSDdY/PblnI+UNzGgsQ48hBTYa/MQhd0ioXXVKurZL2941dLKwcxJw==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
@@ -5237,12 +5237,12 @@
"@expo/osascript": "^2.1.6", "@expo/osascript": "^2.1.6",
"@expo/package-manager": "^1.7.2", "@expo/package-manager": "^1.7.2",
"@expo/plist": "^0.2.2", "@expo/plist": "^0.2.2",
"@expo/prebuild-config": "^8.0.30", "@expo/prebuild-config": "^8.0.31",
"@expo/rudder-sdk-node": "^1.1.1", "@expo/rudder-sdk-node": "^1.1.1",
"@expo/spawn-async": "^1.7.2", "@expo/spawn-async": "^1.7.2",
"@expo/ws-tunnel": "^1.0.1", "@expo/ws-tunnel": "^1.0.1",
"@expo/xcpretty": "^4.3.0", "@expo/xcpretty": "^4.3.0",
"@react-native/dev-middleware": "0.76.8", "@react-native/dev-middleware": "0.76.9",
"@urql/core": "^5.0.6", "@urql/core": "^5.0.6",
"@urql/exchange-retry": "^1.3.0", "@urql/exchange-retry": "^1.3.0",
"accepts": "^1.3.8", "accepts": "^1.3.8",
@@ -6638,9 +6638,9 @@
} }
}, },
"node_modules/@expo/prebuild-config": { "node_modules/@expo/prebuild-config": {
"version": "8.0.30", "version": "8.0.31",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.30.tgz", "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.31.tgz",
"integrity": "sha512-xNHWGh0xLZjxBXwVbDW+TPeexuQ95FZX2ZRrzJkALxhQiwYQswQSFE7CVUFMC2USIKVklCcgfEvtqnguTBQVxQ==", "integrity": "sha512-YTuS5ic9KolD/WA3GqgLcZytHQU1dpitlZ/cbDq8ZqkY+1ae5YWX+GkYEZf2VyECPaWnHYuDGddaTQVw5miTRg==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
@@ -6650,7 +6650,7 @@
"@expo/config-types": "^52.0.5", "@expo/config-types": "^52.0.5",
"@expo/image-utils": "^0.6.5", "@expo/image-utils": "^0.6.5",
"@expo/json-file": "^9.0.2", "@expo/json-file": "^9.0.2",
"@react-native/normalize-colors": "0.76.8", "@react-native/normalize-colors": "0.76.9",
"debug": "^4.3.1", "debug": "^4.3.1",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"resolve-from": "^5.0.0", "resolve-from": "^5.0.0",
@@ -8046,23 +8046,23 @@
} }
}, },
"node_modules/@react-native/babel-plugin-codegen": { "node_modules/@react-native/babel-plugin-codegen": {
"version": "0.76.8", "version": "0.76.9",
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.76.8.tgz", "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.76.9.tgz",
"integrity": "sha512-84RUEhDZS+q7vPtxKi0iMZLd5/W0VN7NOyqX5f+burV3xMYpUhpF5TDJ2Ysol7dJrvEZHm6ISAriO85++V8YDw==", "integrity": "sha512-vxL/vtDEIYHfWKm5oTaEmwcnNGsua/i9OjIxBDBFiJDu5i5RU3bpmDiXQm/bJxrJNPRp5lW0I0kpGihVhnMAIQ==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@react-native/codegen": "0.76.8" "@react-native/codegen": "0.76.9"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@react-native/babel-preset": { "node_modules/@react-native/babel-preset": {
"version": "0.76.8", "version": "0.76.9",
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.76.8.tgz", "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.76.9.tgz",
"integrity": "sha512-xrP+r3orRzzxtC2TrfGIP6IYi1f4AiWlnSiWf4zxEdMFzKrYdmxhD0FPtAZb77B0DqFIW5AcBFlm4grfL/VgfA==", "integrity": "sha512-TbSeCplCM6WhL3hR2MjC/E1a9cRnMLz7i767T7mP90oWkklEjyPxWl+0GGoVGnJ8FC/jLUupg/HvREKjjif6lw==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
@@ -8108,7 +8108,7 @@
"@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-typescript": "^7.25.2",
"@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/plugin-transform-unicode-regex": "^7.24.7",
"@babel/template": "^7.25.0", "@babel/template": "^7.25.0",
"@react-native/babel-plugin-codegen": "0.76.8", "@react-native/babel-plugin-codegen": "0.76.9",
"babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-syntax-hermes-parser": "^0.25.1",
"babel-plugin-transform-flow-enums": "^0.0.2", "babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0" "react-refresh": "^0.14.0"
@@ -8121,9 +8121,9 @@
} }
}, },
"node_modules/@react-native/codegen": { "node_modules/@react-native/codegen": {
"version": "0.76.8", "version": "0.76.9",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.76.8.tgz", "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.76.9.tgz",
"integrity": "sha512-qvKhcYBkRHJFkeWrYm66kEomQOTVXWiHBkZ8VF9oC/71OJkLszpTpVOuPIyyib6fqhjy9l7mHYGYenSpfYI5Ww==", "integrity": "sha512-AzlCHMTKrAVC2709V4ZGtBXmGVtWTpWm3Ruv5vXcd3/anH4mGucfJ4rjbWKdaYQJMpXa3ytGomQrsIsT/s8kgA==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
@@ -8260,9 +8260,9 @@
} }
}, },
"node_modules/@react-native/debugger-frontend": { "node_modules/@react-native/debugger-frontend": {
"version": "0.76.8", "version": "0.76.9",
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.76.8.tgz", "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.76.9.tgz",
"integrity": "sha512-kSukBw2C++5ENLUCAp/1uEeiFgiHi/MBa71Wgym3UD5qwu2vOSPOTSKRX7q2Jb676MUzTcrIaJBZ/r2qk25u7Q==", "integrity": "sha512-0Ru72Bm066xmxFuOXhhvrryxvb57uI79yDSFf+hxRpktkC98NMuRenlJhslMrbJ6WjCu1vOe/9UjWNYyxXTRTA==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"optional": true, "optional": true,
"peer": true, "peer": true,
@@ -8271,15 +8271,15 @@
} }
}, },
"node_modules/@react-native/dev-middleware": { "node_modules/@react-native/dev-middleware": {
"version": "0.76.8", "version": "0.76.9",
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.76.8.tgz", "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.76.9.tgz",
"integrity": "sha512-KYx7hFME2uYQRCDCqb19ghw51TAdh48PZ5EMpoU2kPA1SKKO9c1bUbpsKRhVZ0bv1QqEX6fjox3c4/WYRozHQA==", "integrity": "sha512-xkd3C3dRcmZLjFTEAOvC14q3apMLouIvJViCZY/p1EfCMrNND31dgE1dYrLTiI045WAWMt5bD15i6f7dE2/QWA==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@isaacs/ttlcache": "^1.4.1", "@isaacs/ttlcache": "^1.4.1",
"@react-native/debugger-frontend": "0.76.8", "@react-native/debugger-frontend": "0.76.9",
"chrome-launcher": "^0.15.2", "chrome-launcher": "^0.15.2",
"chromium-edge-launcher": "^0.2.0", "chromium-edge-launcher": "^0.2.0",
"connect": "^3.6.5", "connect": "^3.6.5",
@@ -8607,9 +8607,9 @@
} }
}, },
"node_modules/@react-native/normalize-colors": { "node_modules/@react-native/normalize-colors": {
"version": "0.76.8", "version": "0.76.9",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.8.tgz", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.9.tgz",
"integrity": "sha512-FRjRvs7RgsXjkbGSOjYSxhX5V70c0IzA/jy3HXeYpATMwD9fOR1DbveLW497QGsVdCa0vThbJUtR8rIzAfpHQA==", "integrity": "sha512-TUdMG2JGk72M9d8DYbubdOlrzTYjw+YMe/xOnLU4viDgWRHsCbtRS9x0IAxRjs3amj/7zmK3Atm8jUPvdAc8qw==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true "peer": true
@@ -9791,9 +9791,9 @@
} }
}, },
"node_modules/@types/babel__generator": { "node_modules/@types/babel__generator": {
"version": "7.6.8", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
@@ -9977,9 +9977,9 @@
} }
}, },
"node_modules/@types/luxon": { "node_modules/@types/luxon": {
"version": "3.6.0", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.0.tgz", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
"integrity": "sha512-RtEj20xRyG7cRp142MkQpV3GRF8Wo2MtDkKLz65MQs7rM1Lh8bz+HtfPXCCJEYpnDFu6VwAq/Iv2Ikyp9Jw/hw==", "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -12383,9 +12383,9 @@
} }
}, },
"node_modules/babel-preset-expo": { "node_modules/babel-preset-expo": {
"version": "12.0.10", "version": "12.0.11",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-12.0.10.tgz", "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-12.0.11.tgz",
"integrity": "sha512-6QE52Bxsp5XRE8t0taKRFTFsmTG0ThQE+PTgCgLY9s8v2Aeh8R+E+riXhSHX6hP+diDmBFBdvLCUTq7kroJb1Q==", "integrity": "sha512-4m6D92nKEieg+7DXa8uSvpr0GjfuRfM/G0t0I/Q5hF8HleEv5ms3z4dJ+p52qXSJsm760tMqLdO93Ywuoi7cCQ==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
@@ -12396,7 +12396,7 @@
"@babel/plugin-transform-parameters": "^7.22.15", "@babel/plugin-transform-parameters": "^7.22.15",
"@babel/preset-react": "^7.22.15", "@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0", "@babel/preset-typescript": "^7.23.0",
"@react-native/babel-preset": "0.76.8", "@react-native/babel-preset": "0.76.9",
"babel-plugin-react-native-web": "~0.19.13", "babel-plugin-react-native-web": "~0.19.13",
"react-refresh": "^0.14.2" "react-refresh": "^0.14.2"
}, },
@@ -12610,9 +12610,9 @@
} }
}, },
"node_modules/bignumber.js": { "node_modules/bignumber.js": {
"version": "9.1.2", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.0.tgz",
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "integrity": "sha512-JocpCSOixzy5XFJi2ub6IMmV/G9i8Lrm2lZvwBv9xPdglmZM0ufDVBbjbrfU/zuLvBfD7Bv2eYxz9i+OHTgkew==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "*" "node": "*"
@@ -12641,54 +12641,42 @@
} }
}, },
"node_modules/bl": { "node_modules/bl": {
"version": "4.1.0", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-0.8.2.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "integrity": "sha512-pfqikmByp+lifZCS0p6j6KreV6kNU6Apzpm2nKOk+94cZb/jvle55+JxWiByUQ0Wo/+XnDXEy5MxxKMb6r0VIw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"buffer": "^5.5.0", "readable-stream": "~1.0.26"
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
} }
}, },
"node_modules/bl/node_modules/buffer": { "node_modules/bl/node_modules/isarray": {
"version": "5.7.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"funding": [ "dev": true,
{ "license": "MIT"
"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/bl/node_modules/readable-stream": { "node_modules/bl/node_modules/readable-stream": {
"version": "3.6.2", "version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"inherits": "^2.0.3", "core-util-is": "~1.0.0",
"string_decoder": "^1.1.1", "inherits": "~2.0.1",
"util-deprecate": "^1.0.1" "isarray": "0.0.1",
}, "string_decoder": "~0.10.x"
"engines": {
"node": ">= 6"
} }
}, },
"node_modules/bl/node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
"dev": true,
"license": "MIT"
},
"node_modules/blakejs": { "node_modules/blakejs": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
@@ -12871,20 +12859,6 @@
"node": ">= 0.12" "node": ">= 0.12"
} }
}, },
"node_modules/browserify-sign/node_modules/hash-base": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz",
"integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/browserify-sign/node_modules/isarray": { "node_modules/browserify-sign/node_modules/isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -14883,20 +14857,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/crypto-browserify/node_modules/hash-base": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz",
"integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/crypto-js": { "node_modules/crypto-js": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
@@ -15971,9 +15931,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.130", "version": "1.5.131",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.131.tgz",
"integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==", "integrity": "sha512-fJFRYXVEJgDCiqFOgRGJm8XR97hZ13tw7FXI9k2yC5hgY+nyzC2tMO8baq1cQR7Ur58iCkASx2zrkZPZUnfzPg==",
"devOptional": true, "devOptional": true,
"license": "ISC" "license": "ISC"
}, },
@@ -17024,21 +16984,21 @@
} }
}, },
"node_modules/expo": { "node_modules/expo": {
"version": "52.0.42", "version": "52.0.44",
"resolved": "https://registry.npmjs.org/expo/-/expo-52.0.42.tgz", "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.44.tgz",
"integrity": "sha512-t+PRYIzzPFAlF99OVJOjZwM1glLhN85XGD6vmeg6uwpADDILl9yw4dfy0DXL4hot5GJkAGaZ+uOHUljV4kC2Bg==", "integrity": "sha512-qj3+MWxmqLyBaYQ8jDOvVLEgSqNplH3cf+nDhxCo4C1cpTPD1u/HGh1foibtaeuCYLHsE5km1lrcOpRbFJ4luQ==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.0", "@babel/runtime": "^7.20.0",
"@expo/cli": "0.22.23", "@expo/cli": "0.22.24",
"@expo/config": "~10.0.11", "@expo/config": "~10.0.11",
"@expo/config-plugins": "~9.0.17", "@expo/config-plugins": "~9.0.17",
"@expo/fingerprint": "0.11.11", "@expo/fingerprint": "0.11.11",
"@expo/metro-config": "0.19.12", "@expo/metro-config": "0.19.12",
"@expo/vector-icons": "^14.0.0", "@expo/vector-icons": "^14.0.0",
"babel-preset-expo": "~12.0.10", "babel-preset-expo": "~12.0.11",
"expo-asset": "~11.0.5", "expo-asset": "~11.0.5",
"expo-constants": "~17.0.8", "expo-constants": "~17.0.8",
"expo-file-system": "~18.0.12", "expo-file-system": "~18.0.12",
@@ -18183,6 +18143,16 @@
"xtend": "~4.0.1" "xtend": "~4.0.1"
} }
}, },
"node_modules/get-pkg-repo/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/get-pkg-repo/node_modules/yargs": { "node_modules/get-pkg-repo/node_modules/yargs": {
"version": "16.2.0", "version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@@ -18696,31 +18666,16 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/hash-base": { "node_modules/hash-base": {
"version": "3.1.0", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"inherits": "^2.0.4", "inherits": "^2.0.4",
"readable-stream": "^3.6.0", "safe-buffer": "^5.2.1"
"safe-buffer": "^5.2.0"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">= 0.10"
}
},
"node_modules/hash-base/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==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
} }
}, },
"node_modules/hash.js": { "node_modules/hash.js": {
@@ -20859,15 +20814,6 @@
"xtend": "^2.2.0" "xtend": "^2.2.0"
} }
}, },
"node_modules/level-filesystem/node_modules/xtend": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz",
"integrity": "sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==",
"dev": true,
"engines": {
"node": ">=0.4"
}
},
"node_modules/level-fix-range": { "node_modules/level-fix-range": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/level-fix-range/-/level-fix-range-1.0.2.tgz", "resolved": "https://registry.npmjs.org/level-fix-range/-/level-fix-range-1.0.2.tgz",
@@ -21005,16 +20951,6 @@
"xtend": "~3.0.0" "xtend": "~3.0.0"
} }
}, },
"node_modules/levelup/node_modules/bl": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-0.8.2.tgz",
"integrity": "sha512-pfqikmByp+lifZCS0p6j6KreV6kNU6Apzpm2nKOk+94cZb/jvle55+JxWiByUQ0Wo/+XnDXEy5MxxKMb6r0VIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"readable-stream": "~1.0.26"
}
},
"node_modules/levelup/node_modules/isarray": { "node_modules/levelup/node_modules/isarray": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@@ -24775,6 +24711,58 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/ora/node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/ora/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/ora/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/os-tmpdir": { "node_modules/os-tmpdir": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -24950,20 +24938,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/parse-asn1/node_modules/hash-base": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz",
"integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/parse-entities": { "node_modules/parse-entities": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
@@ -29314,6 +29288,16 @@
"node": ">= 6" "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": { "node_modules/streamx": {
"version": "2.22.0", "version": "2.22.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz",
@@ -29859,6 +29843,41 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tar-stream/node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/tar-stream/node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"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/tar-stream/node_modules/readable-stream": { "node_modules/tar-stream/node_modules/readable-stream": {
"version": "3.6.2", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -30583,24 +30602,24 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/typeorm": { "node_modules/typeorm": {
"version": "0.3.21", "version": "0.3.22",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.22.tgz",
"integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==", "integrity": "sha512-P/Tsz3UpJ9+K0oryC0twK5PO27zejLYYwMsE8SISfZc1lVHX+ajigiOyWsKbuXpEFMjD9z7UjLzY3+ElVOMMDA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sqltools/formatter": "^1.2.5", "@sqltools/formatter": "^1.2.5",
"ansis": "^3.9.0", "ansis": "^3.17.0",
"app-root-path": "^3.1.0", "app-root-path": "^3.1.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"dayjs": "^1.11.9", "dayjs": "^1.11.13",
"debug": "^4.3.4", "debug": "^4.4.0",
"dotenv": "^16.0.3", "dotenv": "^16.4.7",
"glob": "^10.4.5", "glob": "^10.4.5",
"sha.js": "^2.4.11", "sha.js": "^2.4.11",
"sql-highlight": "^6.0.0", "sql-highlight": "^6.0.0",
"tslib": "^2.5.0", "tslib": "^2.8.1",
"uuid": "^11.0.5", "uuid": "^11.1.0",
"yargs": "^17.6.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
"typeorm": "cli.js", "typeorm": "cli.js",
@@ -30614,12 +30633,12 @@
"url": "https://opencollective.com/typeorm" "url": "https://opencollective.com/typeorm"
}, },
"peerDependencies": { "peerDependencies": {
"@google-cloud/spanner": "^5.18.0", "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0",
"@sap/hana-client": "^2.12.25", "@sap/hana-client": "^2.12.25",
"better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
"hdb-pool": "^0.1.6", "hdb-pool": "^0.1.6",
"ioredis": "^5.0.4", "ioredis": "^5.0.4",
"mongodb": "^5.8.0", "mongodb": "^5.8.0 || ^6.0.0",
"mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1",
"mysql2": "^2.2.5 || ^3.0.1", "mysql2": "^2.2.5 || ^3.0.1",
"oracledb": "^6.3.0", "oracledb": "^6.3.0",
@@ -30631,7 +30650,7 @@
"sql.js": "^1.4.0", "sql.js": "^1.4.0",
"sqlite3": "^5.0.3", "sqlite3": "^5.0.3",
"ts-node": "^10.7.0", "ts-node": "^10.7.0",
"typeorm-aurora-data-api-driver": "^2.0.0" "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@google-cloud/spanner": { "@google-cloud/spanner": {
@@ -30687,6 +30706,23 @@
} }
} }
}, },
"node_modules/typeorm/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/typeorm/node_modules/glob": { "node_modules/typeorm/node_modules/glob": {
"version": "10.4.5", "version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -30722,6 +30758,18 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/typeorm/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"
},
"node_modules/typeorm/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typeorm/node_modules/uuid": { "node_modules/typeorm/node_modules/uuid": {
"version": "11.1.0", "version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -31210,9 +31258,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.16", "version": "5.4.17",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.16.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz",
"integrity": "sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==", "integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -32417,11 +32465,10 @@
} }
}, },
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "integrity": "sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=0.4" "node": ">=0.4"
} }

View File

@@ -9,12 +9,26 @@
:height="iconSize" :height="iconSize"
@click="handleClick" @click="handleClick"
/> />
<div <div v-else class="cursor-pointer" @click="handleClick">
v-else <img
v-html="identiconSvg" v-if="!identifier"
class="cursor-pointer" :src="blankSquareUrl"
@click="handleClick" class="rounded"
/> :width="iconSize"
:height="iconSize"
/>
<svg
v-else
:width="iconSize"
:height="iconSize"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<g v-for="(path, index) in avatarPaths" :key="index">
<path :d="path" />
</g>
</svg>
</div>
</div> </div>
</template> </template>
@@ -32,7 +46,9 @@ export default class EntityIcon extends Vue {
@Prop iconSize = 0; @Prop iconSize = 0;
@Prop profileImageUrl = ""; // overridden by contact.profileImageUrl @Prop profileImageUrl = ""; // overridden by contact.profileImageUrl
private identiconSvg = ""; private avatarPaths: string[] = [];
private blankSquareUrl =
import.meta.env.VITE_BASE_URL + "assets/blank-square.svg";
get imageUrl(): string { get imageUrl(): string {
return this.contact?.profileImageUrl || this.profileImageUrl; return this.contact?.profileImageUrl || this.profileImageUrl;
@@ -42,38 +58,43 @@ export default class EntityIcon extends Vue {
return !!this.imageUrl; return !!this.imageUrl;
} }
get identifier(): string | undefined {
return this.contact?.did || this.entityId;
}
handleClick() { handleClick() {
try { try {
// Emit a simple event without passing the event object // Emit a simple event without passing the event object
this.$emit('click'); this.$emit("click");
} catch (error) { } catch (error) {
logger.error('Error handling click event:', error); logger.error("Error handling click event:", error);
} }
} }
generateIdenticon(): string { generateAvatarPaths(): string[] {
const identifier = this.contact?.did || this.entityId; if (!this.identifier) return [];
if (!identifier) {
const baseUrl = import.meta.env.VITE_BASE_URL || '/';
return `<img src="${baseUrl}assets/blank-square.svg" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
}
// https://api.dicebear.com/8.x/avataaars/svg?seed=
// ... does not render things with the same seed as this library.
// "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b" yields a girl with flowers in her hair and a lightning earring
// ... which looks similar to '' at the dicebear site but which is different.
const options: StyleOptions<object> = { const options: StyleOptions<object> = {
seed: identifier || "", seed: this.identifier,
size: this.iconSize, size: this.iconSize,
}; };
const avatar = createAvatar(avataaars, options); const avatar = createAvatar(avataaars, options);
return avatar.toString(); const svgString = avatar.toString();
// Extract paths from SVG string
const parser = new DOMParser();
const doc = parser.parseFromString(svgString, "image/svg+xml");
const paths = Array.from(doc.querySelectorAll("path")).map(
(path) => path.getAttribute("d") || "",
);
return paths;
} }
mounted() { mounted() {
this.identiconSvg = this.generateIdenticon(); this.avatarPaths = this.generateAvatarPaths();
logger.log('EntityIcon mounted, profileImageUrl:', this.profileImageUrl); logger.log("EntityIcon mounted, profileImageUrl:", this.profileImageUrl);
logger.log('EntityIcon mounted, entityId:', this.entityId); logger.log("EntityIcon mounted, entityId:", this.entityId);
logger.log('EntityIcon mounted, iconSize:', this.iconSize); logger.log("EntityIcon mounted, iconSize:", this.iconSize);
} }
} }
</script> </script>

View File

@@ -1,7 +1,8 @@
import BaseDexie, { Table } from "dexie"; import BaseDexie, { Table } from "dexie";
import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon"; import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon";
import { exportDB } from "dexie-export-import"; import { exportDB, ExportOptions } from "dexie-export-import";
import * as R from "ramda"; import * as R from "ramda";
import Dexie from "dexie";
import { Account, AccountsSchema } from "./tables/accounts"; import { Account, AccountsSchema } from "./tables/accounts";
import { Contact, ContactSchema } from "./tables/contacts"; import { Contact, ContactSchema } from "./tables/contacts";
@@ -27,9 +28,14 @@ type NonsensitiveTables = {
}; };
// Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings // Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings
export type SecretDexie<T extends unknown = SecretTable> = BaseDexie & T & { export: (options?: any) => Promise<Blob> }; export type SecretDexie<T extends Record<string, Dexie.Table> = SecretTable> =
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T & { export: (options?: any) => Promise<Blob> }; BaseDexie & T & { export: (options?: ExportOptions) => Promise<Blob> };
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> = BaseDexie & T & { export: (options?: any) => Promise<Blob> }; export type SensitiveDexie<
T extends Record<string, Dexie.Table> = SensitiveTables,
> = BaseDexie & T & { export: (options?: ExportOptions) => Promise<Blob> };
export type NonsensitiveDexie<
T extends Record<string, Dexie.Table> = NonsensitiveTables,
> = BaseDexie & T & { export: (options?: ExportOptions) => Promise<Blob> };
//// Initialize the DBs, starting with the sensitive ones. //// Initialize the DBs, starting with the sensitive ones.

View File

@@ -1,69 +1,82 @@
/** /**
* @file DatabaseBackupService.ts * @file DatabaseBackupService.ts
* @description Capacitor-specific implementation of DatabaseBackupService * @description Capacitor-specific implementation of database backup service
* *
* This implementation handles database backup operations specifically for Capacitor * This service handles database backup operations on Capacitor platforms (Android/iOS)
* platforms (Android/iOS). It uses the Filesystem and Share plugins to save and * using the Filesystem and Share plugins. It creates a temporary backup file,
* share the backup file. * writes the backup data to it, and shares the file using the platform's share sheet.
*/ */
import { DatabaseBackupService as BaseDatabaseBackupService } from "../../services/DatabaseBackupService";
import { Filesystem, Directory } from "@capacitor/filesystem"; import { Filesystem, Directory } from "@capacitor/filesystem";
import { Share } from "@capacitor/share"; import { Share } from "@capacitor/share";
import { DatabaseBackupService as BaseDatabaseBackupService } from "../../services/DatabaseBackupService";
import { log, error } from "../../utils/logger"; import { log, error } from "../../utils/logger";
export class DatabaseBackupService extends BaseDatabaseBackupService { export class DatabaseBackupService extends BaseDatabaseBackupService {
/** /**
* Handles the backup process for Capacitor platforms * Handles the backup process for Capacitor platforms
* *
* @param base64Data - Backup data in base64 format * @param base64Data - Backup data in base64 format
* @param arrayBuffer - Backup data as ArrayBuffer * @param arrayBuffer - Backup data as ArrayBuffer
* @param blob - Backup data as Blob * @param blob - Backup data as Blob
*/ */
protected async handleBackup( protected async handleBackup(
base64Data: string, base64Data: string,
arrayBuffer: ArrayBuffer, _arrayBuffer: ArrayBuffer,
blob: Blob _blob: Blob,
): Promise<void> { ): Promise<void> {
try { try {
log("Starting Capacitor backup process"); log("Starting backup process for Capacitor platform");
// Create a temporary file // Create a timestamped backup file name
const fileName = `timesafari-backup-${new Date().toISOString()}.json`; const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filePath = `backups/${fileName}`; const backupFileName = `timesafari-backup-${timestamp}.json`;
const backupFilePath = `backups/${backupFileName}`;
log("Writing backup file");
const result = await Filesystem.writeFile({ log("Creating backup file:", {
path: filePath, fileName: backupFileName,
path: backupFilePath,
});
// Write the backup file
const writeResult = (await Filesystem.writeFile({
path: backupFilePath,
data: base64Data, data: base64Data,
directory: Directory.Cache, directory: Directory.Cache,
recursive: true recursive: true,
}); })) as unknown as { uri: string };
log("Getting file path"); if (!writeResult.uri) {
const fileInfo = await Filesystem.stat({ throw new Error("Failed to write backup file: No URI returned");
path: filePath, }
directory: Directory.Cache
}); log("Backup file written successfully:", { uri: writeResult.uri });
// Share the backup file
log("Sharing backup file"); log("Sharing backup file");
await Share.share({ await Share.share({
title: "TimeSafari Backup", title: "TimeSafari Backup",
text: "Your TimeSafari backup file", text: "Your TimeSafari backup file",
url: fileInfo.uri, url: writeResult.uri,
dialogTitle: "Share TimeSafari Backup" dialogTitle: "Share TimeSafari Backup",
}); });
log("Backup shared successfully"); log("Backup shared successfully");
// Clean up the temporary file // Clean up the temporary file
await Filesystem.deleteFile({ try {
path: filePath, await Filesystem.deleteFile({
directory: Directory.Cache path: backupFilePath,
}); directory: Directory.Cache,
});
log("Temporary backup file cleaned up");
} catch (cleanupError) {
error("Failed to clean up temporary backup file:", cleanupError);
// Don't throw here as the backup was successful
}
} catch (err) { } catch (err) {
error("Error during Capacitor backup:", err); error("Error during backup process:", err);
throw err; throw err;
} }
} }
} }

View File

@@ -65,23 +65,23 @@ export class DatabaseBackupService {
public static async createAndShareBackup( public static async createAndShareBackup(
base64Data: string, base64Data: string,
arrayBuffer: ArrayBuffer, arrayBuffer: ArrayBuffer,
blob: Blob blob: Blob,
): Promise<void> { ): Promise<void> {
try { try {
log('Creating platform-specific backup service'); log("Creating platform-specific backup service");
const backupService = await this.getPlatformSpecificBackupService(); const backupService = await this.getPlatformSpecificBackupService();
log('Backup service created successfully'); log("Backup service created successfully");
log('Executing platform-specific backup'); log("Executing platform-specific backup");
await backupService.handleBackup(base64Data, arrayBuffer, blob); await backupService.handleBackup(base64Data, arrayBuffer, blob);
log('Backup completed successfully'); log("Backup completed successfully");
} catch (err) { } catch (err) {
error('Error during backup creation:', err); error("Error during backup creation:", err);
if (err instanceof Error) { if (err instanceof Error) {
error('Error details:', { error("Error details:", {
name: err.name, name: err.name,
message: err.message, message: err.message,
stack: err.stack stack: err.stack,
}); });
} }
throw err; throw err;

View File

@@ -131,10 +131,12 @@ export class PlatformServiceFactory {
public async createDatabaseBackupService(): Promise<DatabaseBackupService> { public async createDatabaseBackupService(): Promise<DatabaseBackupService> {
// List of supported platforms for web builds // List of supported platforms for web builds
const webSupportedPlatforms = ["web", "mobile"]; const webSupportedPlatforms = ["web", "mobile"];
// Return stub implementation for unsupported platforms // Return stub implementation for unsupported platforms
if (!webSupportedPlatforms.includes(this.platform)) { if (!webSupportedPlatforms.includes(this.platform)) {
logger.log(`Using stub implementation for unsupported platform: ${this.platform}`); logger.log(
`Using stub implementation for unsupported platform: ${this.platform}`,
);
return new StubDatabaseBackupService(); return new StubDatabaseBackupService();
} }
@@ -145,15 +147,15 @@ export class PlatformServiceFactory {
/* @vite-ignore */ /* @vite-ignore */
`./platforms/${this.platform}/DatabaseBackupService.ts` `./platforms/${this.platform}/DatabaseBackupService.ts`
); );
logger.log('Platform service loaded successfully'); logger.log("Platform service loaded successfully");
return new module.DatabaseBackupService(); return new module.DatabaseBackupService();
} catch (error) { } catch (error) {
logger.error( logger.error(
`[TimeSafari] Failed to load platform-specific service for ${this.platform}:`, `[TimeSafari] Failed to load platform-specific service for ${this.platform}:`,
error error,
); );
// Fallback to stub implementation on error // Fallback to stub implementation on error
logger.log('Falling back to stub implementation'); logger.log("Falling back to stub implementation");
return new StubDatabaseBackupService(); return new StubDatabaseBackupService();
} }
} }

View File

@@ -17,31 +17,45 @@ export class RateLimitsService {
* @param did - The user's DID * @param did - The user's DID
* @returns Promise<EndorserRateLimits> * @returns Promise<EndorserRateLimits>
*/ */
static async fetchRateLimits(apiServer: string, did: string): Promise<EndorserRateLimits> { static async fetchRateLimits(
logger.log('Fetching rate limits for DID:', did); apiServer: string,
logger.log('Using API server:', apiServer); did: string,
): Promise<EndorserRateLimits> {
logger.log("Fetching rate limits for DID:", did);
logger.log("Using API server:", apiServer);
try { try {
const headers = await getHeaders(did); const headers = await getHeaders(did);
const response = await axios.get(`${apiServer}/api/v2/rate-limits/${did}`, { headers }); const response = await axios.get(
logger.log('Rate limits response:', response.data); `${apiServer}/api/v2/rate-limits/${did}`,
{ headers },
);
logger.log("Rate limits response:", response.data);
return response.data; return response.data;
} catch (error) { } catch (error) {
if (axios.isAxiosError(error) && (error.response?.status === 400 || error.response?.status === 404)) { if (
const errorData = error.response.data as { error?: { message?: string, code?: string } }; axios.isAxiosError(error) &&
if (errorData.error?.code === 'UNREGISTERED_USER' || error.response?.status === 404) { (error.response?.status === 400 || error.response?.status === 404)
logger.log('User is not registered, returning default limits'); ) {
const errorData = error.response.data as {
error?: { message?: string; code?: string };
};
if (
errorData.error?.code === "UNREGISTERED_USER" ||
error.response?.status === 404
) {
logger.log("User is not registered, returning default limits");
return { return {
doneClaimsThisWeek: "0", doneClaimsThisWeek: "0",
maxClaimsPerWeek: "0", maxClaimsPerWeek: "0",
nextWeekBeginDateTime: new Date().toISOString(), nextWeekBeginDateTime: new Date().toISOString(),
doneRegistrationsThisMonth: "0", doneRegistrationsThisMonth: "0",
maxRegistrationsPerMonth: "0", maxRegistrationsPerMonth: "0",
nextMonthBeginDateTime: new Date().toISOString() nextMonthBeginDateTime: new Date().toISOString(),
}; };
} }
} }
logger.error('Error fetching rate limits:', error); logger.error("Error fetching rate limits:", error);
throw error; throw error;
} }
} }

View File

@@ -17,4 +17,4 @@ export default class StubDatabaseBackupService extends DatabaseBackupService {
} }
} }
export { StubDatabaseBackupService as DatabaseBackupService }; export { StubDatabaseBackupService as DatabaseBackupService };

View File

@@ -12,10 +12,10 @@ export default class WebDatabaseBackupService extends DatabaseBackupService {
protected async handleBackup( protected async handleBackup(
_base64Data: string, _base64Data: string,
_arrayBuffer: ArrayBuffer, _arrayBuffer: ArrayBuffer,
blob: Blob blob: Blob,
): Promise<void> { ): Promise<void> {
try { try {
log('Starting web platform backup'); log("Starting web platform backup");
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
@@ -24,9 +24,9 @@ export default class WebDatabaseBackupService extends DatabaseBackupService {
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
log('Web platform backup completed'); log("Web platform backup completed");
} catch (err) { } catch (err) {
error('Error during web platform backup:', err); error("Error during web platform backup:", err);
throw err; throw err;
} }
} }

View File

@@ -1,29 +1,29 @@
/** /**
* @file interfaces.ts * @file interfaces.ts
* @description Core type declarations for the TimeSafari application * @description Core type declarations for the TimeSafari application
* *
* This module defines the core interfaces and types used throughout the application. * This module defines the core interfaces and types used throughout the application.
* It serves as the central location for type definitions that are shared across * It serves as the central location for type definitions that are shared across
* multiple components and services. * multiple components and services.
* *
* Architecture: * Architecture:
* 1. DID (Decentralized Identifier) Types: * 1. DID (Decentralized Identifier) Types:
* - IIdentifier: Core DID structure * - IIdentifier: Core DID structure
* - IKey: Cryptographic key information * - IKey: Cryptographic key information
* - IService: Service endpoints and capabilities * - IService: Service endpoints and capabilities
* *
* 2. Verifiable Credential Types: * 2. Verifiable Credential Types:
* - GenericCredWrapper: Base wrapper for all credentials * - GenericCredWrapper: Base wrapper for all credentials
* - GiveVerifiableCredential: Gift-related credentials * - GiveVerifiableCredential: Gift-related credentials
* - OfferVerifiableCredential: Offer-related credentials * - OfferVerifiableCredential: Offer-related credentials
* - RegisterVerifiableCredential: Registration credentials * - RegisterVerifiableCredential: Registration credentials
* *
* 3. Service Types: * 3. Service Types:
* - EndorserService: Claims and endorsements * - EndorserService: Claims and endorsements
* - PushNotificationService: Web push notifications * - PushNotificationService: Web push notifications
* - ProfileService: User profiles * - ProfileService: User profiles
* - BackupService: Data backup * - BackupService: Data backup
* *
* @see src/interfaces/identifier.ts * @see src/interfaces/identifier.ts
* @see src/interfaces/claims.ts * @see src/interfaces/claims.ts
* @see src/interfaces/limits.ts * @see src/interfaces/limits.ts
@@ -33,11 +33,11 @@ import { GiveVerifiableCredential } from "../interfaces";
/** /**
* Interface for a Decentralized Identifier (DID) * Interface for a Decentralized Identifier (DID)
* *
* This interface defines the structure of a DID, which is a unique identifier * This interface defines the structure of a DID, which is a unique identifier
* that can be used to look up a DID document containing information associated * that can be used to look up a DID document containing information associated
* with the DID, such as public keys and service endpoints. * with the DID, such as public keys and service endpoints.
* *
* @example * @example
* ```typescript * ```typescript
* const identifier: IIdentifier = { * const identifier: IIdentifier = {
@@ -102,7 +102,29 @@ export interface IIdentifier {
/** /**
* Optional metadata about the key * Optional metadata about the key
*/ */
meta?: any; meta?: {
/**
* HD wallet derivation path
* @example "m/44'/60'/0'/0/0"
*/
derivationPath?: string;
/**
* Key usage or purpose
* @example "signing", "encryption"
*/
usage?: string;
/**
* Key creation timestamp
*/
createdAt?: number;
/**
* Additional key metadata
*/
[key: string]: unknown;
};
}>; }>;
/** /**
@@ -132,15 +154,41 @@ export interface IIdentifier {
*/ */
description?: string; description?: string;
}>; }>;
/**
* Optional metadata about the identifier
*/
meta?: {
/**
* DID method-specific metadata
* @example { network: "mainnet", chainId: 1 } for ethr
*/
method?: Record<string, unknown>;
/**
* Identifier creation timestamp
*/
createdAt?: number;
/**
* Last update timestamp
*/
updatedAt?: number;
/**
* Additional identifier metadata
*/
[key: string]: unknown;
};
} }
/** /**
* Interface for a cryptographic key * Interface for a cryptographic key
* *
* This interface defines the structure of a cryptographic key used in the * This interface defines the structure of a cryptographic key used in the
* DID system. It includes both public and private key information, along * DID system. It includes both public and private key information, along
* with metadata about the key's purpose and derivation. * with metadata about the key's purpose and derivation.
* *
* @example * @example
* ```typescript * ```typescript
* const key: IKey = { * const key: IKey = {
@@ -203,6 +251,17 @@ export interface IKey {
*/ */
derivationPath?: string; derivationPath?: string;
/**
* Key usage or purpose
* @example "signing", "encryption"
*/
usage?: string;
/**
* Key creation timestamp
*/
createdAt?: number;
/** /**
* Additional key metadata * Additional key metadata
*/ */
@@ -212,11 +271,11 @@ export interface IKey {
/** /**
* Interface for a service endpoint * Interface for a service endpoint
* *
* This interface defines the structure of a service endpoint that can be * This interface defines the structure of a service endpoint that can be
* associated with a DID. Services provide additional functionality and * associated with a DID. Services provide additional functionality and
* endpoints for DID operations. * endpoints for DID operations.
* *
* @example * @example
* ```typescript * ```typescript
* const service: IService = { * const service: IService = {
@@ -352,3 +411,20 @@ export interface GiveRecordWithContactInfo {
recipientProjectName?: string; recipientProjectName?: string;
image?: string; image?: string;
} }
export interface TimeSafariError extends Error {
/**
* User-friendly error message
*/
userMessage?: string;
/**
* Error code for programmatic handling
*/
code?: string;
/**
* Additional error context
*/
context?: Record<string, unknown>;
}

View File

@@ -20,7 +20,7 @@ function safeStringify(obj: unknown) {
} }
function formatMessage(message: string, ...args: unknown[]): string { function formatMessage(message: string, ...args: unknown[]): string {
const prefix = '[TimeSafari]'; const prefix = "[TimeSafari]";
const argsString = args.length > 0 ? " - " + safeStringify(args) : ""; const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
return `${prefix} ${message}${argsString}`; return `${prefix} ${message}${argsString}`;
} }
@@ -29,6 +29,7 @@ export const logger = {
log: (message: string, ...args: unknown[]) => { log: (message: string, ...args: unknown[]) => {
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
const formattedMessage = formatMessage(message, ...args); const formattedMessage = formatMessage(message, ...args);
// eslint-disable-next-line no-console
console.log(formattedMessage); console.log(formattedMessage);
logToDb(message + (args.length > 0 ? " - " + safeStringify(args) : "")); logToDb(message + (args.length > 0 ? " - " + safeStringify(args) : ""));
} }
@@ -36,38 +37,45 @@ export const logger = {
warn: (message: string, ...args: unknown[]) => { warn: (message: string, ...args: unknown[]) => {
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
const formattedMessage = formatMessage(message, ...args); const formattedMessage = formatMessage(message, ...args);
// eslint-disable-next-line no-console
console.warn(formattedMessage); console.warn(formattedMessage);
logToDb(message + (args.length > 0 ? " - " + safeStringify(args) : "")); logToDb(message + (args.length > 0 ? " - " + safeStringify(args) : ""));
} }
}, },
error: (message: string, ...args: unknown[]) => { error: (message: string, ...args: unknown[]) => {
const formattedMessage = formatMessage(message, ...args); const formattedMessage = formatMessage(message, ...args);
// eslint-disable-next-line no-console
console.error(formattedMessage); console.error(formattedMessage);
logToDb(message + (args.length > 0 ? " - " + safeStringify(args) : "")); logToDb(message + (args.length > 0 ? " - " + safeStringify(args) : ""));
}, },
}; };
export function log(...args: any[]) { export function log(message: string, ...args: unknown[]): void {
const message = formatMessage(args[0], ...args.slice(1)); const formattedMessage = formatMessage(message, ...args);
console.log(message); // eslint-disable-next-line no-console
console.log(formattedMessage);
} }
export function error(...args: any[]) { export function error(message: string, ...args: unknown[]): void {
const message = formatMessage(args[0], ...args.slice(1)); const formattedMessage = formatMessage(message, ...args);
console.error(message); // eslint-disable-next-line no-console
console.error(formattedMessage);
} }
export function warn(...args: any[]) { export function warn(message: string, ...args: unknown[]): void {
const message = formatMessage(args[0], ...args.slice(1)); const formattedMessage = formatMessage(message, ...args);
console.warn(message); // eslint-disable-next-line no-console
console.warn(formattedMessage);
} }
export function info(...args: any[]) { export function info(message: string, ...args: unknown[]): void {
const message = formatMessage(args[0], ...args.slice(1)); const formattedMessage = formatMessage(message, ...args);
console.info(message); // eslint-disable-next-line no-console
console.info(formattedMessage);
} }
export function debug(...args: any[]) { export function debug(message: string, ...args: unknown[]): void {
const message = formatMessage(args[0], ...args.slice(1)); const formattedMessage = formatMessage(message, ...args);
console.debug(message); // eslint-disable-next-line no-console
console.debug(formattedMessage);
} }

View File

@@ -836,7 +836,6 @@ import "leaflet/dist/leaflet.css";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { Buffer } from "buffer/"; import { Buffer } from "buffer/";
import Dexie from "dexie"; import Dexie from "dexie";
import { exportDB } from "dexie-export-import";
import * as R from "ramda"; import * as R from "ramda";
import type { IIdentifier, UserProfile } from "@/types/interfaces"; import type { IIdentifier, UserProfile } from "@/types/interfaces";
import { ref } from "vue"; import { ref } from "vue";
@@ -889,7 +888,6 @@ import { DatabaseBackupService } from "../services/DatabaseBackupService";
import { ProfileService } from "../services/ProfileService"; import { ProfileService } from "../services/ProfileService";
import { RateLimitsService } from "../services/RateLimitsService"; import { RateLimitsService } from "../services/RateLimitsService";
import ProfileSection from "../components/ProfileSection.vue"; import ProfileSection from "../components/ProfileSection.vue";
import { log, error } from '../utils/logger';
const inputImportFileNameRef = ref<Blob>(); const inputImportFileNameRef = ref<Blob>();
@@ -1404,12 +1402,12 @@ export default class AccountViewView extends Vue {
async exportDatabase() { async exportDatabase() {
try { try {
logger.log("Starting database export process"); logger.log("Starting database export process");
const db = await this.getDatabase(); const db = await this.getDatabase();
logger.log("Database instance:", { logger.log("Database instance:", {
name: db.name, name: db.name,
version: db.verno, version: db.verno,
tables: db.tables.map((t: { name: string }) => t.name) tables: db.tables.map((t: { name: string }) => t.name),
}); });
if (!db.export) { if (!db.export) {
@@ -1421,7 +1419,7 @@ export default class AccountViewView extends Vue {
const blob = await db.export(); const blob = await db.export();
logger.log("Blob created:", { logger.log("Blob created:", {
type: blob.type, type: blob.type,
size: blob.size size: blob.size,
}); });
logger.log("Converting blob to base64"); logger.log("Converting blob to base64");
@@ -1445,8 +1443,16 @@ export default class AccountViewView extends Vue {
} }
} }
async createAndShareBackup(base64Data: string, arrayBuffer: ArrayBuffer, blob: Blob) { async createAndShareBackup(
await DatabaseBackupService.createAndShareBackup(base64Data, arrayBuffer, blob); base64Data: string,
arrayBuffer: ArrayBuffer,
blob: Blob,
) {
await DatabaseBackupService.createAndShareBackup(
base64Data,
arrayBuffer,
blob,
);
} }
async uploadImportFile(event: Event) { async uploadImportFile(event: Event) {

View File

@@ -24,8 +24,8 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { nextTick } from "vue"; import { nextTick } from "vue";
import QRCodeVue from 'qrcode.vue'; import QRCodeVue from "qrcode.vue";
import { APP_SERVER, NotificationIface } from "../constants/app"; import { NotificationIface } from "../constants/app";
import { db, retrieveSettingsForActiveAccount } from "../db/index"; import { db, retrieveSettingsForActiveAccount } from "../db/index";
import * as serverUtil from "../libs/endorserServer"; import * as serverUtil from "../libs/endorserServer";
import { GenericCredWrapper, GenericVerifiableCredential } from "../interfaces"; import { GenericCredWrapper, GenericVerifiableCredential } from "../interfaces";
@@ -277,11 +277,11 @@ export default class ClaimCertificateView extends Vue {
private async generateQRCode() { private async generateQRCode() {
if (!this.qrCodeRef) return; if (!this.qrCodeRef) return;
const canvas = await this.qrCodeRef.toCanvas(); const canvas = await this.qrCodeRef.toCanvas();
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d");
if (!ctx) return; if (!ctx) return;
// Draw the QR code on the claim canvas // Draw the QR code on the claim canvas
const CANVAS_WIDTH = 1100; const CANVAS_WIDTH = 1100;
const CANVAS_HEIGHT = 850; const CANVAS_HEIGHT = 850;

View File

@@ -20,9 +20,9 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { nextTick } from "vue"; import { nextTick } from "vue";
import QRCodeVue from 'qrcode.vue'; import QRCodeVue from "qrcode.vue";
import { APP_SERVER, NotificationIface } from "../constants/app"; import { NotificationIface } from "../constants/app";
import { db, retrieveSettingsForActiveAccount } from "../db/index"; import { db, retrieveSettingsForActiveAccount } from "../db/index";
import * as endorserServer from "../libs/endorserServer"; import * as endorserServer from "../libs/endorserServer";
import { GenericCredWrapper, GenericVerifiableCredential } from "../interfaces"; import { GenericCredWrapper, GenericVerifiableCredential } from "../interfaces";
@@ -84,9 +84,7 @@ export default class ClaimReportCertificateView extends Vue {
} }
} }
async drawCanvas( async drawCanvas(claimData: GenericCredWrapper<GenericVerifiableCredential>) {
claimData: GenericCredWrapper<GenericVerifiableCredential>,
) {
await db.open(); await db.open();
const allContacts = await db.contacts.toArray(); const allContacts = await db.contacts.toArray();
@@ -99,7 +97,13 @@ export default class ClaimReportCertificateView extends Vue {
backgroundImage.src = "/img/background/cert-frame-2.jpg"; backgroundImage.src = "/img/background/cert-frame-2.jpg";
backgroundImage.onload = async () => { backgroundImage.onload = async () => {
// Draw the background image // Draw the background image
ctx.drawImage(backgroundImage, 0, 0, this.CANVAS_WIDTH, this.CANVAS_HEIGHT); ctx.drawImage(
backgroundImage,
0,
0,
this.CANVAS_WIDTH,
this.CANVAS_HEIGHT,
);
// Set font and styles // Set font and styles
ctx.fillStyle = "black"; ctx.fillStyle = "black";
@@ -164,12 +168,20 @@ export default class ClaimReportCertificateView extends Vue {
claimData.issuer, claimData.issuer,
allContacts, allContacts,
); );
ctx.fillText(issuerText, this.CANVAS_WIDTH * 0.3, this.CANVAS_HEIGHT * 0.6); ctx.fillText(
issuerText,
this.CANVAS_WIDTH * 0.3,
this.CANVAS_HEIGHT * 0.6,
);
} }
// Draw claim ID // Draw claim ID
ctx.font = "14px Arial"; ctx.font = "14px Arial";
ctx.fillText(this.claimId, this.CANVAS_WIDTH * 0.3, this.CANVAS_HEIGHT * 0.7); ctx.fillText(
this.claimId,
this.CANVAS_WIDTH * 0.3,
this.CANVAS_HEIGHT * 0.7,
);
ctx.fillText( ctx.fillText(
"via EndorserSearch.com", "via EndorserSearch.com",
this.CANVAS_WIDTH * 0.3, this.CANVAS_WIDTH * 0.3,
@@ -185,11 +197,11 @@ export default class ClaimReportCertificateView extends Vue {
private async generateQRCode() { private async generateQRCode() {
if (!this.qrCodeRef) return; if (!this.qrCodeRef) return;
const canvas = await this.qrCodeRef.toCanvas(); const canvas = await this.qrCodeRef.toCanvas();
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d");
if (!ctx) return; if (!ctx) return;
// Draw the QR code on the report canvas // Draw the QR code on the report canvas
ctx.drawImage(canvas, this.CANVAS_WIDTH * 0.6, this.CANVAS_HEIGHT * 0.55); ctx.drawImage(canvas, this.CANVAS_WIDTH * 0.6, this.CANVAS_HEIGHT * 0.55);
} }

View File

@@ -345,10 +345,13 @@ import {
GiverReceiverInputInfo, GiverReceiverInputInfo,
OnboardPage, OnboardPage,
} from "../libs/util"; } from "../libs/util";
import { GiveSummaryRecord } from "../interfaces"; import { GiveSummaryRecord, PlanSummaryRecord } from "../interfaces/records";
import * as serverUtil from "../libs/endorserServer"; import * as serverUtil from "../libs/endorserServer";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { GiveRecordWithContactInfo } from "types"; import { GiveRecordWithContactInfo } from "types";
import { TimeSafariError } from "../types/interfaces";
import { GiveVerifiableCredential } from "../interfaces/claims";
import { GenericVerifiableCredential } from "../interfaces/common";
/** /**
* HomeView Component * HomeView Component
@@ -451,20 +454,20 @@ export default class HomeView extends Vue {
const initPromises = [ const initPromises = [
this.initializeIdentity(), this.initializeIdentity(),
this.loadSettings(), this.loadSettings(),
this.loadContacts() this.loadContacts(),
]; ];
await Promise.all(initPromises); await Promise.all(initPromises);
// Sequential operations that depend on the above // Sequential operations that depend on the above
await this.checkRegistrationStatus(); await this.checkRegistrationStatus();
await this.loadFeedData(); await this.loadFeedData();
// Non-critical operations that can run after UI is ready // Non-critical operations that can run after UI is ready
this.loadNewOffers().catch(err => { this.loadNewOffers().catch((err) => {
logger.error("Error loading new offers:", err); logger.error("Error loading new offers:", err);
}); });
this.checkOnboarding().catch(err => { this.checkOnboarding().catch((err) => {
logger.error("Error checking onboarding:", err); logger.error("Error checking onboarding:", err);
}); });
} catch (err: unknown) { } catch (err: unknown) {
@@ -487,7 +490,7 @@ export default class HomeView extends Vue {
try { try {
// Load DIDs first as it's critical // Load DIDs first as it's critical
this.allMyDids = await retrieveAccountDids(); this.allMyDids = await retrieveAccountDids();
if (this.allMyDids.length === 0) { if (this.allMyDids.length === 0) {
this.isCreatingIdentifier = true; this.isCreatingIdentifier = true;
const newDid = await generateSaveAndActivateIdentity(); const newDid = await generateSaveAndActivateIdentity();
@@ -498,7 +501,7 @@ export default class HomeView extends Vue {
// Load settings and contacts in parallel // Load settings and contacts in parallel
const [settings, contacts] = await Promise.all([ const [settings, contacts] = await Promise.all([
retrieveSettingsForActiveAccount(), retrieveSettingsForActiveAccount(),
db.contacts.toArray() db.contacts.toArray(),
]); ]);
// Update state with loaded data // Update state with loaded data
@@ -511,36 +514,43 @@ export default class HomeView extends Vue {
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby; this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings.isRegistered; this.isRegistered = !!settings.isRegistered;
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId; this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId;
this.lastAckedOfferToUserProjectsJwtId = settings.lastAckedOfferToUserProjectsJwtId; this.lastAckedOfferToUserProjectsJwtId =
settings.lastAckedOfferToUserProjectsJwtId;
this.searchBoxes = settings.searchBoxes || []; this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings.showShortcutBvc; this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings); this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
// Start non-critical operations // Start non-critical operations
if (!settings.finishedOnboarding) { if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(OnboardPage.Home); (this.$refs.onboardingDialog as OnboardingDialog).open(
OnboardPage.Home,
);
} }
// Check registration status in background // Check registration status in background
if (!this.isRegistered && this.activeDid) { if (!this.isRegistered && this.activeDid) {
this.checkRegistrationStatus().catch(err => { this.checkRegistrationStatus().catch((err: TimeSafariError) => {
logger.error("Error checking registration status:", err); logger.error("Error checking registration status:", err);
}); });
} }
// Start feed update in background // Start feed update in background
this.updateAllFeed().catch(err => { this.updateAllFeed().catch((err: TimeSafariError) => {
logger.error("Error updating feed:", err); logger.error("Error updating feed:", err);
}); });
} catch (err) {
} catch (err: any) { const error = err as TimeSafariError;
logConsoleAndDb("Error retrieving settings or feed: " + err, true); logConsoleAndDb(
"Error retrieving settings or feed: " + error.message,
true,
);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Error", title: "Error",
text: (err as { userMessage?: string })?.userMessage || text:
error.userMessage ||
"There was an error retrieving your settings or the latest activity.", "There was an error retrieving your settings or the latest activity.",
}, },
5000, 5000,
@@ -835,8 +845,9 @@ export default class HomeView extends Vue {
await this.processFeedResults(results.data); await this.processFeedResults(results.data);
await this.updateFeedLastViewedId(results.data); await this.updateFeedLastViewedId(results.data);
} }
} catch (e) { } catch (err) {
this.handleFeedError(e); const error = err as TimeSafariError;
this.handleFeedError(error);
} }
if (this.feedData.length === 0 && !endOfResults) { if (this.feedData.length === 0 && !endOfResults) {
@@ -878,10 +889,10 @@ export default class HomeView extends Vue {
if (processedRecord) { if (processedRecord) {
this.feedData.push(processedRecord); this.feedData.push(processedRecord);
} }
}) }),
); );
// Allow UI to update between chunks // Allow UI to update between chunks
await new Promise(resolve => setTimeout(resolve, 0)); await new Promise((resolve) => setTimeout(resolve, 0));
} }
this.feedPreviousOldestId = records[records.length - 1].jwtId; this.feedPreviousOldestId = records[records.length - 1].jwtId;
} }
@@ -983,9 +994,11 @@ export default class HomeView extends Vue {
* @param claim The claim object containing giver information * @param claim The claim object containing giver information
* @returns The giver's DID * @returns The giver's DID
*/ */
private extractGiverDid(claim: any) { private extractGiverDid(claim: GiveVerifiableCredential): string {
// eslint-disable-next-line @typescript-eslint/no-explicit-any if (!claim.agent?.identifier) {
return claim.agent?.identifier || (claim.agent as any)?.did; throw new Error("Agent identifier is missing in claim");
}
return claim.agent.identifier;
} }
/** /**
@@ -994,9 +1007,11 @@ export default class HomeView extends Vue {
* @internal * @internal
* Called by processRecord() * Called by processRecord()
*/ */
private extractRecipientDid(claim: any) { private extractRecipientDid(claim: GiveVerifiableCredential): string {
// eslint-disable-next-line @typescript-eslint/no-explicit-any if (!claim.recipient?.identifier) {
return claim.recipient?.identifier || (claim.recipient as any)?.did; throw new Error("Recipient identifier is missing in claim");
}
return claim.recipient.identifier;
} }
/** /**
@@ -1052,7 +1067,7 @@ export default class HomeView extends Vue {
*/ */
private shouldIncludeRecord( private shouldIncludeRecord(
record: GiveSummaryRecord, record: GiveSummaryRecord,
fulfillsPlan: any, fulfillsPlan: PlanSummaryRecord | null | undefined,
): boolean { ): boolean {
if (!this.isAnyFeedFilterOn) { if (!this.isAnyFeedFilterOn) {
return true; return true;
@@ -1086,7 +1101,7 @@ export default class HomeView extends Vue {
* @internal * @internal
* Called by processRecord() * Called by processRecord()
*/ */
private extractProvider(claim: any) { private extractProvider(claim: GiveVerifiableCredential) {
return Array.isArray(claim.provider) ? claim.provider[0] : claim.provider; return Array.isArray(claim.provider) ? claim.provider[0] : claim.provider;
} }
@@ -1096,7 +1111,9 @@ export default class HomeView extends Vue {
* @internal * @internal
* Called by processRecord() * Called by processRecord()
*/ */
private async getProvidedByPlan(provider: any) { private async getProvidedByPlan(
provider: GenericVerifiableCredential | null,
) {
return await getPlanFromCache( return await getPlanFromCache(
provider?.identifier as string, provider?.identifier as string,
this.axios, this.axios,
@@ -1134,12 +1151,12 @@ export default class HomeView extends Vue {
*/ */
private createFeedRecord( private createFeedRecord(
record: GiveSummaryRecord, record: GiveSummaryRecord,
claim: any, claim: GiveVerifiableCredential,
giverDid: string, giverDid: string,
recipientDid: string, recipientDid: string,
provider: any, provider: GenericVerifiableCredential | null,
fulfillsPlan: any, fulfillsPlan: PlanSummaryRecord | null | undefined,
providedByPlan: any, providedByPlan: PlanSummaryRecord | null | undefined,
): GiveRecordWithContactInfo { ): GiveRecordWithContactInfo {
return { return {
...record, ...record,
@@ -1198,14 +1215,14 @@ export default class HomeView extends Vue {
* @internal * @internal
* Called by updateAllFeed() * Called by updateAllFeed()
*/ */
private handleFeedError(e: any) { private handleFeedError(error: TimeSafariError) {
logger.error("Error with feed load:", e); logger.error("Error with feed load:", error);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Feed Error", title: "Feed Error",
text: e.userMessage || "There was an error retrieving feed data.", text: error.userMessage || "There was an error retrieving feed data.",
}, },
-1, -1,
); );
@@ -1217,7 +1234,7 @@ export default class HomeView extends Vue {
* @internal * @internal
* Called by updateAllFeed() * Called by updateAllFeed()
* @param endorserApiServer API server URL * @param endorserApiServer API server URL
* @param beforeId OptioCalled by updateAllFeed()nal ID to fetch earlier results * @param beforeId Optional ID to fetch earlier results
* @returns claims in reverse chronological order * @returns claims in reverse chronological order
*/ */
async retrieveGives(endorserApiServer: string, beforeId?: string) { async retrieveGives(endorserApiServer: string, beforeId?: string) {
@@ -1606,10 +1623,11 @@ export default class HomeView extends Vue {
* @param eventOrUrl Either an Event object or a direct image URL string * @param eventOrUrl Either an Event object or a direct image URL string
*/ */
private async openImageViewer(eventOrUrl: Event | string) { private async openImageViewer(eventOrUrl: Event | string) {
const imageUrl = typeof eventOrUrl === 'string' const imageUrl =
? eventOrUrl typeof eventOrUrl === "string"
: (eventOrUrl.target as HTMLElement).getAttribute('src') || ''; ? eventOrUrl
: (eventOrUrl.target as HTMLElement).getAttribute("src") || "";
if (!imageUrl) return; if (!imageUrl) return;
// Check cache first // Check cache first
@@ -1624,16 +1642,16 @@ export default class HomeView extends Vue {
// If not in cache, load it // If not in cache, load it
try { try {
const response = await fetch(imageUrl); const response = await fetch(imageUrl);
if (!response.ok) throw new Error('Failed to load image'); if (!response.ok) throw new Error("Failed to load image");
const blob = await response.blob(); const blob = await response.blob();
await this.cacheImageData(imageUrl, blob); await this.cacheImageData(imageUrl, blob);
this.selectedImageData = blob; this.selectedImageData = blob;
this.selectedImage = imageUrl; this.selectedImage = imageUrl;
this.isImageViewerOpen = true; this.isImageViewerOpen = true;
} catch (error) { } catch (error) {
logger.error('Error loading image:', error); logger.error("Error loading image:", error);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",