forked from trent_larson/crowd-funder-for-time-pwa
Compare commits
37 Commits
why-migrat
...
home-view-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b8cd180a1 | ||
|
|
abc4a93b34 | ||
|
|
36493920f3 | ||
|
|
96fd4f68ef | ||
|
|
fc37f475d5 | ||
|
|
80b7ab2f82 | ||
|
|
5f2658fd01 | ||
|
|
267ed40946 | ||
|
|
1ce9e788e9 | ||
|
|
fe7b30ee32 | ||
|
|
ad3cb10722 | ||
|
|
d6dc7fbb10 | ||
|
|
cf2fec75ea | ||
|
|
9216be523a | ||
|
|
7ab3cb2d1f | ||
|
|
aba0d832db | ||
|
|
7906aab36e | ||
|
|
9b3d2b4c52 | ||
|
|
ceb12ef5bc | ||
|
|
805c7b4810 | ||
|
|
c344d37bd9 | ||
|
|
552ad5a267 | ||
|
|
910f57ec7d | ||
|
|
e813315dad | ||
| aea9626c06 | |||
|
|
7f0f1b7fc8 | ||
|
|
8684488def | ||
|
|
ee28b18b14 | ||
|
|
2d38183dce | ||
|
|
082a6eae1f | ||
|
|
d07fb47721 | ||
|
|
ccb6160bca | ||
|
|
2eaa4203aa | ||
|
|
f27a18c712 | ||
|
|
2c4a920c3c | ||
|
|
ed91cadd9d | ||
|
|
a6de282aec |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ node_modules
|
|||||||
signature.bin
|
signature.bin
|
||||||
*.pem
|
*.pem
|
||||||
verified.txt
|
verified.txt
|
||||||
|
myenv
|
||||||
|
|
||||||
*~
|
*~
|
||||||
# local env files
|
# local env files
|
||||||
|
|||||||
143
package-lock.json
generated
143
package-lock.json
generated
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "kickstart-for-time-pwa",
|
"name": "kickstart-for-time-pwa",
|
||||||
"version": "0.1.0",
|
"version": "0.1.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "kickstart-for-time-pwa",
|
"name": "kickstart-for-time-pwa",
|
||||||
"version": "0.1.0",
|
"version": "0.1.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersproject/hdnode": "^5.7.0",
|
"@ethersproject/hdnode": "^5.7.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||||
|
"@lionello/secp256k1-js": "^1.1.0",
|
||||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.0.0",
|
||||||
"@veramo/core": "^5.4.1",
|
"@veramo/core": "^5.4.1",
|
||||||
@@ -30,11 +31,13 @@
|
|||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"dexie-export-import": "^4.0.7",
|
"dexie-export-import": "^4.0.7",
|
||||||
"did-jwt": "^7.2.7",
|
"did-jwt": "^7.2.7",
|
||||||
|
"elliptic": "^6.5.4",
|
||||||
"ethereum-cryptography": "^2.1.2",
|
"ethereum-cryptography": "^2.1.2",
|
||||||
"ethereumjs-util": "^7.1.5",
|
"ethereumjs-util": "^7.1.5",
|
||||||
"ethr-did-resolver": "^8.1.2",
|
"ethr-did-resolver": "^8.1.2",
|
||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
|
"jssha": "^3.3.1",
|
||||||
"localstorage-slim": "^2.5.0",
|
"localstorage-slim": "^2.5.0",
|
||||||
"luxon": "^3.4.3",
|
"luxon": "^3.4.3",
|
||||||
"merkletreejs": "^0.3.10",
|
"merkletreejs": "^0.3.10",
|
||||||
@@ -52,6 +55,7 @@
|
|||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.2",
|
"vue-facing-decorator": "^3.0.2",
|
||||||
|
"vue-qrcode-reader": "^5.4.1",
|
||||||
"vue-router": "^4.2.4",
|
"vue-router": "^4.2.4",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27"
|
||||||
},
|
},
|
||||||
@@ -71,13 +75,13 @@
|
|||||||
"@vue/cli-service": "~5.0.8",
|
"@vue/cli-service": "~5.0.8",
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.15",
|
||||||
"eslint": "^8.48.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"postcss": "^8.4.29",
|
"postcss": "^8.4.29",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.1.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "~5.2.2"
|
"typescript": "~5.2.2"
|
||||||
}
|
}
|
||||||
@@ -2820,9 +2824,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
|
||||||
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
|
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
@@ -2870,9 +2874,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "8.51.0",
|
"version": "8.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
|
||||||
"integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==",
|
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
@@ -5497,12 +5501,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.11",
|
"version": "0.11.13",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
|
||||||
"integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
|
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanwhocodes/object-schema": "^1.2.1",
|
"@humanwhocodes/object-schema": "^2.0.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"minimatch": "^3.0.5"
|
"minimatch": "^3.0.5"
|
||||||
},
|
},
|
||||||
@@ -5524,9 +5528,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/object-schema": {
|
"node_modules/@humanwhocodes/object-schema": {
|
||||||
"version": "1.2.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
|
||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@jest/create-cache-key-function": {
|
"node_modules/@jest/create-cache-key-function": {
|
||||||
@@ -6057,6 +6061,22 @@
|
|||||||
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
|
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@lionello/secp256k1-js": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lionello/secp256k1-js/-/secp256k1-js-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-frvrdwwgWm0gq43rYcGwwZee+k5sX9HfWnB80740h1dvRT0FO0Z6Wt5C5I7bS0dXKy1pt6IomkR1VcXirP+g4Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": "^4.11.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lionello/secp256k1-js/node_modules/bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
|
},
|
||||||
"node_modules/@noble/ciphers": {
|
"node_modules/@noble/ciphers": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.3.0.tgz",
|
||||||
@@ -8676,6 +8696,16 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/dom-webcodecs": {
|
||||||
|
"version": "0.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.10.tgz",
|
||||||
|
"integrity": "sha512-qQfLMw4yhtagKQApMQKaf21KZeJu3Psysbm/wLQ3mkpyBWY3x3dHCKFcYs43WEH+s8zgTSF0DvJUPWTtyZP0Dw=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/emscripten": {
|
||||||
|
"version": "1.39.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.10.tgz",
|
||||||
|
"integrity": "sha512-TB/6hBkYQJxsZHSqyeuO1Jt0AB/bW6G7rHt9g7lML7SOF6lbgcHvw/Lr+69iqN0qxgXLhWKScAon73JNnptuDw=="
|
||||||
|
},
|
||||||
"node_modules/@types/eslint": {
|
"node_modules/@types/eslint": {
|
||||||
"version": "8.44.4",
|
"version": "8.44.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.4.tgz",
|
||||||
@@ -9195,6 +9225,12 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ungap/structured-clone": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@unimodules/core": {
|
"node_modules/@unimodules/core": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.2.tgz",
|
||||||
@@ -11515,6 +11551,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/barcode-detector": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-yA6gR5u5j22uw2eHSlFGzhYgnnQqx6hc4amDb/r0bKWl2gcDOqVE6SzUE6O87UzJ3ZhjJjM9uG/L9+D705HsKg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/dom-webcodecs": "^0.1.9",
|
||||||
|
"zxing-wasm": "1.0.0-rc.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/base-64": {
|
"node_modules/base-64": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||||
@@ -14200,18 +14245,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "8.51.0",
|
"version": "8.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
|
||||||
"integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==",
|
"integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
"@eslint/eslintrc": "^2.1.2",
|
"@eslint/eslintrc": "^2.1.3",
|
||||||
"@eslint/js": "8.51.0",
|
"@eslint/js": "8.53.0",
|
||||||
"@humanwhocodes/config-array": "^0.11.11",
|
"@humanwhocodes/config-array": "^0.11.13",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
|
"@ungap/structured-clone": "^1.2.0",
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@@ -18839,6 +18885,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jssha": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keccak": {
|
"node_modules/keccak": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz",
|
||||||
@@ -23117,9 +23171,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.0.3",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
||||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
@@ -24637,6 +24691,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
|
||||||
"integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="
|
"integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/sdp": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw=="
|
||||||
|
},
|
||||||
"node_modules/secp256k1": {
|
"node_modules/secp256k1": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz",
|
||||||
@@ -27264,6 +27323,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-qrcode-reader": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-jwETIaRdumSCnXOpp0BkpZW8sySNFUfIPNOFa8oHAEmoSSdKK/ub5C1+3vMwokjU8iNERR2v/YhfBdcWDe0s5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"barcode-detector": "2.1.1",
|
||||||
|
"webrtc-adapter": "8.2.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-router": {
|
"node_modules/vue-router": {
|
||||||
"version": "4.2.5",
|
"version": "4.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
|
||||||
@@ -27839,6 +27910,18 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webrtc-adapter": {
|
||||||
|
"version": "8.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz",
|
||||||
|
"integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"sdp": "^3.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0",
|
||||||
|
"npm": ">=3.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/websocket-driver": {
|
"node_modules/websocket-driver": {
|
||||||
"version": "0.7.4",
|
"version": "0.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
||||||
@@ -28644,6 +28727,14 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zxing-wasm": {
|
||||||
|
"version": "1.0.0-rc.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-1.0.0-rc.4.tgz",
|
||||||
|
"integrity": "sha512-SvVErHUZhzFqpqA2vpwmXeAPa6sgGdUCOkMCd5cMch6L1urZbZCZR8jb2+NI9bCfJRNkQi2ZjME9/NaiUFiSGg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/emscripten": "^1.39.9"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kickstart-for-time-pwa",
|
"name": "kickstart-for-time-pwa",
|
||||||
"version": "0.1.0",
|
"version": "0.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||||
|
"@lionello/secp256k1-js": "^1.1.0",
|
||||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.0.0",
|
||||||
"@veramo/core": "^5.4.1",
|
"@veramo/core": "^5.4.1",
|
||||||
@@ -30,11 +31,13 @@
|
|||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"dexie-export-import": "^4.0.7",
|
"dexie-export-import": "^4.0.7",
|
||||||
"did-jwt": "^7.2.7",
|
"did-jwt": "^7.2.7",
|
||||||
|
"elliptic": "^6.5.4",
|
||||||
"ethereum-cryptography": "^2.1.2",
|
"ethereum-cryptography": "^2.1.2",
|
||||||
"ethereumjs-util": "^7.1.5",
|
"ethereumjs-util": "^7.1.5",
|
||||||
"ethr-did-resolver": "^8.1.2",
|
"ethr-did-resolver": "^8.1.2",
|
||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
|
"jssha": "^3.3.1",
|
||||||
"localstorage-slim": "^2.5.0",
|
"localstorage-slim": "^2.5.0",
|
||||||
"luxon": "^3.4.3",
|
"luxon": "^3.4.3",
|
||||||
"merkletreejs": "^0.3.10",
|
"merkletreejs": "^0.3.10",
|
||||||
@@ -52,6 +55,7 @@
|
|||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.2",
|
"vue-facing-decorator": "^3.0.2",
|
||||||
|
"vue-qrcode-reader": "^5.4.1",
|
||||||
"vue-router": "^4.2.4",
|
"vue-router": "^4.2.4",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27"
|
||||||
},
|
},
|
||||||
@@ -71,13 +75,13 @@
|
|||||||
"@vue/cli-service": "~5.0.8",
|
"@vue/cli-service": "~5.0.8",
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.15",
|
||||||
"eslint": "^8.48.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"postcss": "^8.4.29",
|
"postcss": "^8.4.29",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.1.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "~5.2.2"
|
"typescript": "~5.2.2"
|
||||||
}
|
}
|
||||||
|
|||||||
253
src/App.vue
253
src/App.vue
@@ -162,17 +162,22 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
|
@click="
|
||||||
|
close(notification.id);
|
||||||
|
turnOnNotifications();
|
||||||
|
"
|
||||||
>
|
>
|
||||||
Turn on Notifications
|
Turn on Notifications
|
||||||
</button>
|
</button>
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
@click="maybeLater(notification.id)"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
>
|
>
|
||||||
Maybe Later
|
Maybe Later
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
@click="never(notification.id)"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md"
|
||||||
>
|
>
|
||||||
Never
|
Never
|
||||||
@@ -254,4 +259,248 @@
|
|||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
||||||
<script lang="ts"></script>
|
<script lang="ts">
|
||||||
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
import axios, { AxiosError } from "axios";
|
||||||
|
|
||||||
|
interface ServiceWorkerMessage {
|
||||||
|
type: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServiceWorkerResponse {
|
||||||
|
// Define the properties and their types
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example interface for error
|
||||||
|
interface ErrorResponse {
|
||||||
|
message: string;
|
||||||
|
// Other properties as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VapidResponse {
|
||||||
|
data: {
|
||||||
|
vapidKey: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class App extends Vue {
|
||||||
|
b64 = "";
|
||||||
|
mounted() {
|
||||||
|
axios
|
||||||
|
.get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid")
|
||||||
|
.then((response: VapidResponse) => {
|
||||||
|
this.b64 = response.data.vapidKey;
|
||||||
|
console.log(this.b64);
|
||||||
|
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||||
|
console.log("New service worker is now controlling the page");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error: AxiosError) => {
|
||||||
|
console.error("API error", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendMessageToServiceWorker(
|
||||||
|
message: ServiceWorkerMessage,
|
||||||
|
): Promise<unknown> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
|
messageChannel.port1.onmessage = (event: MessageEvent) => {
|
||||||
|
if (event.data.error) {
|
||||||
|
reject(event.data.error as ErrorResponse);
|
||||||
|
} else {
|
||||||
|
resolve(event.data as ServiceWorkerResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
navigator.serviceWorker.controller.postMessage(message, [
|
||||||
|
messageChannel.port2,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
reject("Service worker controller not available");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private askPermission(): Promise<NotificationPermission> {
|
||||||
|
if (!("serviceWorker" in navigator && navigator.serviceWorker.controller)) {
|
||||||
|
return Promise.reject("Service worker not available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = localStorage.getItem("secret");
|
||||||
|
if (!secret) {
|
||||||
|
return Promise.reject("No secret found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sendSecretToServiceWorker(secret)
|
||||||
|
.then(() => this.checkNotificationSupport())
|
||||||
|
.then(() => this.requestNotificationPermission())
|
||||||
|
.catch((error) => Promise.reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendSecretToServiceWorker(secret: string): Promise<void> {
|
||||||
|
const message: ServiceWorkerMessage = {
|
||||||
|
type: "SEND_LOCAL_DATA",
|
||||||
|
data: secret,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.sendMessageToServiceWorker(message).then((response) => {
|
||||||
|
console.log("Response from service worker:", response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkNotificationSupport(): Promise<void> {
|
||||||
|
if (!("Notification" in window)) {
|
||||||
|
alert("This browser does not support notifications.");
|
||||||
|
return Promise.reject("This browser does not support notifications.");
|
||||||
|
}
|
||||||
|
if (Notification.permission === "granted") {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private requestNotificationPermission(): Promise<NotificationPermission> {
|
||||||
|
return Notification.requestPermission().then((permission) => {
|
||||||
|
if (permission !== "granted") {
|
||||||
|
alert("We need notification permission to provide certain features.");
|
||||||
|
throw new Error("We weren't granted permission.");
|
||||||
|
}
|
||||||
|
return permission;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async turnOnNotifications() {
|
||||||
|
return this.askPermission()
|
||||||
|
.then((permission) => {
|
||||||
|
console.log("Permission granted:", permission);
|
||||||
|
|
||||||
|
// Call the function and handle promises
|
||||||
|
this.subscribeToPush()
|
||||||
|
.then(() => {
|
||||||
|
console.log("Subscribed successfully.");
|
||||||
|
// Assuming the subscription object is available
|
||||||
|
return navigator.serviceWorker.ready;
|
||||||
|
})
|
||||||
|
.then((registration) => {
|
||||||
|
// Fetch the existing subscription object from the registration
|
||||||
|
return registration.pushManager.getSubscription();
|
||||||
|
})
|
||||||
|
.then((subscription) => {
|
||||||
|
if (subscription) {
|
||||||
|
console.log(subscription);
|
||||||
|
return this.sendSubscriptionToServer(subscription);
|
||||||
|
} else {
|
||||||
|
throw new Error("Subscription object is not available.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log("Subscription data sent to server.");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
"Subscription or server communication failed:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("An error occurred:", error);
|
||||||
|
// Handle error appropriately here
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to convert URL base64 to Uint8Array
|
||||||
|
private urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||||
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||||
|
const base64 = (base64String + padding)
|
||||||
|
.replace(/-/g, "+")
|
||||||
|
.replace(/_/g, "/");
|
||||||
|
const rawData = window.atob(base64);
|
||||||
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
outputArray[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return outputArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private subscribeToPush(): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
if (!("serviceWorker" in navigator && "PushManager" in window)) {
|
||||||
|
const errorMsg = "Push messaging is not supported";
|
||||||
|
console.warn(errorMsg);
|
||||||
|
return reject(new Error(errorMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Notification.permission !== "granted") {
|
||||||
|
const errorMsg = "Notification permission not granted";
|
||||||
|
console.warn(errorMsg);
|
||||||
|
return reject(new Error(errorMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
const applicationServerKey = this.urlBase64ToUint8Array(this.b64);
|
||||||
|
const options: PushSubscriptionOptions = {
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: applicationServerKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
navigator.serviceWorker.ready
|
||||||
|
.then((registration) => {
|
||||||
|
return registration.pushManager.subscribe(options);
|
||||||
|
})
|
||||||
|
.then((subscription) => {
|
||||||
|
console.log("Push subscription successful:", subscription);
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
"Subscription or server communication failed:",
|
||||||
|
error,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inform the user about the issue
|
||||||
|
alert(
|
||||||
|
"We encountered an issue setting up push notifications. " +
|
||||||
|
"If you wish to revoke notification permissions, please do so in your browser settings.",
|
||||||
|
);
|
||||||
|
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendSubscriptionToServer(
|
||||||
|
subscription: PushSubscription,
|
||||||
|
): Promise<void> {
|
||||||
|
console.log(subscription);
|
||||||
|
return fetch("/web-push/subscribe", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(subscription),
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to send subscription to server");
|
||||||
|
}
|
||||||
|
console.log("Subscription sent to server successfully.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
never(ID: string) {
|
||||||
|
alert(ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeLater(ID: string) {
|
||||||
|
alert(ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -9,59 +9,39 @@ import {
|
|||||||
} from "./tables/settings";
|
} from "./tables/settings";
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
|
|
||||||
// a separate DB because the seed is super-sensitive data
|
// Define types for tables that hold sensitive and non-sensitive data
|
||||||
type SensitiveTables = {
|
type SensitiveTables = { accounts: Table<Account> };
|
||||||
accounts: Table<Account>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NonsensitiveTables = {
|
type NonsensitiveTables = {
|
||||||
contacts: Table<Contact>;
|
contacts: Table<Contact>;
|
||||||
settings: Table<Settings>;
|
settings: Table<Settings>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings
|
||||||
* In order to make the next line be acceptable, the program needs to have its linter suppress a rule:
|
|
||||||
* https://typescript-eslint.io/rules/no-unnecessary-type-constraint/
|
|
||||||
*
|
|
||||||
* and change *any* to *unknown*
|
|
||||||
*
|
|
||||||
* https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any
|
|
||||||
*/
|
|
||||||
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T;
|
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T;
|
||||||
export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
|
|
||||||
const SensitiveSchemas = Object.assign({}, AccountsSchema);
|
|
||||||
|
|
||||||
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> =
|
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> =
|
||||||
BaseDexie & T;
|
BaseDexie & T;
|
||||||
|
|
||||||
|
// Initialize Dexie databases for sensitive and non-sensitive data
|
||||||
|
export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
|
||||||
|
const SensitiveSchemas = { ...AccountsSchema };
|
||||||
|
|
||||||
export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;
|
export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;
|
||||||
const NonsensitiveSchemas = Object.assign({}, ContactsSchema, SettingsSchema);
|
const NonsensitiveSchemas = { ...ContactsSchema, ...SettingsSchema };
|
||||||
|
|
||||||
/**
|
// Manage the encryption key. If not present in localStorage, create and store it.
|
||||||
* Needed to enable a special webpack setting to allow *await* below:
|
|
||||||
* https://stackoverflow.com/questions/72474803/error-the-top-level-await-experiment-is-not-enabled-set-experiments-toplevelaw
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create password and place password in localStorage.
|
|
||||||
*
|
|
||||||
* It's good practice to keep the data encrypted at rest, so we'll do that even
|
|
||||||
* if the secret is stored right next to the app.
|
|
||||||
*/
|
|
||||||
const secret =
|
const secret =
|
||||||
localStorage.getItem("secret") || Encryption.createRandomEncryptionKey();
|
localStorage.getItem("secret") || Encryption.createRandomEncryptionKey();
|
||||||
|
if (!localStorage.getItem("secret")) localStorage.setItem("secret", secret);
|
||||||
|
|
||||||
if (localStorage.getItem("secret") == null) {
|
// Apply encryption to the sensitive database using the secret key
|
||||||
localStorage.setItem("secret", secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
encrypted(accountsDB, { secretKey: secret });
|
encrypted(accountsDB, { secretKey: secret });
|
||||||
accountsDB.version(1).stores(SensitiveSchemas);
|
|
||||||
|
|
||||||
|
// Define the schema for our databases
|
||||||
|
accountsDB.version(1).stores(SensitiveSchemas);
|
||||||
db.version(1).stores(NonsensitiveSchemas);
|
db.version(1).stores(NonsensitiveSchemas);
|
||||||
|
|
||||||
// initialize, a la https://dexie.org/docs/Tutorial/Design#the-populate-event
|
// Event handler to initialize the non-sensitive database with default settings
|
||||||
db.on("populate", function () {
|
db.on("populate", () => {
|
||||||
// ensure there's an initial entry for settings
|
|
||||||
db.settings.add({
|
db.settings.add({
|
||||||
id: MASTER_SETTINGS_KEY,
|
id: MASTER_SETTINGS_KEY,
|
||||||
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
|
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
|
||||||
|
|||||||
@@ -1,29 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* BoundingBox type describes the geographical bounding box coordinates.
|
||||||
|
*/
|
||||||
export type BoundingBox = {
|
export type BoundingBox = {
|
||||||
eastLong: number;
|
eastLong: number; // Eastern longitude
|
||||||
maxLat: number;
|
maxLat: number; // Maximum (Northernmost) latitude
|
||||||
minLat: number;
|
minLat: number; // Minimum (Southernmost) latitude
|
||||||
westLong: number;
|
westLong: number; // Western longitude
|
||||||
};
|
};
|
||||||
|
|
||||||
// a singleton
|
/**
|
||||||
|
* Settings type encompasses user-specific configuration details.
|
||||||
|
*/
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
id: number; // there's only one entry: MASTER_SETTINGS_KEY
|
id: number; // Only one entry using MASTER_SETTINGS_KEY
|
||||||
|
activeDid?: string; // Active Decentralized ID
|
||||||
activeDid?: string;
|
apiServer?: string; // API server URL
|
||||||
apiServer?: string;
|
firstName?: string; // User's first name
|
||||||
firstName?: string;
|
lastName?: string; // User's last name
|
||||||
|
lastViewedClaimId?: string; // Last viewed claim ID
|
||||||
|
lastNotifiedClaimId?: string; // Last notified claim ID
|
||||||
isRegistered?: boolean;
|
isRegistered?: boolean;
|
||||||
lastName?: string; // deprecated, pre v 0.1.3
|
|
||||||
lastViewedClaimId?: string;
|
// Array of named search boxes defined by bounding boxes
|
||||||
|
|
||||||
searchBoxes?: Array<{
|
searchBoxes?: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
bbox: BoundingBox;
|
bbox: BoundingBox;
|
||||||
}>;
|
}>;
|
||||||
showContactGivesInline?: boolean;
|
|
||||||
|
showContactGivesInline?: boolean; // Display contact inline or not
|
||||||
|
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
||||||
|
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
|
||||||
|
reminderOn?: boolean; // Toggle to enable or disable reminders
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema for the Settings table in the database.
|
||||||
|
*/
|
||||||
export const SettingsSchema = {
|
export const SettingsSchema = {
|
||||||
settings: "id",
|
settings: "id",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants.
|
||||||
|
*/
|
||||||
export const MASTER_SETTINGS_KEY = 1;
|
export const MASTER_SETTINGS_KEY = 1;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { register } from "register-service-worker";
|
import { register } from "register-service-worker";
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
register("/additional-scripts.js", {
|
||||||
ready() {
|
ready() {
|
||||||
console.log(
|
console.log(
|
||||||
"App is being served from cache by a service worker.\n" +
|
"App is being served from cache by a service worker.\n" +
|
||||||
|
|||||||
@@ -8,22 +8,38 @@
|
|||||||
|
|
||||||
<!-- show the actions for recognizing a give -->
|
<!-- show the actions for recognizing a give -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div v-if="!activeDid">
|
<div
|
||||||
To record others' giving,
|
v-if="!activeDid"
|
||||||
<router-link :to="{ name: 'start' }" class="text-blue-500">
|
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
|
||||||
create your identifier.</router-link
|
>
|
||||||
|
<p class="text-lg mb-3">
|
||||||
|
You need an <b>identifier</b> before you can record others' giving.
|
||||||
|
</p>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'start' }"
|
||||||
|
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
|
||||||
|
>
|
||||||
|
Create Your Identifier</router-link
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!isRegistered">
|
<div
|
||||||
To record others' giving, someone must register your account, so show
|
v-else-if="!isRegistered"
|
||||||
them
|
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
|
||||||
<router-link :to="{ name: 'contact-qr' }" class="text-blue-500">
|
|
||||||
your identity info</router-link
|
|
||||||
>
|
>
|
||||||
and then
|
Someone must register your account before you can record others' giving.
|
||||||
<router-link :to="{ name: 'account' }" class="text-blue-500">
|
To do this:
|
||||||
check your limits.</router-link
|
<router-link
|
||||||
|
:to="{ name: 'contact-qr' }"
|
||||||
|
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
|
||||||
|
>
|
||||||
|
1. Show Them Your Identity Info</router-link
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'account' }"
|
||||||
|
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
|
||||||
|
>
|
||||||
|
2. Check Your Limits</router-link
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
63
sw_scripts/additional-scripts.js
Normal file
63
sw_scripts/additional-scripts.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/* eslint-env serviceworker */
|
||||||
|
/* global workbox */
|
||||||
|
importScripts(
|
||||||
|
"https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js",
|
||||||
|
);
|
||||||
|
|
||||||
|
self.addEventListener("install", (event) => {
|
||||||
|
importScripts(
|
||||||
|
"safari-notifications.js",
|
||||||
|
"nacl.js",
|
||||||
|
"noble-curves.js",
|
||||||
|
"noble-hashes.js",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("push", function (event) {
|
||||||
|
event.waitUntil(
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
let payload;
|
||||||
|
if (event.data) {
|
||||||
|
payload = JSON.parse(event.data.text());
|
||||||
|
}
|
||||||
|
const message = await self.getNotificationCount();
|
||||||
|
const title = payload ? payload.title : "Custom Title";
|
||||||
|
const options = {
|
||||||
|
body: message,
|
||||||
|
icon: payload ? payload.icon : "icon.png",
|
||||||
|
badge: payload ? payload.badge : "badge.png",
|
||||||
|
};
|
||||||
|
await self.registration.showNotification(title, options);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in processing the push event:", error);
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("message", (event) => {
|
||||||
|
if (event.data && event.data.type === "SEND_LOCAL_DATA") {
|
||||||
|
self.secret = event.data.data;
|
||||||
|
event.ports[0].postMessage({ success: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("activate", (event) => {
|
||||||
|
event.waitUntil(clients.claim());
|
||||||
|
console.log("Service worker activated", event);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (event) => {
|
||||||
|
console.log(event.request);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("error", (event) => {
|
||||||
|
console.error("Error in Service Worker:", event.message);
|
||||||
|
console.error("File:", event.filename);
|
||||||
|
console.error("Line:", event.lineno);
|
||||||
|
console.error("Column:", event.colno);
|
||||||
|
console.error("Error Object:", event.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
|
||||||
1051
sw_scripts/nacl.js
Normal file
1051
sw_scripts/nacl.js
Normal file
File diff suppressed because it is too large
Load Diff
5248
sw_scripts/noble-curves.js
Normal file
5248
sw_scripts/noble-curves.js
Normal file
File diff suppressed because it is too large
Load Diff
3068
sw_scripts/noble-hashes.js
Normal file
3068
sw_scripts/noble-hashes.js
Normal file
File diff suppressed because it is too large
Load Diff
545
sw_scripts/safari-notifications.js
Normal file
545
sw_scripts/safari-notifications.js
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
function bufferFromBase64(base64) {
|
||||||
|
const binaryString = atob(base64);
|
||||||
|
const length = binaryString.length;
|
||||||
|
const bytes = new Uint8Array(length);
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromString(str, encoding = "utf8") {
|
||||||
|
if (encoding === "utf8") {
|
||||||
|
return new TextEncoder().encode(str);
|
||||||
|
} else if (encoding === "base16") {
|
||||||
|
if (str.length % 2 !== 0) {
|
||||||
|
throw new Error("Invalid hex string length.");
|
||||||
|
}
|
||||||
|
let bytes = new Uint8Array(str.length / 2);
|
||||||
|
for (let i = 0; i < str.length; i += 2) {
|
||||||
|
bytes[i / 2] = parseInt(str.substring(i, i + 2), 16);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
} else if (encoding === "base64url") {
|
||||||
|
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
while (str.length % 4) {
|
||||||
|
str += "=";
|
||||||
|
}
|
||||||
|
return new Uint8Array(bufferFromBase64(str));
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported encoding "${encoding}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Uint8Array to a string with the given encoding.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} byteArray - The Uint8Array to convert.
|
||||||
|
* @param {string} [encoding='utf8'] - The desired encoding ('utf8', 'base16', 'base64url').
|
||||||
|
* @returns {string} - The encoded string.
|
||||||
|
* @throws {Error} - Throws an error if the encoding is unsupported.
|
||||||
|
*/
|
||||||
|
function toString(byteArray, encoding = "utf8") {
|
||||||
|
switch (encoding) {
|
||||||
|
case "utf8":
|
||||||
|
return decodeUTF8(byteArray);
|
||||||
|
case "base16":
|
||||||
|
return toBase16(byteArray);
|
||||||
|
case "base64url":
|
||||||
|
return toBase64Url(byteArray);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported encoding "${encoding}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a Uint8Array as a UTF-8 string.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} byteArray
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function decodeUTF8(byteArray) {
|
||||||
|
return new TextDecoder().decode(byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Uint8Array to a base16 (hex) encoded string.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} byteArray
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function toBase16(byteArray) {
|
||||||
|
return Array.from(byteArray)
|
||||||
|
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Uint8Array to a base64url encoded string.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} byteArray
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function toBase64Url(byteArray) {
|
||||||
|
let uint8Array = new Uint8Array(byteArray);
|
||||||
|
let binaryString = "";
|
||||||
|
for (let i = 0; i < uint8Array.length; i++) {
|
||||||
|
binaryString += String.fromCharCode(uint8Array[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode to base64
|
||||||
|
let base64 = btoa(binaryString);
|
||||||
|
|
||||||
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const u8a = { toString, fromString };
|
||||||
|
|
||||||
|
function sha256(payload) {
|
||||||
|
const data = typeof payload === "string" ? u8a.fromString(payload) : payload;
|
||||||
|
return nobleHashes.sha256(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function accessToken(identifier) {
|
||||||
|
const did = identifier["did"];
|
||||||
|
const privateKeyHex = identifier["keys"][0]["privateKeyHex"];
|
||||||
|
|
||||||
|
const signer = await SimpleSigner(privateKeyHex);
|
||||||
|
|
||||||
|
const nowEpoch = Math.floor(Date.now() / 1000);
|
||||||
|
const endEpoch = nowEpoch + 60; // add one minute
|
||||||
|
|
||||||
|
const tokenPayload = { exp: endEpoch, iat: nowEpoch, iss: did };
|
||||||
|
const alg = undefined; // defaults to 'ES256K', more standardized but harder to verify vs ES256K-R
|
||||||
|
const jwt = await createJWT(tokenPayload, {
|
||||||
|
alg,
|
||||||
|
issuer: did,
|
||||||
|
signer,
|
||||||
|
});
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createJWT(payload, options, header = {}) {
|
||||||
|
const { issuer, signer, alg, expiresIn, canonicalize } = options;
|
||||||
|
|
||||||
|
if (!signer)
|
||||||
|
throw new Error(
|
||||||
|
"missing_signer: No Signer functionality has been configured",
|
||||||
|
);
|
||||||
|
if (!issuer)
|
||||||
|
throw new Error("missing_issuer: No issuing DID has been configured");
|
||||||
|
if (!header.typ) header.typ = "JWT";
|
||||||
|
if (!header.alg) header.alg = alg;
|
||||||
|
|
||||||
|
const timestamps = {
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
exp: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (expiresIn) {
|
||||||
|
if (typeof expiresIn === "number") {
|
||||||
|
timestamps.exp = (payload.nbf || timestamps.iat) + Math.floor(expiresIn);
|
||||||
|
} else {
|
||||||
|
throw new Error("invalid_argument: JWT expiresIn is not a number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullPayload = { ...timestamps, ...payload, iss: issuer };
|
||||||
|
return createJWS(fullPayload, signer, header, { canonicalize });
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAlg = "ES256K";
|
||||||
|
|
||||||
|
async function createJWS(payload, signer, header = {}, options = {}) {
|
||||||
|
if (!header.alg) header.alg = defaultAlg;
|
||||||
|
const encodedPayload =
|
||||||
|
typeof payload === "string"
|
||||||
|
? payload
|
||||||
|
: encodeSection(payload, options.canonicalize);
|
||||||
|
const signingInput = [
|
||||||
|
encodeSection(header, options.canonicalize),
|
||||||
|
encodedPayload,
|
||||||
|
].join(".");
|
||||||
|
|
||||||
|
const jwtSigner = ES256KSignerAlg(false);
|
||||||
|
const signature = await jwtSigner(signingInput, signer);
|
||||||
|
|
||||||
|
// JWS Compact Serialization
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7515#section-7.1
|
||||||
|
return [signingInput, signature].join(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalizeData(object) {
|
||||||
|
if (typeof object === "number" && isNaN(object)) {
|
||||||
|
throw new Error("NaN is not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof object === "number" && !isFinite(object)) {
|
||||||
|
throw new Error("Infinity is not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object === null || typeof object !== "object") {
|
||||||
|
return JSON.stringify(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object.toJSON instanceof Function) {
|
||||||
|
return serialize(object.toJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(object)) {
|
||||||
|
const values = object.reduce((t, cv, ci) => {
|
||||||
|
const comma = ci === 0 ? "" : ",";
|
||||||
|
const value = cv === undefined || typeof cv === "symbol" ? null : cv;
|
||||||
|
return `${t}${comma}${serialize(value)}`;
|
||||||
|
}, "");
|
||||||
|
return `[${values}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = Object.keys(object)
|
||||||
|
.sort()
|
||||||
|
.reduce((t, cv) => {
|
||||||
|
if (object[cv] === undefined || typeof object[cv] === "symbol") {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
const comma = t.length === 0 ? "" : ",";
|
||||||
|
return `${t}${comma}${serialize(cv)}:${serialize(object[cv])}`;
|
||||||
|
}, "");
|
||||||
|
return `{${values}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeSection(data, shouldCanonicalize = false) {
|
||||||
|
if (shouldCanonicalize) {
|
||||||
|
return encodeBase64url(canonicalizeData(data));
|
||||||
|
} else {
|
||||||
|
return encodeBase64url(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeBase64url(s) {
|
||||||
|
return bytesToBase64url(u8a.fromString(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
function instanceOfEcdsaSignature(object) {
|
||||||
|
return typeof object === "object" && "r" in object && "s" in object;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ES256KSignerAlg(recoverable) {
|
||||||
|
return async function sign(payload, signer) {
|
||||||
|
const signature = await signer(payload);
|
||||||
|
if (instanceOfEcdsaSignature(signature)) {
|
||||||
|
return toJose(signature, recoverable);
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
recoverable &&
|
||||||
|
typeof fromJose(signature).recoveryParam === "undefined"
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`not_supported: ES256K-R not supported when signer doesn't provide a recovery param`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function leftpad(data, size = 64) {
|
||||||
|
if (data.length === size) return data;
|
||||||
|
return "0".repeat(size - data.length) + data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function SimpleSigner(hexPrivateKey) {
|
||||||
|
const signer = await ES256KSigner(hexToBytes(hexPrivateKey), true);
|
||||||
|
return async (data) => {
|
||||||
|
const signature = await signer(data);
|
||||||
|
return fromJose(signature);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToBytes(s, minLength) {
|
||||||
|
let input = s.startsWith("0x") ? s.substring(2) : s;
|
||||||
|
|
||||||
|
if (input.length % 2 !== 0) {
|
||||||
|
input = `0${input}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minLength) {
|
||||||
|
const paddedLength = Math.max(input.length, minLength * 2);
|
||||||
|
input = input.padStart(paddedLength, "00");
|
||||||
|
}
|
||||||
|
|
||||||
|
return u8a.fromString(input.toLowerCase(), "base16");
|
||||||
|
}
|
||||||
|
|
||||||
|
function ES256KSigner(privateKey, recoverable = false) {
|
||||||
|
const privateKeyBytes = privateKey;
|
||||||
|
if (privateKeyBytes.length !== 32) {
|
||||||
|
throw new Error(
|
||||||
|
`bad_key: Invalid private key format. Expecting 32 bytes, but got ${privateKeyBytes.length}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return async function (data) {
|
||||||
|
const signature = nobleCurves.secp256k1.sign(sha256(data), privateKeyBytes);
|
||||||
|
return toJose(
|
||||||
|
{
|
||||||
|
r: leftpad(signature.r.toString(16)),
|
||||||
|
s: leftpad(signature.s.toString(16)),
|
||||||
|
recoveryParam: signature.recovery,
|
||||||
|
},
|
||||||
|
recoverable,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toJose(signature, recoverable) {
|
||||||
|
const { r, s, recoveryParam } = signature;
|
||||||
|
const jose = new Uint8Array(recoverable ? 65 : 64);
|
||||||
|
jose.set(u8a.fromString(r, "base16"), 0);
|
||||||
|
jose.set(u8a.fromString(s, "base16"), 32);
|
||||||
|
|
||||||
|
if (recoverable) {
|
||||||
|
if (typeof recoveryParam === "undefined") {
|
||||||
|
throw new Error("Signer did not return a recoveryParam");
|
||||||
|
}
|
||||||
|
jose[64] = recoveryParam;
|
||||||
|
}
|
||||||
|
return bytesToBase64url(jose);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToBase64url(b) {
|
||||||
|
return u8a.toString(b, "base64url");
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64ToBytes(s) {
|
||||||
|
const inputBase64Url = s
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
.replace(/=/g, "");
|
||||||
|
return u8a.fromString(inputBase64Url, "base64url");
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToHex(b) {
|
||||||
|
return u8a.toString(b, "base16");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromJose(signature) {
|
||||||
|
const signatureBytes = base64ToBytes(signature);
|
||||||
|
if (signatureBytes.length < 64 || signatureBytes.length > 65) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const r = bytesToHex(signatureBytes.slice(0, 32));
|
||||||
|
const s = bytesToHex(signatureBytes.slice(32, 64));
|
||||||
|
const recoveryParam =
|
||||||
|
signatureBytes.length === 65 ? signatureBytes[64] : undefined;
|
||||||
|
|
||||||
|
return { r, s, recoveryParam };
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateBase64(s) {
|
||||||
|
if (
|
||||||
|
!/^(?:[A-Za-z0-9+\/]{2}[A-Za-z0-9+\/]{2})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/.test(
|
||||||
|
s,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new TypeError("invalid encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeBase64(s) {
|
||||||
|
validateBase64(s);
|
||||||
|
var i,
|
||||||
|
d = atob(s),
|
||||||
|
b = new Uint8Array(d.length);
|
||||||
|
for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSettingById(id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let openRequest = indexedDB.open("TimeSafari");
|
||||||
|
|
||||||
|
openRequest.onupgradeneeded = (event) => {
|
||||||
|
// Handle database setup if necessary
|
||||||
|
let db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains("settings")) {
|
||||||
|
db.createObjectStore("settings", { keyPath: "id" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openRequest.onsuccess = (event) => {
|
||||||
|
let db = event.target.result;
|
||||||
|
let transaction = db.transaction("settings", "readonly");
|
||||||
|
let objectStore = transaction.objectStore("settings");
|
||||||
|
let getRequest = objectStore.get(id);
|
||||||
|
|
||||||
|
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||||
|
getRequest.onerror = () => reject(getRequest.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
openRequest.onerror = () => reject(openRequest.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function setMostRecentNotified(id) {
|
||||||
|
try {
|
||||||
|
const db = await openIndexedDB("TimeSafari");
|
||||||
|
const transaction = db.transaction("settings", "readwrite");
|
||||||
|
const store = transaction.objectStore("settings");
|
||||||
|
|
||||||
|
const data = await getRecord(store, 1);
|
||||||
|
if (data) {
|
||||||
|
data["lastNotifiedClaimId"] = id;
|
||||||
|
await updateRecord(store, data);
|
||||||
|
} else {
|
||||||
|
console.error("Record not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.oncomplete = () => db.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Database error: " + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function openIndexedDB(dbName) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open(dbName);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onupgradeneeded = (event) => {
|
||||||
|
const db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains("settings")) {
|
||||||
|
db.createObjectStore("settings");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getRecord(store, key) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = store.get(key);
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateRecord(store, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = store.put(data);
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchAllAccounts() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let openRequest = indexedDB.open("TimeSafariAccounts");
|
||||||
|
|
||||||
|
openRequest.onupgradeneeded = function (event) {
|
||||||
|
let db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains("accounts")) {
|
||||||
|
db.createObjectStore("accounts", { keyPath: "id" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openRequest.onsuccess = function (event) {
|
||||||
|
let db = event.target.result;
|
||||||
|
let transaction = db.transaction("accounts", "readonly");
|
||||||
|
let objectStore = transaction.objectStore("accounts");
|
||||||
|
let getAllRequest = objectStore.getAll();
|
||||||
|
|
||||||
|
getAllRequest.onsuccess = function () {
|
||||||
|
resolve(getAllRequest.result);
|
||||||
|
};
|
||||||
|
getAllRequest.onerror = function () {
|
||||||
|
reject(getAllRequest.error);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
openRequest.onerror = function () {
|
||||||
|
reject(openRequest.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNotificationCount() {
|
||||||
|
let secret = null;
|
||||||
|
let accounts = [];
|
||||||
|
let result = null;
|
||||||
|
if ("secret" in self) {
|
||||||
|
secret = self.secret;
|
||||||
|
const secretUint8Array = self.decodeBase64(secret);
|
||||||
|
const settings = await getSettingById(1);
|
||||||
|
let lastNotifiedClaimId = null;
|
||||||
|
if ("lastNotifiedClaimId" in settings) {
|
||||||
|
lastNotifiedClaimId = settings["lastNotifiedClaimId"];
|
||||||
|
}
|
||||||
|
const activeDid = settings["activeDid"];
|
||||||
|
accounts = await fetchAllAccounts();
|
||||||
|
let did = null;
|
||||||
|
for (var i = 0; i < accounts.length; i++) {
|
||||||
|
let account = accounts[i];
|
||||||
|
let did = account["did"];
|
||||||
|
if (did == activeDid) {
|
||||||
|
let publicKeyHex = account["publicKeyHex"];
|
||||||
|
let identity = account["identity"];
|
||||||
|
const messageWithNonceAsUint8Array = self.decodeBase64(identity);
|
||||||
|
const nonce = messageWithNonceAsUint8Array.slice(0, 24);
|
||||||
|
const message = messageWithNonceAsUint8Array.slice(24, identity.length);
|
||||||
|
const decoder = new TextDecoder("utf-8");
|
||||||
|
const decrypted = self.secretbox.open(message, nonce, secretUint8Array);
|
||||||
|
|
||||||
|
const msg = decoder.decode(decrypted);
|
||||||
|
const identifier = JSON.parse(JSON.parse(msg));
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
headers["Authorization"] = "Bearer " + (await accessToken(identifier));
|
||||||
|
|
||||||
|
let response = await fetch(
|
||||||
|
"https://test-api.endorser.ch/api/v2/report/claims",
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: headers,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (response.status == 200) {
|
||||||
|
let json = await response.json();
|
||||||
|
let claims = json["data"];
|
||||||
|
let newClaims = 0;
|
||||||
|
for (var i = 0; i < claims.length; i++) {
|
||||||
|
let claim = claims[i];
|
||||||
|
if (claim["id"] === lastNotifiedClaimId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
newClaims++;
|
||||||
|
}
|
||||||
|
if (newClaims === 0) {
|
||||||
|
result = "You have no new claims today.";
|
||||||
|
} else {
|
||||||
|
result = `${newClaims} have been shared with you`;
|
||||||
|
}
|
||||||
|
const most_recent_notified = claims[0]["id"];
|
||||||
|
await setMostRecentNotified(most_recent_notified);
|
||||||
|
return "TEST";
|
||||||
|
} else {
|
||||||
|
console.error(response.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.getNotificationCount = getNotificationCount;
|
||||||
|
self.decodeBase64 = decodeBase64;
|
||||||
@@ -11,5 +11,9 @@ module.exports = defineConfig({
|
|||||||
iconPaths: {
|
iconPaths: {
|
||||||
faviconSVG: "img/icons/safari-pinned-tab.svg",
|
faviconSVG: "img/icons/safari-pinned-tab.svg",
|
||||||
},
|
},
|
||||||
|
workboxPluginMode: "InjectManifest",
|
||||||
|
workboxOptions: {
|
||||||
|
swSrc: "./sw_scripts/additional-scripts.js",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user