Compare commits

...

35 Commits
0.1.8 ... 0.2.1

Author SHA1 Message Date
60fec5763d bump version to 0.2.1 2024-01-05 12:48:38 -07:00
aeb1d6a6a5 add next-public-key-hash to manual input 2024-01-05 12:44:28 -07:00
ec6175a550 make a confirmation for contact visibility 2024-01-05 12:14:56 -07:00
c1361e088f render full claim details in a more resonable format of YAML not JSON 2024-01-05 11:20:16 -07:00
a2c986951e doc: refactor project tasks 2024-01-05 11:11:06 -07:00
dce7b8e3d9 add terms & conditions, and a note about data in this service 2024-01-05 10:34:13 -07:00
211e0487fe add verbiage for other non-Chrome cases 2024-01-05 09:56:29 -07:00
cc931dcb04 add notification check with instructions on front screen 2024-01-05 09:48:15 -07:00
bfe14cc9c2 increment version and add -beta 2024-01-04 10:40:15 -07:00
275dba4468 bump version to 0.2.0 2024-01-04 10:35:32 -07:00
1f05e81b05 add notification immediately after setting up subscription, and tweak messaging 2024-01-03 21:27:13 -07:00
e9ad68f2a5 add maskable images 2024-01-03 18:32:38 -07:00
934664b9c9 add the hashed-next-key to the contact data, shown & stored 2024-01-03 17:44:41 -07:00
780be59c76 remove name from identity switcher (since they are not tied to a DID) 2024-01-02 19:53:19 -07:00
4a0bedb628 fix one more list-outside indent 2024-01-02 19:27:13 -07:00
5689f95230 change list-inside to list-outside 2024-01-02 19:21:57 -07:00
3083bb084a add more notification help instructions; remove confusing, big name-edit button 2024-01-02 19:16:54 -07:00
821d27a58a Merge pull request 'Set max screen content width' (#102) from app-screen-max-width into master
Reviewed-on: #102
2024-01-02 10:24:09 -05:00
Jose Olarte III
998a1d312f Set max screen content width 2024-01-02 15:25:51 +08:00
1f13bf772c move wait for service-worker initialization into the notification modal 2024-01-01 20:36:23 -07:00
def744b3df don't allow notification service-worker interaction until it is ready 2024-01-01 20:04:37 -07:00
0fb37acb24 increment version and add -beta 2024-01-01 19:38:07 -07:00
20bb723f0b bump to version 0.1.9 2024-01-01 19:34:34 -07:00
d821a7bd59 Merge pull request 'make a backup download for browsers that don't get it automatically' (#101) from another-download into master
Reviewed-on: #101
2024-01-01 21:28:22 -05:00
9f3b7314e8 Merge branch 'master' into another-download 2024-01-01 19:27:52 -07:00
4df7bb58a4 add help for clearing data, plus some other help fixes 2024-01-01 17:09:30 -07:00
15ccd2394f add missing 'date' to log interface 2024-01-01 16:25:23 -07:00
920c7bb612 adjust look & message on DB download 2024-01-01 16:24:36 -07:00
6eb26ea90c remove IndexedDB keys that shouldn't be keys, and remove unused table, and add commentary 2024-01-01 16:24:30 -07:00
28b6d9bbf9 doc: reorganize top tasks 2023-12-31 19:58:51 -05:00
7a099183ae remove db.close calls that caused an error when trying to download 2023-12-29 13:14:35 -07:00
11070755d6 refine error message when duplicate contact is input 2023-12-29 13:10:07 -07:00
c79dfac1fe add a way to import contacts & settings 2023-12-29 12:46:47 -07:00
2b06c64664 make a backup download for browsers that don't get it automatically 2023-12-28 14:42:56 -07:00
769a928b3d increment version & add "-beta" 2023-12-27 20:03:59 -07:00
45 changed files with 742 additions and 343 deletions

View File

@@ -9,7 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.1.8] - 2023.12.27
## [0.2.1] - 2024.01.05
### Added
- Check for notification capability on front screen
- Contact next-public-key-hash in manual textual input
- Confirmation for contact visibility change
- YAML rendering of full claim details
## [0.2.0] - 2024.01.04
### Added
- Contact next-public-key-hash
- Icon for Android
- More thorough messaging and testing for notifications
## [0.1.9] - 2024.01.01
### Added
- Import for contacts and settings
- Second download button for DuckDuckGo
### Changed
- Removed some keys from Dexie's IndexedDB declarations
## [0.1.8] - 2023.12.27- d26d1d360152a7d0e559b68486e85b72b88bd9ff
### Added
- DB logging for service-worker events
- Help page for notifications

View File

@@ -22,25 +22,25 @@ npm run lint
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
* Update the CHANGELOG.md & the version in package.json, run `npm install`, and commit.
* Update the project.task.yaml & CHANGELOG.md & the version in package.json, run `npm install`, and commit.
* [Tag wth the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
* If production, change src/constants/app.ts DEFAULT_*_SERVER to be "PROD" and package.json to remove "_Test".
* If production, change src/constants/app.ts DEFAULT_*_SERVER to be "PROD" and package.json to remove "_Test". Also record what version is on production.
* `npm run build`
* `npx prettier --write ./sw_scripts/`
...to make sure the service worker scripts are in proper form. It's only important if you changed something in that directory.
...to make sure the service worker scripts are in proper form. (It's only important if you changed something in that directory.)
* `cp sw_scripts/[ns]* dist/`
... to copy the contents of the `sw_scripts` folder to the `dist` folder - except additional_scripts.js.
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntu@endorser.ch:time-safari`
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari`
* Revert src/constants/app.ts and/or package.json, edit package.json to increment version & add "-beta", `npm install`, and commit.
* Revert src/constants/app.ts and package.json (if that was prod), edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
@@ -113,9 +113,9 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
### Clear/Reset data & restart
* Clear cache for site. (In Chrome, go to `chrome://settings/cookies` and "all site data and permissions"; in Firefox, go to `about:preferences` and search for "cache" then "Manage Data", and also manually remove the IndexedDB data if the DBs still show.)
* Clear notification permission. (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search for "notifications".)
* Unregister service worker. (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers`.)
* Clear Cache Storage. (in Chrome, in dev tools under Application; in Firefox, in dev tools under Storage.)
* Clear notification permission. (In Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search for "notifications".)
* Unregister service worker. (In Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers`.)
* Clear Cache Storage manually, possibly deleting the DB. (In Chrome, in dev tools under Application; in Firefox, in dev tools under Storage.)
(If you find more, add them to the HelpNotificationsView.vue file.)

31
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "TimeSafari_Test",
"version": "0.1.8",
"version": "0.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "TimeSafari_Test",
"version": "0.1.8",
"version": "0.2.1",
"dependencies": {
"@ethersproject/hdnode": "^5.7.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
@@ -14,6 +14,7 @@
"@fortawesome/vue-fontawesome": "^3.0.3",
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@tweenjs/tween.js": "^21.0.0",
"@types/js-yaml": "^4.0.9",
"@veramo/core": "^5.4.1",
"@veramo/credential-w3c": "^5.4.1",
"@veramo/data-store": "^5.4.1",
@@ -36,6 +37,7 @@
"git-describe": "^4.1.1",
"jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9",
"js-yaml": "^4.1.0",
"localstorage-slim": "^2.5.0",
"luxon": "^3.4.3",
"merkletreejs": "^0.3.10",
@@ -50,6 +52,7 @@
"reflect-metadata": "^0.1.13",
"register-service-worker": "^1.7.2",
"three": "^0.156.1",
"ua-parser-js": "^1.0.37",
"util": "^0.12.5",
"vue": "^3.3.4",
"vue-axios": "^3.5.2",
@@ -62,6 +65,7 @@
"@types/leaflet": "^1.9.4",
"@types/ramda": "^0.29.3",
"@types/three": "^0.155.1",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -8793,6 +8797,11 @@
"@types/istanbul-lib-report": "*"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="
},
"node_modules/@types/json-schema": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
@@ -8975,6 +8984,12 @@
"integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==",
"dev": true
},
"node_modules/@types/ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
"integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==",
"dev": true
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz",
@@ -11061,8 +11076,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"devOptional": true
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/array-buffer-byte-length": {
"version": "1.0.0",
@@ -18610,7 +18624,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"devOptional": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -26834,9 +26847,9 @@
}
},
"node_modules/ua-parser-js": {
"version": "1.0.36",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.36.tgz",
"integrity": "sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==",
"version": "1.0.37",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
"integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==",
"funding": [
{
"type": "opencollective",
@@ -26851,8 +26864,6 @@
"url": "https://github.com/sponsors/faisalman"
}
],
"optional": true,
"peer": true,
"engines": {
"node": "*"
}

View File

@@ -1,6 +1,6 @@
{
"name": "TimeSafari_Test",
"version": "0.1.8",
"version": "0.2.1",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -14,6 +14,7 @@
"@fortawesome/vue-fontawesome": "^3.0.3",
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@tweenjs/tween.js": "^21.0.0",
"@types/js-yaml": "^4.0.9",
"@veramo/core": "^5.4.1",
"@veramo/credential-w3c": "^5.4.1",
"@veramo/data-store": "^5.4.1",
@@ -36,6 +37,7 @@
"git-describe": "^4.1.1",
"jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9",
"js-yaml": "^4.1.0",
"localstorage-slim": "^2.5.0",
"luxon": "^3.4.3",
"merkletreejs": "^0.3.10",
@@ -50,6 +52,7 @@
"reflect-metadata": "^0.1.13",
"register-service-worker": "^1.7.2",
"three": "^0.156.1",
"ua-parser-js": "^1.0.37",
"util": "^0.12.5",
"vue": "^3.3.4",
"vue-axios": "^3.5.2",
@@ -62,6 +65,7 @@
"@types/leaflet": "^1.9.4",
"@types/ramda": "^0.29.3",
"@types/three": "^0.155.1",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@vue-leaflet/vue-leaflet": "^0.10.1",

View File

@@ -1,41 +1,19 @@
tasks:
- 08 notifications :
- .2 after turning on notification, don't wait in push server but wait in client for message test
- insert tooling (exportable logs?) so that we can see problems and troubleshoot as we onboard
- if navigator.serviceWorker is null, then tell the user to wait
- Local install works after cleared out cache in Chrome
- fix maskable icon
- give users next public key hash
- .3 bug - make or edit a project, choose "Include location", and see the map display shows on top of the bottom icons assignee-group:ui
- .5 If notifications are not enabled, add message to front page with link/button to enable
- show VC details... somehow:
- 01 show my VCs - most interesting, or via search
- 01 allow download of each VC (& confirmations, to show that they actually own their data)
- 04 allow user to download VCs, mine + ones I can see about me from others
- add VC confirmation?
- 04 allow user to download chains of VCs, mine + ones I can see about me from others
- add VC confirmation
- Release Minimum Viable Product :
- .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions)
- 08 thorough testing for errors & edge cases
- 01 ensure ability to recover server remotely, and add redundant access
- Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot).
- Add disclaimers.
- Switch default server to the public server.
- Deploy to a server.
- Ensure public server has limits that work for group adoption.
- Test PWA features on Android and iOS.
- Other features - donation vs give, show offers, show give & outstanding totals, show network view, restrict registration, connect to contacts
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
- look for "if is a new user" -- blank name
- copy button for seed
- .5 If notifications are not enabled, add message to front page with link/button to enable
- record donations vs gives
- make server endpoint for full English description of limits
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
- 02 watch for the service worker activation before showing the button to turn on notifications
- 01 server - show all claim details when issued by the issuer
- bug - got error adding on Firefox user #0 as contact for themselves
- bug (that is hard to reproduce) - back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity
@@ -66,6 +44,7 @@ tasks:
- 01 Import all the non-sensitive data (ie. contacts & settings).
- .2 show error to user when adding a duplicate contact
- 01 parse input more robustly (with CSV lib and not commas)
- stats v1 :
- 01 show numeric stats
- 04 show different graphic for projects vs people (gnome?) on world
@@ -83,6 +62,9 @@ tasks:
- 32 accept images for contacts
- import project interactions from GitHub/GitLab and manage signing
- show total time offered to & fulfilled to a project
- show total time offered by & fulfilled by a contact
- linking between projects or plans :
- show total time given to & from a project
- terminology:
@@ -104,6 +86,7 @@ tasks:
- automated tests, eg. cypress
- Notifications (wake on the phone, push notifications)
- 02 change push server so that the web-push/subscribe call sets up a thread for the 10-seconds-later push notification, but returns immediately to the callee
- pull instead of push, maybe via scheduled runs
- have a notification pop-up on Mac screen

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -156,11 +156,17 @@
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
>
<div class="w-full px-6 py-6 text-slate-900 text-center">
<p class="text-lg mb-4">
<p v-if="serviceWorkerReady" class="text-lg mb-4">
Would you like to <b>turn on</b> notifications for this app?
</p>
<p v-else class="text-lg mb-4">
Waiting for system initialization, which may take up to 10
seconds...
<fa icon="spinner" spin />
</p>
<button
v-if="serviceWorkerReady"
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);
@@ -169,6 +175,7 @@
>
Turn on Notifications
</button>
<button
@click="close(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
@@ -284,6 +291,7 @@ interface VapidResponse {
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { sendTestThroughPushServer } from "@/libs/util";
interface Notification {
group: string;
@@ -297,6 +305,7 @@ export default class App extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
b64 = "";
serviceWorkerReady = false;
async mounted() {
try {
@@ -343,6 +352,10 @@ export default class App extends Vue {
);
}
}
// there may be a long pause here on first initialization
navigator.serviceWorker.ready.then(() => {
this.serviceWorkerReady = true;
});
}
private sendMessageToServiceWorker(
@@ -435,23 +448,34 @@ export default class App extends Vue {
.then((registration) => {
return registration.pushManager.getSubscription();
})
.then((subscription) => {
.then(async (subscription) => {
if (subscription) {
return this.sendSubscriptionToServer(subscription);
await this.$notify(
{
group: "alert",
type: "info",
title: "Notification Setup Underway",
text: "Setting up notifications for interesting activity, which takes about 10 seconds. If you don't see a final confirmation, check the 'Troubleshoot' page.",
},
-1,
);
this.sendSubscriptionToServer(subscription);
return subscription;
} else {
throw new Error("Subscription object is not available.");
}
})
.then(() => {
.then(async (subscription) => {
console.log(
"Subscription data sent to server and all finished successfully.",
);
await sendTestThroughPushServer(subscription, true);
this.$notify(
{
group: "alert",
type: "success",
title: "Notifications Turned On",
text: "Notifications are on. You should see one on your device; if not, see the 'Troubleshoot' page.",
text: "Notifications are on. You should see at least one on your device; if not, check the 'Troubleshoot' page.",
},
-1,
);

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" enable-background="new 0 0 50 50">
<path d="M30.3 13.7L25 8.4l-5.3 5.3-1.4-1.4L25 5.6l6.7 6.7z"/><path d="M24 7h2v21h-2z"/><path d="M35 40H15c-1.7 0-3-1.3-3-3V19c0-1.7 1.3-3 3-3h7v2h-7c-.6 0-1 .4-1 1v18c0 .6.4 1 1 1h20c.6 0 1-.4 1-1V19c0-.6-.4-1-1-1h-7v-2h7c1.7 0 3 1.3 3 3v18c0 1.7-1.3 3-3 3z"/>
</svg>

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="5.5 -3.5 64 64" enable-background="new 5.5 -3.5 64 64" xml:space="preserve">
<g>
<circle fill="#FFFFFF" cx="37.785" cy="28.501" r="28.836"/>
<path d="M37.441-3.5c8.951,0,16.572,3.125,22.857,9.372c3.008,3.009,5.295,6.448,6.857,10.314
c1.561,3.867,2.344,7.971,2.344,12.314c0,4.381-0.773,8.486-2.314,12.313c-1.543,3.828-3.82,7.21-6.828,10.143
c-3.123,3.085-6.666,5.448-10.629,7.086c-3.961,1.638-8.057,2.457-12.285,2.457s-8.276-0.808-12.143-2.429
c-3.866-1.618-7.333-3.961-10.4-7.027c-3.067-3.066-5.4-6.524-7-10.372S5.5,32.767,5.5,28.5c0-4.229,0.809-8.295,2.428-12.2
c1.619-3.905,3.972-7.4,7.057-10.486C21.08-0.394,28.565-3.5,37.441-3.5z M37.557,2.272c-7.314,0-13.467,2.553-18.458,7.657
c-2.515,2.553-4.448,5.419-5.8,8.6c-1.354,3.181-2.029,6.505-2.029,9.972c0,3.429,0.675,6.734,2.029,9.913
c1.353,3.183,3.285,6.021,5.8,8.516c2.514,2.496,5.351,4.399,8.515,5.715c3.161,1.314,6.476,1.971,9.943,1.971
c3.428,0,6.75-0.665,9.973-1.999c3.219-1.335,6.121-3.257,8.713-5.771c4.99-4.876,7.484-10.99,7.484-18.344
c0-3.543-0.648-6.895-1.943-10.057c-1.293-3.162-3.18-5.98-5.654-8.458C50.984,4.844,44.795,2.272,37.557,2.272z M37.156,23.187
l-4.287,2.229c-0.458-0.951-1.019-1.619-1.685-2c-0.667-0.38-1.286-0.571-1.858-0.571c-2.856,0-4.286,1.885-4.286,5.657
c0,1.714,0.362,3.084,1.085,4.113c0.724,1.029,1.791,1.544,3.201,1.544c1.867,0,3.181-0.915,3.944-2.743l3.942,2
c-0.838,1.563-2,2.791-3.486,3.686c-1.484,0.896-3.123,1.343-4.914,1.343c-2.857,0-5.163-0.875-6.915-2.629
c-1.752-1.752-2.628-4.19-2.628-7.313c0-3.048,0.886-5.466,2.657-7.257c1.771-1.79,4.009-2.686,6.715-2.686
C32.604,18.558,35.441,20.101,37.156,23.187z M55.613,23.187l-4.229,2.229c-0.457-0.951-1.02-1.619-1.686-2
c-0.668-0.38-1.307-0.571-1.914-0.571c-2.857,0-4.287,1.885-4.287,5.657c0,1.714,0.363,3.084,1.086,4.113
c0.723,1.029,1.789,1.544,3.201,1.544c1.865,0,3.18-0.915,3.941-2.743l4,2c-0.875,1.563-2.057,2.791-3.541,3.686
c-1.486,0.896-3.105,1.343-4.857,1.343c-2.896,0-5.209-0.875-6.941-2.629c-1.736-1.752-2.602-4.19-2.602-7.313
c0-3.048,0.885-5.466,2.658-7.257c1.77-1.79,4.008-2.686,6.713-2.686C51.117,18.558,53.938,20.101,55.613,23.187z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="-0.5 0.5 64 64" enable-background="new -0.5 0.5 64 64" xml:space="preserve">
<g>
<circle fill="#FFFFFF" cx="31.325" cy="32.873" r="30.096"/>
<path id="text2809_1_" d="M31.5,14.08c-10.565,0-13.222,9.969-13.222,18.42c0,8.452,2.656,18.42,13.222,18.42
c10.564,0,13.221-9.968,13.221-18.42C44.721,24.049,42.064,14.08,31.5,14.08z M31.5,21.026c0.429,0,0.82,0.066,1.188,0.157
c0.761,0.656,1.133,1.561,0.403,2.823l-7.036,12.93c-0.216-1.636-0.247-3.24-0.247-4.437C25.808,28.777,26.066,21.026,31.5,21.026z
M36.766,26.987c0.373,1.984,0.426,4.056,0.426,5.513c0,3.723-0.258,11.475-5.69,11.475c-0.428,0-0.822-0.045-1.188-0.136
c-0.07-0.021-0.134-0.043-0.202-0.067c-0.112-0.032-0.23-0.068-0.336-0.11c-1.21-0.515-1.972-1.446-0.874-3.093L36.766,26.987z"/>
<path id="path2815_1_" d="M31.433,0.5c-8.877,0-16.359,3.09-22.454,9.3c-3.087,3.087-5.443,6.607-7.082,10.532
C0.297,24.219-0.5,28.271-0.5,32.5c0,4.268,0.797,8.32,2.397,12.168c1.6,3.85,3.921,7.312,6.969,10.396
c3.085,3.049,6.549,5.399,10.398,7.037c3.886,1.602,7.939,2.398,12.169,2.398c4.229,0,8.34-0.826,12.303-2.465
c3.962-1.639,7.496-3.994,10.621-7.081c3.011-2.933,5.289-6.297,6.812-10.106C62.73,41,63.5,36.883,63.5,32.5
c0-4.343-0.77-8.454-2.33-12.303c-1.562-3.885-3.848-7.32-6.857-10.33C48.025,3.619,40.385,0.5,31.433,0.5z M31.567,6.259
c7.238,0,13.412,2.566,18.554,7.709c2.477,2.477,4.375,5.31,5.67,8.471c1.296,3.162,1.949,6.518,1.949,10.061
c0,7.354-2.516,13.454-7.506,18.33c-2.592,2.516-5.502,4.447-8.74,5.781c-3.2,1.334-6.498,1.994-9.927,1.994
c-3.468,0-6.788-0.653-9.949-1.948c-3.163-1.334-6.001-3.238-8.516-5.716c-2.515-2.514-4.455-5.353-5.826-8.516
c-1.333-3.199-2.017-6.498-2.017-9.927c0-3.467,0.684-6.787,2.017-9.949c1.371-3.2,3.312-6.074,5.826-8.628
C18.092,8.818,24.252,6.259,31.567,6.259z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,7 +1,7 @@
<template>
<!-- QUICK NAV -->
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
<ul class="flex text-2xl p-2 gap-2">
<ul class="flex text-2xl p-2 gap-2 max-w-3xl mx-auto">
<!-- Home Feed -->
<li
:class="{

View File

@@ -1,11 +1,12 @@
export interface Contact {
did: string;
name?: string;
nextPubKeyHashB64?: string; // base64-encoded SHA256 hash of next public key
publicKeyBase64?: string;
seesMe?: boolean;
registered?: boolean;
}
export const ContactSchema = {
contacts: "&did, name, publicKeyBase64, registered, seesMe",
contacts: "&did, name", // no need to key by other things
};

View File

@@ -1,4 +1,5 @@
export interface Log {
date: string;
message: string;
}
@@ -6,5 +7,5 @@ export const LogSchema = {
// Currently keyed by "date" because A) today's log data is what we need so we append, and
// B) we don't want it to grow so we remove everything if this is the first entry today.
// See safari-notifications.js logMessage for the associated logic.
logs: "date, message",
logs: "date", // definitely don't key by the potentially large message field
};

View File

@@ -173,3 +173,19 @@ export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
return jwt.payload;
};
export const nextDerivationPath = (origDerivPath: string) => {
let lastStr = origDerivPath.split("/").slice(-1)[0];
if (lastStr.endsWith("'")) {
lastStr = lastStr.slice(0, -1);
}
const lastNum = parseInt(lastStr, 10);
const newLastNum = lastNum + 1;
const newLastStr = newLastNum.toString() + (lastStr.endsWith("'") ? "'" : "");
const newDerivPath = origDerivPath
.split("/")
.slice(0, -1)
.concat([newLastStr])
.join("/");
return newDerivPath;
};

View File

@@ -1,5 +1,69 @@
// many of these are also found in endorser-mobile utility.ts
import axios, { AxiosResponse } from "axios";
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
export const isGlobalUri = (uri: string) => {
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
};
export const sendTestThroughPushServer = async (
subscription: PushSubscription,
skipFilter: boolean,
): Promise<AxiosResponse> => {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
let pushUrl: string = DEFAULT_PUSH_SERVER as string;
if (settings?.webPushServer) {
pushUrl = settings.webPushServer;
}
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
const auth = Buffer.from(subscription.getKey("auth"));
const authB64 = auth
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const p256dh = Buffer.from(subscription.getKey("p256dh"));
const p256dhB64 = p256dh
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const newPayload = {
endpoint: subscription.endpoint,
keys: {
auth: authB64,
p256dh: p256dhB64,
},
message: `Test, where you will see this message ${
skipFilter ? "un" : ""
}filtered.`,
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
};
console.log("Sending a test web push message:", newPayload);
const payloadStr = JSON.stringify(newPayload);
const response = await axios.post(
pushUrl + "/web-push/send-test",
payloadStr,
{
headers: {
"Content-Type": "application/json",
},
},
);
console.log("Got response from web push server:", response);
return response;
};

View File

@@ -56,6 +56,7 @@ import {
faSpinner,
faSquareCaretDown,
faSquareCaretUp,
faSquarePlus,
faTrashCan,
faTriangleExclamation,
faUser,
@@ -109,6 +110,7 @@ library.add(
faSpinner,
faSquareCaretDown,
faSquareCaretUp,
faSquarePlus,
faTrashCan,
faTriangleExclamation,
faUser,

View File

@@ -3,7 +3,7 @@
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-4">
Your Identity
@@ -72,11 +72,14 @@
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h2 v-if="givenName" class="text-xl font-semibold mb-2">
{{ givenName }}
<router-link :to="{ name: 'new-edit-account' }">
<fa icon="pen" class="text-xs text-blue-500 mb-1"></fa>
</router-link>
</h2>
<span v-else>
<router-link
:to="{ name: 'new-edit-account' }"
class="block w-full text-center text-md text-slate-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
class="block w-full text-center text-md text-blue-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
>
(Set Your Name)
</router-link>
@@ -97,13 +100,6 @@
</div>
</div>
<router-link
:to="{ name: 'new-edit-account' }"
class="block text-center text-lg font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md mb-2"
>
Edit Identity
</router-link>
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
<div
v-if="!notificationMaybeChanged"
@@ -130,15 +126,15 @@
</div>
</div>
<div v-else>
Notification status may have changed. Revisit this page to see the
Notification status may have changed. Refresh this page to see the
latest setting.
</div>
<router-link class="px-4 text-sm text-blue-500" to="/help-notifications">
Test your notification setup.
Troubleshoot your notification setup.
</router-link>
</div>
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3>
<h3 class="text-sm uppercase font-semibold mb-3">Data Export</h3>
<router-link
:to="{ name: 'seed-backup' }"
@@ -147,29 +143,37 @@
>
Backup Identifier Seed
</router-link>
<a
<button
v-bind:class="computedStartDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@click="exportDatabase()"
>
Download Settings & Contacts
<br />
(excluding Identifier Data)
</button>
<a
ref="downloadLink"
v-bind:class="computedDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-green-600 text-white px-1.5 py-2 rounded-md mb-6"
>
If no download happened yet, click again here to download now.
</a>
<a ref="downloadLink" />
<div v-if="activeDid" class="my-8">
<h3 class="text-sm uppercase font-semibold mb-3">Rate Limits</h3>
<div v-if="activeDid" class="flex mt-8 py-2">
<h3 class="text-sm uppercase font-semibold">Rate Limits</h3>
<button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md ml-2 mr-2 mb-2"
@click="checkLimits()"
>
Check Limits
</button>
<!-- show spinner if loading limits -->
<div v-if="loadingLimits" class="text-center mb-4">
<div v-if="loadingLimits" class="text-center">
Checking&hellip; <fa icon="spinner" class="fa-spin"></fa>
</div>
<div class="mb-4">
<div>
{{ limitsMessage }}
</div>
<div v-if="!!limits?.nextWeekBeginDateTime">
@@ -293,18 +297,37 @@
</div>
</label>
<router-link
:to="{ name: 'statistics' }"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
See Animated Global History of Giving
</router-link>
<div class="grid-cols-2 mb-4">
<span class="text-slate-500 text-sm font-bold mb-2">Data Import</span>
<input type="file" @change="uploadFile" class="ml-2" />
<div v-if="showContactImport()">
<button
class="block text-center text-md uppercase bg-green-600 text-white px-1.5 py-2 rounded-md mb-6"
@click="submitFile()"
>
Import Settings & Contacts
<br />
(excluding Identifier Data)
</button>
</div>
</div>
<div class="flex py-2">
<button>
<router-link
:to="{ name: 'statistics' }"
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
See Global Animated History of Giving
</router-link>
</button>
</div>
<!-- id used by puppeteer test script -->
<router-link
id="switch-identity-link"
:to="{ name: 'identity-switcher' }"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Switch Identity
</router-link>
@@ -429,7 +452,9 @@
<script lang="ts">
import { AxiosError, AxiosRequestConfig } from "axios";
import Dexie from "dexie";
import "dexie-export-import";
import { ref } from "vue";
import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";
@@ -441,6 +466,7 @@ import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { IIdentifier } from "@veramo/core";
import { ErrorResponse, RateLimits } from "@/libs/endorserServer";
import { ImportProgress } from "dexie-export-import/dist/import";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
@@ -459,6 +485,8 @@ interface IAccount {
derivationPath: string;
}
const inputFileNameRef = ref<Blob>();
@Component({ components: { QuickNav, TopMessage } })
export default class AccountViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@@ -469,6 +497,7 @@ export default class AccountViewView extends Vue {
apiServer = "";
apiServerInput = "";
derivationPath = "";
downloadUrl = ""; // because DuckDuckGo doesn't download on automated call to "click" on the anchor
givenName = "";
isRegistered = false;
isSubscribed = false;
@@ -494,6 +523,11 @@ export default class AccountViewView extends Vue {
warnIfProdServer = false;
warnIfTestServer = false;
async beforeCreate() {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
}
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
@@ -530,6 +564,12 @@ export default class AccountViewView extends Vue {
}
}
beforeUnmount() {
if (this.downloadUrl) {
URL.revokeObjectURL(this.downloadUrl);
}
}
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
@@ -553,26 +593,17 @@ export default class AccountViewView extends Vue {
try {
// Open the accounts database
await accountsDB.open();
} catch (error) {
console.error("Failed to open accounts database:", error);
return null;
}
let account: { identity?: string } | undefined;
try {
// Search for the account with the matching DID (decentralized identifier)
account = await accountsDB.accounts
.where("did")
.equals(activeDid)
.first();
const account: { identity?: string } | undefined =
await accountsDB.accounts.where("did").equals(activeDid).first();
// Return parsed identity or null if not found
return JSON.parse((account?.identity as string) || "null");
} catch (error) {
console.error("Failed to find account:", error);
return null;
}
// Return parsed identity or null if not found
return JSON.parse((account?.identity as string) || "null");
}
/**
@@ -628,11 +659,6 @@ export default class AccountViewView extends Vue {
return timeStr.substring(0, timeStr.indexOf("T"));
}
async beforeCreate() {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
}
/**
* Processes the identity and updates the component's state.
* @param {IdentityType} identity - Object containing identity information.
@@ -788,13 +814,13 @@ export default class AccountViewView extends Vue {
const blob = await this.generateDatabaseBlob();
// Create a temporary URL for the blob
const url = this.createBlobURL(blob);
this.downloadUrl = this.createBlobURL(blob);
// Trigger the download
this.downloadDatabaseBackup(url);
this.downloadDatabaseBackup(this.downloadUrl);
// Revoke the temporary URL
URL.revokeObjectURL(url);
// Revoke the temporary URL -- not yet because of DuckDuckGo download failure
//URL.revokeObjectURL(this.downloadUrl);
// Notify the user that the download has started
this.notifyDownloadStarted();
@@ -831,7 +857,19 @@ export default class AccountViewView extends Vue {
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
downloadAnchor.href = url;
downloadAnchor.download = `${db.name}-backup.json`;
downloadAnchor.click();
downloadAnchor.click(); // doesn't work for some browsers, eg. DuckDuckGo
}
public computedStartDownloadLinkClassNames() {
return {
invisible: this.downloadUrl,
};
}
public computedDownloadLinkClassNames() {
return {
invisible: !this.downloadUrl,
};
}
/**
@@ -843,7 +881,7 @@ export default class AccountViewView extends Vue {
group: "alert",
type: "success",
title: "Download Started",
text: "See your downloads directory for the backup.",
text: "See your downloads directory for the backup. It is in the Dexie format.",
},
-1,
);
@@ -867,6 +905,43 @@ export default class AccountViewView extends Vue {
console.error("Export Error:", error);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async uploadFile(event: any) {
inputFileNameRef.value = event.target.files[0];
}
showContactImport() {
return !!inputFileNameRef.value;
}
/**
* Asynchronously imports the database from a downloadable JSON file.
*
* @throws Will notify the user if there is an export error.
*/
async submitFile() {
if (inputFileNameRef.value != null) {
if (
confirm(
"This will replace all settings and contacts, so we recommend you first do the backup step above." +
" Are you sure you want to import and replace all contacts and settings?",
)
) {
await db.delete();
await Dexie.import(inputFileNameRef.value, {
progressCallback: this.progressCallback,
});
}
}
}
private progressCallback(progress: ImportProgress) {
console.log(
`Import progress: ${progress.completedRows} of ${progress.totalRows} rows completed.`,
);
return true;
}
async checkLimits() {
const identity = await this.getIdentity(this.activeDid);
if (identity) {
@@ -996,6 +1071,7 @@ export default class AccountViewView extends Vue {
const accounts = await accountsDB.accounts.toArray();
const account = accounts[accountNum - 1];
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, { activeDid: account.did });
this.updateActiveAccountProperties(account);

View File

@@ -1,7 +1,7 @@
<template>
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
@@ -137,9 +137,10 @@
<div>
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Claim</h2>
<pre class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md">
{{ util.inspect(veriClaim, false, null) }}
</pre>
<pre
class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md"
>{{ veriClaimDump }}</pre
>
</div>
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Full Claim</h2>
@@ -160,7 +161,7 @@
</button>
</div>
<div v-else>
<pre>{{ util.inspect(fullClaim, false, null) }}</pre>
<pre>{{ fullClaimDump }}</pre>
</div>
<a
@@ -175,6 +176,7 @@
<script lang="ts">
import { AxiosError, RawAxiosRequestHeaders } from "axios";
import * as yaml from "js-yaml";
import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import * as util from "util";
@@ -212,11 +214,14 @@ export default class ClaimView extends Vue {
confsVisibleErrorMessage = "";
confsVisibleToIdList = []; // list of DIDs that can see any confirmer
fullClaim = null;
fullClaimDump = "";
fullClaimMessage = "";
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
veriClaimDump = "";
util = util;
yaml = yaml;
containsHiddenDid = serverUtil.containsHiddenDid;
async created() {
@@ -305,6 +310,7 @@ export default class ClaimView extends Vue {
const resp = await this.axios.get(url, { headers });
if (resp.status === 200) {
this.veriClaim = resp.data;
this.veriClaimDump = yaml.dump(this.veriClaim);
} else {
// actually, axios typically throws an error so we never get here
console.log("Error getting claim:", resp);
@@ -384,6 +390,7 @@ export default class ClaimView extends Vue {
const resp = await this.axios.get(url, { headers });
if (resp.status === 200) {
this.fullClaim = resp.data;
this.fullClaimDump = yaml.dump(this.fullClaim);
} else {
// actually, axios typically throws an error so we never get here
console.log("Error getting full claim:", resp);

View File

@@ -1,6 +1,6 @@
<template>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -1,6 +1,6 @@
<template>
<QuickNav selected="Contacts"></QuickNav>
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<h1

View File

@@ -1,7 +1,7 @@
<template>
<QuickNav selected="Home"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -1,7 +1,7 @@
<template>
<QuickNav selected="Profile"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->
@@ -58,16 +58,17 @@
</template>
<script lang="ts">
import * as didJwt from "did-jwt";
import { sha256 } from "ethereum-cryptography/sha256.js";
import QRCodeVue3 from "qr-code-generator-vue3";
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator";
import { QrcodeStream } from "vue-qrcode-reader";
import { useClipboard } from "@vueuse/core";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import * as R from "ramda";
import { SimpleSigner } from "@/libs/crypto";
import * as didJwt from "did-jwt";
import { deriveAddress, nextDerivationPath, SimpleSigner } from "@/libs/crypto";
import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts";
import {
@@ -131,6 +132,14 @@ export default class ContactQRScanShow extends Vue {
const identity = await this.getIdentity(this.activeDid);
const publicKeyHex = identity.keys[0].publicKeyHex;
const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64");
const newDerivPath = nextDerivationPath(account.derivationPath);
const nextPublicHex = deriveAddress(account.mnemonic, newDerivPath)[2];
const nextPublicEncKey = Buffer.from(nextPublicHex, "hex");
const nextPublicEncKeyHash = sha256(nextPublicEncKey);
const nextPublicEncKeyHashBase64 =
Buffer.from(nextPublicEncKeyHash).toString("base64");
const contactInfo = {
iat: Date.now(),
iss: this.activeDid,
@@ -139,6 +148,7 @@ export default class ContactQRScanShow extends Vue {
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3
publicEncKey,
nextPublicEncKeyHash: nextPublicEncKeyHashBase64,
},
};

View File

@@ -1,5 +1,5 @@
<template>
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -1,6 +1,6 @@
<template>
<QuickNav selected="Contacts"></QuickNav>
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Contacts
@@ -28,7 +28,7 @@
</router-link>
<input
type="text"
placeholder="DID, Name, Public Key (base 16 or 64)"
placeholder="DID, Name, Public Key, Next Public Key Hash"
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
v-model="contactInput"
/>
@@ -109,12 +109,16 @@
<div class="text-sm truncate" v-if="contact.publicKeyBase64">
Public Key (base 64): {{ contact.publicKeyBase64 }}
</div>
<div class="text-sm truncate" v-if="contact.nextPubKeyHashB64">
Next Public Key Hash (base 64):
{{ contact.nextPubKeyHashB64 }}
</div>
<div id="ContactActions" class="flex gap-1.5 mt-2">
<div v-if="activeDid">
<button
v-if="contact.seesMe"
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-slate-500 text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="setVisibility(contact, false, true)"
title="They can see you"
>
@@ -122,14 +126,14 @@
</button>
<button
v-else
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-slate-500 text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="setVisibility(contact, true, true)"
title="They cannot see you"
>
<fa icon="eye-slash" class="fa-fw" />
</button>
<button
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-slate-500 text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="checkVisibility(contact)"
title="Check Visibility"
v-if="activeDid"
@@ -493,7 +497,7 @@ export default class ContactsView extends Vue {
}
let did = this.contactInput;
let name, publicKeyBase64;
let name, publicKeyInput, nextPublicKeyHashInput;
const commaPos1 = this.contactInput.indexOf(",");
if (commaPos1 > -1) {
did = this.contactInput.substring(0, commaPos1).trim();
@@ -501,15 +505,31 @@ export default class ContactsView extends Vue {
const commaPos2 = this.contactInput.indexOf(",", commaPos1 + 1);
if (commaPos2 > -1) {
name = this.contactInput.substring(commaPos1 + 1, commaPos2).trim();
publicKeyBase64 = this.contactInput.substring(commaPos2 + 1).trim();
publicKeyInput = this.contactInput.substring(commaPos2 + 1).trim();
const commaPos3 = this.contactInput.indexOf(",", commaPos2 + 1);
if (commaPos3 > -1) {
publicKeyInput = this.contactInput.substring(commaPos2 + 1, commaPos3).trim(); // eslint-disable-line prettier/prettier
nextPublicKeyHashInput = this.contactInput.substring(commaPos3 + 1).trim(); // eslint-disable-line prettier/prettier
}
}
}
// help with potential mistakes while this sharing requires copy-and-paste
let publicKeyBase64 = publicKeyInput;
if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) {
// it must be all hex (compressed public key), so convert
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
}
const newContact = { did, name, publicKeyBase64 };
let nextPubKeyHashB64 = nextPublicKeyHashInput;
if (nextPubKeyHashB64 && /^[0-9A-Fa-f]{66}$/i.test(nextPubKeyHashB64)) {
// it must be all hex (compressed public key), so convert
nextPubKeyHashB64 = Buffer.from(nextPubKeyHashB64, "hex").toString("base64"); // eslint-disable-line prettier/prettier
}
const newContact = {
did,
name,
publicKeyBase64,
nextPubKeyHashB64: nextPubKeyHashB64,
};
await this.addContact(newContact);
}
@@ -530,6 +550,7 @@ export default class ContactsView extends Vue {
return this.addContact({
did: payload.iss,
name: payload.own.name,
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
publicKeyBase64: payload.own.publicEncKey,
} as Contact);
}
@@ -566,17 +587,7 @@ export default class ContactsView extends Vue {
} else {
addedMessage = newContact.name + " was added.";
}
this.$notify(
{
group: "alert",
type: "success",
title: "Contact Added",
text: addedMessage,
},
5000,
);
if (this.isRegistered) {
// putting this last so that it shows on the top
this.$notify(
{
group: "alert",
@@ -590,15 +601,31 @@ export default class ContactsView extends Vue {
-1,
);
}
this.$notify(
{
group: "alert",
type: "success",
title: "Contact Added",
text: addedMessage,
},
-1, // keeping it up so that the "visibility" message is seen
);
})
.catch((err) => {
console.error("Error when adding contact to storage:", err);
let message = "An error prevented this import.";
if (
err.message?.indexOf("Key already exists in the object store.") > -1
) {
message =
"A contact with that DID is already in your contact list. Edit them directly below.";
}
this.$notify(
{
group: "alert",
type: "danger",
title: "Contact Not Added",
text: "An error prevented this import.",
text: message,
},
-1,
);
@@ -742,63 +769,70 @@ export default class ContactsView extends Vue {
visibility: boolean,
showSuccessAlert: boolean,
) {
const url =
this.apiServer +
"/api/report/" +
(visibility ? "canSeeMe" : "cannotSeeMe");
const identity = await this.getIdentity(this.activeDid);
const headers = await this.getHeaders(identity);
const payload = JSON.stringify({ did: contact.did });
const visibilityPrompt =
showSuccessAlert &&
(visibility
? "Are you sure you want to make your activity visible to them?"
: "Are you sure you want to hide all your activity from them?");
if (visibilityPrompt && confirm(visibilityPrompt)) {
const url =
this.apiServer +
"/api/report/" +
(visibility ? "canSeeMe" : "cannotSeeMe");
const identity = await this.getIdentity(this.activeDid);
const headers = await this.getHeaders(identity);
const payload = JSON.stringify({ did: contact.did });
try {
const resp = await this.axios.post(url, payload, { headers });
if (resp.status === 200) {
if (showSuccessAlert) {
try {
const resp = await this.axios.post(url, payload, { headers });
if (resp.status === 200) {
if (showSuccessAlert) {
this.$notify(
{
group: "alert",
type: "success",
title: "Visibility Set",
text:
this.nameForDid(this.contacts, contact.did) +
" can " +
(visibility ? "" : "not ") +
"see your activity.",
},
-1,
);
}
contact.seesMe = visibility;
db.contacts.update(contact.did, { seesMe: visibility });
} else {
console.error(
"Got some bad server response when setting visibility: ",
resp.status,
resp,
);
const message =
resp.data.error?.message || "Got some error setting visibility.";
this.$notify(
{
group: "alert",
type: "success",
title: "Visibility Set",
text:
this.nameForDid(this.contacts, contact.did) +
" can " +
(visibility ? "" : "not ") +
"see your activity.",
type: "danger",
title: "Error Setting Visibility",
text: message,
},
-1,
);
}
contact.seesMe = visibility;
db.contacts.update(contact.did, { seesMe: visibility });
} else {
console.error(
"Got some bad server response when setting visibility: ",
resp.status,
resp,
);
const message =
resp.data.error?.message || "Got some error setting visibility.";
} catch (err) {
console.error("Got some error when setting visibility:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Visibility",
text: message,
text: "Check connectivity and try again.",
},
-1,
);
}
} catch (err) {
console.error("Got some error when setting visibility:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Visibility",
text: "Check connectivity and try again.",
},
-1,
);
}
}

View File

@@ -3,7 +3,7 @@
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Discover

View File

@@ -2,7 +2,7 @@
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->
@@ -21,10 +21,11 @@
</h1>
</div>
<!-- eslint-disable prettier/prettier -->
<div>
<p>Here are ways to test notifications and get them working.</p>
<h2 class="text-xl font-semibold">Full Test</h2>
<h2 class="text-xl font-semibold mt-4">Full Test</h2>
<div>
<p>
If this works then you're all set.
@@ -38,13 +39,16 @@
</p>
</div>
<h2 class="text-xl font-semibold">If this app is not installed...</h2>
<h2 class="text-xl font-semibold mt-4">
If this app doesn't support notifications...
<!-- Note that that exact verbiage shows in a message elsewhere. -->
</h2>
<div>
<p>
For best results on mobile, install this app on your device (as
opposed to using it inside the broser app). In Chrome, it may prompt
To be notified of interesting updates, install this app on your device
(as opposed to using it inside the browser app). In Chrome, it may prompt
you, and you can also look for the "Install" command in the browser
settings; on the the deskop, look for this icon in the address bar:
settings; on the the desktop, look for this icon in the address bar:
<img
src="../assets/help/chrome-install-pwa.png"
alt="Chrome 'install' icon"
@@ -53,19 +57,31 @@
</p>
</div>
<h2 class="text-xl font-semibold">
If "you must enable notifications"...
<h2 class="text-xl font-semibold mt-4">
If you must enable notifications...
<!-- Note that that exact verbiage shows in a message elsewhere. -->
</h2>
<div>
<p>
Wait for about 10 seconds (for the service worker to activate), then
<button class="text-blue-500" @click="showNotificationChoice()">
click here.
Click here.
</button>
</p>
</div>
<h2 class="text-xl font-semibold">Check App Permissions</h2>
<h2 class="text-xl font-semibold mt-4">
If you're waiting for system initialization...
<!-- Note that that exact verbiage shows in a message elsewhere. -->
</h2>
<div>
<p>
... and it never stops, then there is a problem with the underlying
service worker or push server mechanism in your browser. Your best bet
is to follow the "Reinstall" steps below or use a different browser.
</p>
</div>
<h2 class="text-xl font-semibold mt-4">Check App Permissions</h2>
<div>
<p>
In Apple iOS, check "Settings" -> "Notifications", look for the Time
@@ -89,8 +105,8 @@
</li>
<li class="list-disc ml-4">
Go to Chrome "Settings", then "Privacy and Security" and "Clear
"Clear browsing data", then "Cookies and site data". Also make sure
the "Time Range" at the top shows "All time".
"Clear browsing data", then "Cookies and site data". Make sure the
"Time Range" at the top shows "All time".
</li>
</ul>
<p>
@@ -103,7 +119,7 @@
</p>
</div>
<h2 class="text-xl font-semibold">Check Browser Permissions</h2>
<h2 class="text-xl font-semibold mt-4">Check Browser Permissions</h2>
<div>
<p>In Apple iOS, check Settings -> Notifications.</p>
<p>In Android, check Settings -> Notifications.</p>
@@ -118,7 +134,9 @@
</a>
</div>
<h2 class="text-xl font-semibold">Check OS Permissions</h2>
<h2 class="text-xl font-semibold mt-4">
Check Operating System (OS) Permissions
</h2>
<div class="px-2">
<div>
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
@@ -136,13 +154,15 @@
<h3 class="text-lg font-semibold">Desktop - Mac</h3>
<div>
<span>
Requires Mac OS 13; see your macOS version under Apple -> About
This Mac.
See "System Settings" -> "Notifications" and make sure it is
enabled for the browser you're using. Note that these
notifications require Mac OS 13; see your macOS version under
Apple -> "About This Mac".
</span>
</div>
<h3 class="text-lg font-semibold">Windows desktop</h3>
In Windows, check Settings -> Notifications.
<h3 class="text-lg font-semibold">Desktop - Windows</h3>
In Windows, check "Settings" -> "Notifications".
<img
src="../assets/help/windows-system-enable-notifications.png"
alt="Windows system settings"
@@ -161,80 +181,120 @@
</div>
</div>
<h2 class="text-xl font-semibold">Reinstall</h2>
<h2 class="text-xl font-semibold mt-4">Reinstall</h2>
<div>
<p>
If all else fails, uninstall the app, ensure all the browser tabs with
it are closed, and clear out caches and storage.
</p>
<p>
Of course, you'll want to back up all your data first -- all seeds as
well as the contacts & settings -- on the Account
<fa icon="circle-user" /> page.
</p>
<ul class="ml-4 list-disc">
<li>
Clear cache for site. (In Chrome, go to `chrome://settings/cookies`
and "all site data and permissions"; in Firefox, go to
`about:preferences` and search for "cache" then "Manage Data", and
also manually remove the IndexedDB data if the DBs still show.)
Clear cache.
<ul>
<li>
In mobile, look for the browser app settings. This is true even
for an installed app: go to the browser which you used to
initially visit timesafari.app, because those settings affect
the app. Look for "Delete browsing data" in the "Settings",
under "Privacy and Security".
</li>
<li>
In Chrome, go to `chrome://settings/cookies` and "all site data
and permissions" for timesafari.app; in Firefox, go to
`about:preferences` and search for "cache" then "Manage Data"
for timesafari.app. Also manually remove the IndexedDB data if
the DBs still show.)
</li>
</ul>
</li>
<li>
Clear notification permission. (in Chrome, go to
Clear notification permission. (In Chrome, go to
`chrome://settings/content/notifications`; in Firefox, go to
`about:preferences` and search for "notifications".)
</li>
<li>
Unregister service worker. (in Chrome, go to
Unregister service worker. (In Chrome, go to
`chrome://serviceworker-internals/`; in Firefox, go to
`about:serviceworkers`.)
</li>
<li>
Clear Cache Storage. (in Chrome, in dev tools under Application; in
Firefox, in dev tools under Storage.)
Clear "Cache Storage". (In Chrome, in dev tools under "Application";
in Firefox, in dev tools under "Storage".)
</li>
</ul>
<p>Then reinstall the app.</p>
</div>
<h2 class="text-xl font-semibold mt-4">Tests</h2>
<button
@click="showTestNotification()"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Send Test Notification Directly to Device (Not Through Push Server)
</button>
<p>
If that didn't show a notification on your device, the problem is that
your browser or your operating system are not allowing notifications
through. See "Check App Permissions" and "Check Browser Permissions" and
"Check Operating System (OS) Permissions" above.
</p>
<button
@click="alertWebPushSubscription()"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Show Web Push Subscription Info
</button>
<p>
If that showed "null" then the notification is not active.
<button class="text-blue-500" @click="showNotificationChoice()">
Click here.
</button>
</p>
<button
@click="sendTestWebPushMessage(true)"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Send Yourself a Test Web Push Message (Through Push Server but Skipping
Client Filter)
</button>
<p>
If that didn't show a notification on your device, there is a problem
getting to the push server. Disable notifications and then enable them
again.
</p>
<button
@click="sendTestWebPushMessage()"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Send Yourself a Test Web Push Message (Through Push Server and Client
Filter)
</button>
<p>
If you don't see a message, it could be that there is nothing new for
you to see. If the previous test worked, then things should work fine.
If you notice a full 24 hours where you get no notification and you know
that there are new items that should show, gather as many details as
possible and go to the bottom of
<router-link to="help" class="text-blue-500"> this page </router-link>
for ways to contact us.
</p>
</div>
<!-- eslint-enable -->
</section>
</template>
<script lang="ts">
import axios from "axios";
import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
import { sendTestThroughPushServer } from "@/libs/util";
interface Notification {
group: string;
@@ -273,6 +333,7 @@ export default class HelpNotificationsView extends Vue {
group: "alert",
type: "danger",
title: "Not Subscribed",
// Note that this exact verbiage shows in help text.
text: "You must enable notifications before testing the web push.",
},
-1,
@@ -281,61 +342,16 @@ export default class HelpNotificationsView extends Vue {
}
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
let pushUrl: string = DEFAULT_PUSH_SERVER as string;
if (settings?.webPushServer) {
pushUrl = settings.webPushServer;
}
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
const auth = Buffer.from(this.subscription.getKey("auth"));
const authB64 = auth
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const p256dh = Buffer.from(this.subscription.getKey("p256dh"));
const p256dhB64 = p256dh
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const newPayload = {
endpoint: this.subscription.endpoint,
keys: {
auth: authB64,
p256dh: p256dhB64,
},
message: `Test, where you will see this message ${
skipFilter ? "un" : ""
}filtered.`,
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
};
console.log("Sending a test web push message:", newPayload);
const payloadStr = JSON.stringify(newPayload);
const response = await axios.post(
pushUrl + "/web-push/send-test",
payloadStr,
{
headers: {
"Content-Type": "application/json",
},
},
);
console.log("Got response from web push server:", response);
await sendTestThroughPushServer(this.subscription, skipFilter);
this.$notify(
{
group: "alert",
type: "success",
title: "Test Web Push Sent",
text: "Check your device for the test web push message, depending on the filtering you chose.",
text:
"Check your device for the test web push message" +
(skipFilter ? "." : " if there are new items in your feed."),
},
-1,
);

View File

@@ -2,7 +2,7 @@
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->
@@ -21,13 +21,14 @@
</h1>
</div>
<!-- eslint-disable prettier/prettier -->
<div>
<p>
This app is a window into data that you and your friends own, focused on
gifts and collaboration.
</p>
<h2 class="text-xl font-semibold">What is the philosophy here?</h2>
<h2 class="text-xl font-semibold">What is the idea here?</h2>
<p>
We are building networks of people who want to grow a giving society.
First of all, you can see what people have given, and also recognize
@@ -94,7 +95,7 @@
<h2 class="text-xl font-semibold">
How do I backup my identifier (secret) data?
</h2>
<ul class="list-disc list-inside">
<ul class="list-disc list-outside ml-4">
<li>
Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page.
</li>
@@ -110,7 +111,7 @@
<h2 class="text-xl font-semibold">
How do I backup my other (non-identifier-secret) data?
</h2>
<ul class="list-disc list-inside">
<ul class="list-disc list-outside ml-4">
<li>
Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page.
</li>
@@ -132,7 +133,7 @@
<h2 class="text-xl font-semibold">
How do I restore my identifier (secret) data?
</h2>
<ul class="list-disc list-inside">
<ul class="list-disc list-outside ml-4">
<li>
<router-link class="text-blue-500" to="/import-account">
Go to the import page
@@ -144,12 +145,10 @@
<h2 class="text-xl font-semibold">
How do I restore my other (non-identifier-secret) data?
</h2>
<ul class="list-disc list-inside">
<ul class="list-disc list-outside ml-4">
<li>
Make sure you have your backup file (above), then contact us with
your interest. This is functionality that has to be written, and
your interest will help us prioritize it, but there are also manual
ways to restore your data.
Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page,
click Advanced, and follow the instructions for Data Import.
</li>
</ul>
</div>
@@ -164,6 +163,47 @@
</router-link>
</p>
<h2 class="text-xl font-semibold">How do I erase my data?</h2>
<p>
Before doing this, note the two kinds of data to backup: identity data,
and other data for contacts and settings (see instructions above).
</p>
<ul>
<li class="list-disc list-outside ml-4">
Mobile
<ul>
<li class="list-disc list-outside ml-4">
Chrome: Settings -> Privacy and Security -> Clear Browsing Data
</li>
<li class="list-disc list-outside ml-4">
DuckDuckGo: long hold -> Clear Data (takes effect immediately)
</li>
</ul>
</li>
<li class="list-disc list-outside ml-4">
Desktop
<ul>
<li class="list-disc list-outside ml-4">
Chrome:
<a href="chrome://settings/content/all" class="text-blue-500"
>clear here</a
>
also clear under dev tools Application
</li>
<li class="list-disc list-outside ml-4">
Firefox: <a href="about:preferences">go here</a>, Manage Data,
find timesafari.app and select, hit Remove Selected, then Save
Changes
</li>
<li class="list-disc list-outside ml-4">
Safari: Settings -> Privacy -> Manage Website Data, search for
timesafari.app and select, hit Remove Selected, then Done.
</li>
</ul>
</li>
</ul>
<p>To erase your data from our servers, contact us (below).</p>
<h2 class="text-xl font-semibold">
I know there is a record from someone, so why can't I see that info?
</h2>
@@ -206,11 +246,31 @@
</a>
</p>
<h2 class="text-xl font-semibold">What is your privacy policy?</h2>
<p>
See
<h2 class="text-xl font-semibold">What are the terms & conditions and the privacy policy?</h2>
<p style="display:inline; align-items: center">
This work is marked with
<a href="http://creativecommons.org/publicdomain/zero/1.0?ref=chooser-v1" target="_blank" rel="license noopener noreferrer">
<span class="text-blue-500 mr-1">CC0 1.0</span>
<img
src="../assets/help/creative-commons-circle.svg"
alt="CC circle"
width="20"
class="display: inline"
/>
<img
src="../assets/help/creative-commons-zero.svg"
alt="CC zero"
width="20"
style="display: inline"
/>
</a>
<br />
For notifications, this service stores push token data; that can be revoked at any time
by disabling notifications on the Account <fa icon="circle-user" class="fa-fw" /> page.
<br />
For all other claim data,
<a href="https://endorser.ch/privacy-policy" class="text-blue-500">
the Endorser Service Privacy Policy.
the Endorser Service has this Privacy Policy.
</a>
</p>
@@ -231,9 +291,12 @@
</h2>
<p>
Contact us at
<a mailto="info@TimeSafari.app">info@TimeSafari.app</a>
<a href="mailto:info@TimeSafari.app" class="text-blue-500"
>info@TimeSafari.app</a
>
</p>
</div>
<!-- eslint enable -->
</section>
</template>

View File

@@ -3,28 +3,62 @@
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Time Safari
</h1>
<!-- show the actions for recognizing a give -->
<!-- prompt to install notifications -->
<div class="mb-8">
<div
v-if="!isInstalled()"
v-if="!notificationsSupported()"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
>
<p>
You should install this as an app.
<router-link
:to="{ name: 'help-notifications' }"
class="text-blue-500"
<p style="display: inline; align-items: center">
This app doesn't support notifications, so let's fix that. <br />
<!-- Note that that exact verbiage shows in the help. -->
<span v-if="userAgentInfo.getOS().name === 'iOS'">
Tap on "Share"<img
src="../assets/help/apple-share-icon.svg"
alt="Apple 'share' icon"
width="30"
style="display: inline; margin: 0 5px; vertical-align: middle"
/>and then "Add to Home Screen"
<fa icon="square-plus" title="Apple 'Add' icon" />
and go click on that new app.
</span>
<span
v-else-if="userAgentInfo.getBrowser().name.startsWith('Chrome')"
>
Go here for instructions.
</router-link>
You should see a prompt to install, or you can click on the
top-right dots
<fa
icon="ellipsis-vertical"
title="vertical ellipsis"
class="fa-fw"
/>
and then "Install"<img
src="../assets/help/install-android-chrome.png"
alt="Android 'install' icon"
width="30"
style="display: inline; margin: 0 5px; vertical-align: middle"
/>
and go use that app.
</span>
<span v-else>
Try
<a href="https://www.google.com/chrome/" class="text-blue-500"
>Google Chrome</a
>
or look for a way to install as an app.
</span>
</p>
</div>
</div>
<!-- show the actions for recognizing a give -->
<div class="mb-8">
<div
v-if="!activeDid"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
@@ -157,6 +191,7 @@
</template>
<script lang="ts">
import { UAParser } from "ua-parser-js";
import { Component, Vue } from "vue-facing-decorator";
import EntityIcon from "@/components/EntityIcon.vue";
@@ -205,6 +240,7 @@ export default class HomeView extends Vue {
isHiddenSpinner = true;
isRegistered = false;
numAccounts = 0;
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
async beforeCreate() {
await accountsDB.open();
@@ -264,16 +300,8 @@ export default class HomeView extends Vue {
}
}
// from https://benborgers.com/posts/pwa-detect-installed
isInstalled() {
// For iOS
if ("standalone" in window.navigator) return true;
// For Android
if (window.matchMedia("(display-mode: standalone)").matches) return true;
// If neither is true, it's not installed
return false;
notificationsSupported() {
return "Notification" in window;
}
public async buildHeaders() {

View File

@@ -1,6 +1,6 @@
<template>
<QuickNav selected="Profile"></QuickNav>
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
@@ -21,9 +21,6 @@
<div class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-4">
<fa icon="circle-check" class="fa-fw text-blue-600 text-xl mr-3"></fa>
<span class="overflow-hidden">
<h2 class="text-xl font-semibold mb-0">
{{ givenName }}
</h2>
<div class="text-sm text-slate-500 truncate">
<b>ID:</b> <code>{{ activeDid }}</code>
</div>
@@ -90,7 +87,6 @@ export default class IdentitySwitcherView extends Vue {
public activeDid = "";
public apiServer = "";
public apiServerInput = "";
public givenName = "";
public otherIdentities: Array<{ did: string }> = [];
public showContactGives = false;
@@ -111,9 +107,6 @@ export default class IdentitySwitcherView extends Vue {
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.apiServerInput = settings?.apiServer || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // deprecated, pre v 0.1.3
this.showContactGives = !!settings?.showContactGivesInline;
const identity = await this.getIdentity(this.activeDid);

View File

@@ -1,5 +1,5 @@
<template>
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -1,5 +1,5 @@
<template>
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
@@ -72,6 +72,7 @@ import {
DEFAULT_ROOT_DERIVATION_PATH,
deriveAddress,
newIdentifier,
nextDerivationPath,
} from "../libs/crypto";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@@ -121,17 +122,7 @@ export default class ImportAccountView extends Vue {
}
});
// increment the last number in that max derivation path
let lastStr = accountWithMaxDeriv.derivationPath.split("/").slice(-1)[0];
if (lastStr.endsWith("'")) {
lastStr = lastStr.slice(0, -1);
}
const lastNum = parseInt(lastStr, 10);
const newLastNum = lastNum + 1;
const newDerivPath = accountWithMaxDeriv.derivationPath
.split("/")
.slice(0, -1)
.concat([newLastNum.toString() + "'"])
.join("/");
const newDerivPath = nextDerivationPath(accountWithMaxDeriv.derivationPath);
const mne: string = accountWithMaxDeriv.mnemonic;

View File

@@ -1,5 +1,5 @@
<template>
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -1,6 +1,6 @@
<template>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -1,7 +1,7 @@
<template>
<QuickNav selected="Projects"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -2,7 +2,7 @@
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->

View File

@@ -3,7 +3,7 @@
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">

View File

@@ -2,7 +2,7 @@
<QuickNav selected="Projects"></QuickNav>
<TopMessage />
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Ideas

View File

@@ -2,7 +2,7 @@
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->

View File

@@ -1,7 +1,7 @@
<template>
<QuickNav selected="Profile"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Seed Backup

View File

@@ -2,7 +2,7 @@
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->
@@ -23,7 +23,7 @@
<div>
Here is a view of the activity you can see.
<ul class="list-disc list-inside">
<ul class="list-disc outside ml-4">
<li>Each identity and claim has a unique position.</li>
<!-- eslint-disable prettier/prettier --><!-- If we format prettier then there is extra space at the start of the line. -->
<li>Each will show at their time of appearance relative to all others.</li>

View File

@@ -2,7 +2,7 @@
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->

View File

@@ -18,7 +18,9 @@ function logConsoleAndDb(message, arg1, arg2) {
self.appendDailyLog(fullMessage);
} else {
// sometimes we get the error: "Uncaught TypeError: self.appendDailyLog is not a function"
console.log("Not logging to DB because self.appendDailyLog doesn't exist.");
console.log(
"Not logging to DB (often because self.appendDailyLog doesn't exist).",
);
}
}

View File

@@ -414,11 +414,11 @@ async function appendDailyLog(message) {
const db = await openIndexedDB("TimeSafari");
const transaction = db.transaction("logs", "readwrite");
const store = transaction.objectStore("logs");
// will only keep one day's worth of logs
// only keep one day's worth of logs
const todayKey = new Date().toDateString();
const previous = await getRecord(store, todayKey);
if (!previous) {
await store.clear(); // clear out anything older than today
await store.clear(); // clear out everything previous when this is today's first log
}
let fullMessage = (previous && previous.message) || "";
if (fullMessage) {
@@ -468,9 +468,6 @@ async function fetchAllAccounts() {
if (!db.objectStoreNames.contains("accounts")) {
db.createObjectStore("accounts", { keyPath: "id" });
}
if (!db.objectStoreNames.contains("worker_log")) {
db.createObjectStore("worker_log");
}
};
openRequest.onsuccess = function (event) {