don't show non-message to user; fix API server setting; misc doc & task stuff #88

Merged
anomalist merged 10 commits from adjust-note into master 10 months ago
  1. 2
      README.md
  2. 34
      project.task.yaml
  3. 25
      src/views/ContactQRScanShowView.vue
  4. 35
      src/views/ContactsView.vue
  5. 2
      src/views/DiscoverView.vue
  6. 10
      src/views/HomeView.vue
  7. 22
      src/views/NewEditProjectView.vue
  8. 12
      src/views/ProjectViewView.vue
  9. 12
      sw_scripts/additional-scripts.js
  10. 11
      sw_scripts/safari-notifications.js

2
README.md

@ -85,7 +85,7 @@ For your own web-push tests, change the 'vapid' URL in App.vue, and install apps
- Create a new identity as prompted. Go to "Your Identity" screen and copy the ID to the clipboard. - Create a new identity as prompted. Go to "Your Identity" screen and copy the ID to the clipboard.
- Go back to /start and import test User `did:ethr:0x000Ee5654b9742f6Fe18ea970e32b97ee2247B51` with this this seed phrase: - Go back to /start and import test User `did:ethr:0x000Ee5654b9742f6Fe18ea970e32b97ee2247B51` with this this seed phrase:
`seminar accuse mystery assist delay law thing deal image undo guard initial shallow wrestle list fragile borrow velvet tomorrow awake explain test offer control` `rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage`
(Other test users are found [here](https://github.com/trentlarson/endorser-ch/blob/master/test/util.js).) (Other test users are found [here](https://github.com/trentlarson/endorser-ch/blob/master/test/util.js).)
- Go to "Your Contacts" screen and add the ID you copied to the clipboard, and hit "+" to add them. - Go to "Your Contacts" screen and add the ID you copied to the clipboard, and hit "+" to add them.

34
project.task.yaml

@ -1,51 +1,40 @@
tasks: tasks:
- remove hard-coded anomalistlabs.com
- don't show "Give" & "Offer" on project screen if they don't have an identifier
- allow some gives even if they aren't registered
- in endorser-push-server - mount folder for persistent sqlite DB outside of container
- extract private_key_hex in webpush.py
- 40 notifications : - 40 notifications :
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew - push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
- extract private_key_hex in py-push-server webpush.py
- lock down regenerate_vapid endpoint (so only we admins can do it on demand)
- remove sleep in py-push-server app.py
- .2 change the "claims" verbiage in feeds (eg. safari-notifications.js)
- .5 allow to manage their notifications even without an identity
- 01 Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s)
- .3 fix the Project-location-selection map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui - .3 fix the Project-location-selection map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
- .5 Add infinite scroll to gifts on the home page - .5 Add infinite scroll to gifts on the home page
- .5 bug - search for "Safari" does not find the project, but if already on the "Anywhere" tab it shows all
- .2 figure out why endorser-mobile search doesn't find recently created PlanAction
- .1 when creating a plan, select location and then make sure you can deselect on Android
- .5 fix where user 0 sees no txns from user 1 on contacts page but sees them on list page
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164 assignee:trent
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
- fix cert generation (since it didn't happen automatically for Nov 30)
- Discuss whether the remaining tasks are worthwhile before MVP release. - Discuss whether the remaining tasks are worthwhile before MVP release.
- .1 Add units or different icon to the coins (to distinguish $, BTC, hours, etc)
- .5 make a VC details page, or link to endorser.ch (including confirmations)
- 01 allow download of each VC (& confirmations, to show that they actually own their data)
- .3 check that Android shows "back" buttons on screens without bottom tray
- .1 Make give description text box into something that expands as they type? - .1 Make give description text box into something that expands as they type?
- 04 allow user to download claims, mine + ones I can see about me from others - 04 allow user to download claims, mine + ones I can see about me from others
- .5 customize favicon assignee-group:ui - .5 customize favicon assignee-group:ui
- .2 Show a warning if both giver and recipient are the same (but still allow?) - .2 Show a warning if both giver and recipient are the same (but still allow?)
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui - 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui
- .5 Display a more appealing confirmation on the map when erasing the marker - .5 Display a more appealing confirmation on the map when erasing the marker
- .5 make a VC details page, or link to endorser.ch
- .1 Add units or different icon to the coins (to distinguish $, BTC, hours, etc)
- .5 include the hash of the latest commit on help page next to version (maybe Trent's git-hash branch) - .5 include the hash of the latest commit on help page next to version (maybe Trent's git-hash branch)
- .5 remove references to localStorage for projectId (now that it's pulling from the path) - .5 remove references to localStorage for projectId (now that it's pulling from the path)
- bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent - bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too) - make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
- allow download of each VC (to show that they can actually own their data) - allow some gives even if they aren't registered
- switch some checks for activeDid to check for isRegistered
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
- .5 fix cert generation on server (since it didn't happen automatically for Nov 30)
- contacts v+ : - contacts v+ :
- 01 Import all the non-sensitive data (ie. contacts & settings). - 01 Import all the non-sensitive data (ie. contacts & settings).
- .2 show error to user when adding a duplicate contact - .2 show error to user when adding a duplicate contact
- 01 parse input more robustly (with CSV lib and not commas) - 01 parse input more robustly (with CSV lib and not commas)
- stats v1 : - stats v1 :
- 01 show numeric stats - 01 show numeric stats
- 04 show different graphic for projects vs people (gnome?) on world - 04 show different graphic for projects vs people (gnome?) on world
@ -69,6 +58,7 @@ tasks:
- .5 show seed phrase in a QR code for transfer to another device - .5 show seed phrase in a QR code for transfer to another device
- .5 on DiscoverView, switch to a filter UI (eg. just from friend - .5 on DiscoverView, switch to a filter UI (eg. just from friend
- .5 don't show "Offer" on project screen if they aren't registered
- 24 Move to Vite - 24 Move to Vite
- 32 accept images for projects - 32 accept images for projects

25
src/views/ContactQRScanShowView.vue

@ -20,7 +20,7 @@
</h1> </h1>
</div> </div>
<div @click="onCopyToClipboard()"> <div @click="onCopyToClipboard()" v-if="activeDid">
<!-- <!--
Play with display options: https://qr-code-styling.com/ Play with display options: https://qr-code-styling.com/
See docs: https://www.npmjs.com/package/qr-code-generator-vue3 See docs: https://www.npmjs.com/package/qr-code-generator-vue3
@ -32,6 +32,15 @@
class="flex justify-center" class="flex justify-center"
/> />
</div> </div>
<div class="text-center" v-else>
You have no identitifiers yet, so
<router-link :to="{ name: 'start' }" class="text-blue-500">
create your identifier.
</router-link>
<br />
We recommend you do that first; otherwise, these contacts won't see your
activity.
</div>
<h1 class="text-4xl text-center font-light pt-4">Scan Contact Info</h1> <h1 class="text-4xl text-center font-light pt-4">Scan Contact Info</h1>
<qrcode-stream @detect="onScanDetect" @error="onScanError" /> <qrcode-stream @detect="onScanDetect" @error="onScanError" />
@ -91,7 +100,7 @@ export default class ContactQRScanShow extends Vue {
if (!identity) { if (!identity) {
throw new Error( throw new Error(
"Attempted to load Give records with no identity available.", "Attempted to show contact info with no identity available.",
); );
} }
return identity; return identity;
@ -106,17 +115,7 @@ export default class ContactQRScanShow extends Vue {
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts); const account = R.find((acc) => acc.did === this.activeDid, accounts);
if (!account) { if (account) {
this.$notify(
{
group: "alert",
type: "warning",
title: "",
text: "You have no identity yet.",
},
-1,
);
} else {
const identity = await this.getIdentity(this.activeDid); const identity = await this.getIdentity(this.activeDid);
const publicKeyHex = identity.keys[0].publicKeyHex; const publicKeyHex = identity.keys[0].publicKeyHex;
const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64"); const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64");

35
src/views/ContactsView.vue

@ -27,7 +27,7 @@
</span> </span>
<input <input
type="text" type="text"
placeholder="DID, Name, Public Key" placeholder="DID, Name, Public Key (base 16 or 64)"
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2" class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
v-model="contactInput" v-model="contactInput"
/> />
@ -110,6 +110,7 @@
</div> </div>
<div id="ContactActions" class="flex gap-1.5 mt-2"> <div id="ContactActions" class="flex gap-1.5 mt-2">
<div v-if="activeDid">
<button <button
v-if="contact.seesMe" 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 px-2 py-1.5 rounded-md"
@ -126,18 +127,18 @@
> >
<fa icon="eye-slash" class="fa-fw" /> <fa icon="eye-slash" class="fa-fw" />
</button> </button>
<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 px-2 py-1.5 rounded-md"
@click="checkVisibility(contact)" @click="checkVisibility(contact)"
title="Check Visibility" title="Check Visibility"
v-if="activeDid"
> >
<fa icon="rotate" class="fa-fw" /> <fa icon="rotate" class="fa-fw" />
</button> </button>
<button <button
@click="register(contact)" @click="register(contact)"
class="text-sm uppercase bg-slate-500 text-white ml-6 px-2 py-1.5 rounded-md" class="text-sm uppercase bg-slate-500 text-white ml-6 px-2 py-1.5 rounded-md"
v-if="activeDid"
> >
<fa <fa
v-if="contact.registered" v-if="contact.registered"
@ -152,6 +153,7 @@
title="Registration Unknown" title="Registration Unknown"
/> />
</button> </button>
</div>
<button <button
@click="deleteContact(contact)" @click="deleteContact(contact)"
@ -263,6 +265,7 @@ import {
SimpleSigner, SimpleSigner,
} from "@/libs/crypto"; } from "@/libs/crypto";
import { import {
CONTACT_URL_PREFIX,
GiveServerRecord, GiveServerRecord,
GiveVerifiableCredential, GiveVerifiableCredential,
RegisterVerifiableCredential, RegisterVerifiableCredential,
@ -303,6 +306,7 @@ export default class ContactsView extends Vue {
givenToMeUnconfirmed: Record<string, number> = {}; givenToMeUnconfirmed: Record<string, number> = {};
hourDescriptionInput = ""; hourDescriptionInput = "";
hourInput = "0"; hourInput = "0";
isRegistered = false;
showGiveNumbers = false; showGiveNumbers = false;
showGiveTotals = true; showGiveTotals = true;
showGiveConfirmed = true; showGiveConfirmed = true;
@ -312,6 +316,7 @@ export default class ContactsView extends Vue {
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || ""; this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
this.isRegistered = !!settings?.isRegistered;
this.showGiveNumbers = !!settings?.showContactGivesInline; this.showGiveNumbers = !!settings?.showContactGivesInline;
if (this.showGiveNumbers) { if (this.showGiveNumbers) {
@ -475,6 +480,12 @@ export default class ContactsView extends Vue {
); );
return; return;
} }
if (this.contactInput.startsWith(CONTACT_URL_PREFIX)) {
await this.newContactFromScan(this.contactInput);
return;
}
let did = this.contactInput; let did = this.contactInput;
let name, publicKeyBase64; let name, publicKeyBase64;
const commaPos1 = this.contactInput.indexOf(","); const commaPos1 = this.contactInput.indexOf(",");
@ -493,7 +504,7 @@ export default class ContactsView extends Vue {
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64"); publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
} }
const newContact = { did, name, publicKeyBase64 }; const newContact = { did, name, publicKeyBase64 };
return this.addContact(newContact); await this.addContact(newContact);
} }
async newContactFromScan(url: string): Promise<void> { async newContactFromScan(url: string): Promise<void> {
@ -540,18 +551,25 @@ export default class ContactsView extends Vue {
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""), (a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
allContacts, allContacts,
); );
let addedMessage;
if (this.activeDid) {
this.setVisibility(newContact, true, false); this.setVisibility(newContact, true, false);
addedMessage =
newContact.name +
" was added, and your activity is visible to them.";
} else {
addedMessage = newContact.name + " was added.";
}
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "success", type: "success",
title: "Contact Added", title: "Contact Added",
text: text: addedMessage,
newContact.name +
" was added, and your activity is visible to them.",
}, },
-1, 5000,
); );
if (this.isRegistered) {
// putting this last so that it shows on the top // putting this last so that it shows on the top
this.$notify( this.$notify(
{ {
@ -565,6 +583,7 @@ export default class ContactsView extends Vue {
}, },
-1, -1,
); );
}
}) })
.catch((err) => { .catch((err) => {
console.error("Error when adding contact to storage:", err); console.error("Error when adding contact to storage:", err);

2
src/views/DiscoverView.vue

@ -358,10 +358,8 @@ export default class DiscoverView extends Vue {
const plans: ProjectData[] = results.data; const plans: ProjectData[] = results.data;
for (const plan of plans) { for (const plan of plans) {
const { name, description, handleId = plan.handleId, rowid } = plan; const { name, description, handleId = plan.handleId, rowid } = plan;
if (beforeId !== plan["rowid"]) {
this.projects.push({ name, description, handleId, rowid }); this.projects.push({ name, description, handleId, rowid });
} }
}
} else { } else {
this.projects = results.data; this.projects = results.data;
} }

10
src/views/HomeView.vue

@ -104,7 +104,7 @@
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm" class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
v-if="record.jwtId == feedLastViewedId" v-if="record.jwtId == feedLastViewedId"
> >
You've seen all the following before You've seen all the following
</div> </div>
<div class="flex"> <div class="flex">
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa> <fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
@ -171,13 +171,7 @@ export default class HomeView extends Vue {
.equals(activeDid) .equals(activeDid)
.first()) as Account; .first()) as Account;
const identity = JSON.parse(account?.identity || "null"); const identity = JSON.parse(account?.identity || "null");
return identity; // may be null
if (!identity) {
throw new Error(
"Attempted to load Give records with no identity available.",
);
}
return identity;
} }
public async getHeaders(identity: IIdentifier) { public async getHeaders(identity: IIdentifier) {

22
src/views/NewEditProjectView.vue

@ -11,7 +11,7 @@
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><fa icon="chevron-left" class="fa-fw"></fa ><fa icon="chevron-left" class="fa-fw"></fa
></router-link> ></router-link>
[New/Edit] Plan Edit Idea
</h1> </h1>
</div> </div>
@ -24,7 +24,7 @@
<input <input
type="text" type="text"
placeholder="Project Name" placeholder="Idea Name"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
v-model="projectName" v-model="projectName"
/> />
@ -34,10 +34,10 @@
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
rows="5" rows="5"
v-model="description" v-model="description"
maxlength="500" maxlength="5000"
></textarea> ></textarea>
<div class="text-xs text-slate-500 italic -mt-3 mb-4"> <div class="text-xs text-slate-500 italic -mt-3 mb-4">
{{ description.length }}/500 max. characters {{ description.length }}/5000 max. characters
</div> </div>
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
@ -293,6 +293,20 @@ export default class NewEditProjectView extends Vue {
2000, 2000,
this, this,
); );
} else {
console.log(
"Got unexpected 'data' inside response from server",
resp,
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Saving Idea",
text: "Server did not save the idea. Try again.",
},
-1,
);
} }
} catch (error) { } catch (error) {
let userMessage = "There was an error saving the project."; let userMessage = "There was an error saving the project.";

12
src/views/ProjectViewView.vue

@ -12,7 +12,7 @@
> >
<fa icon="chevron-left" class="fa-fw"></fa> <fa icon="chevron-left" class="fa-fw"></fa>
</button> </button>
View Plan Idea
</h1> </h1>
</div> </div>
@ -148,7 +148,7 @@
<div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4"> <div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4">
<div class="bg-slate-100 px-4 py-3 rounded-md"> <div class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3"> <h3 class="text-sm uppercase font-semibold mb-3">
Offered To This Project Offered To This Idea
</h3> </h3>
<div v-if="offersToThis.length === 0"> <div v-if="offersToThis.length === 0">
@ -180,9 +180,7 @@
</div> </div>
<div class="bg-slate-100 px-4 py-3 rounded-md"> <div class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3"> <h3 class="text-sm uppercase font-semibold mb-3">Given To This Idea</h3>
Given To This Project
</h3>
<div v-if="givesToThis.length === 0">(None yet. Record one above.)</div> <div v-if="givesToThis.length === 0">(None yet. Record one above.)</div>
@ -216,7 +214,7 @@
class="bg-slate-100 px-4 py-3 rounded-md" class="bg-slate-100 px-4 py-3 rounded-md"
> >
<h3 class="text-sm uppercase font-semibold mb-3"> <h3 class="text-sm uppercase font-semibold mb-3">
Contributions To This Project Contributions To This Idea
</h3> </h3>
<ul> <ul>
<li v-for="plan in fulfillersToThis" :key="plan.handleId"> <li v-for="plan in fulfillersToThis" :key="plan.handleId">
@ -232,7 +230,7 @@
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md"> <div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3"> <h3 class="text-sm uppercase font-semibold mb-3">
Contributions By This Project Contributions By This Idea
</h3> </h3>
<button <button
@click="onClickLoadProject(fulfilledByThis.handleId)" @click="onClickLoadProject(fulfilledByThis.handleId)"

12
sw_scripts/additional-scripts.js

@ -5,7 +5,7 @@ importScripts(
); );
self.addEventListener("install", (event) => { self.addEventListener("install", (event) => {
console.error(event); console.error("Adding event listener for:", event);
importScripts( importScripts(
"safari-notifications.js", "safari-notifications.js",
"nacl.js", "nacl.js",
@ -23,7 +23,8 @@ self.addEventListener("push", function (event) {
payload = JSON.parse(event.data.text()); payload = JSON.parse(event.data.text());
} }
const message = await self.getNotificationCount(); const message = await self.getNotificationCount();
console.error(message); if (message) {
console.log("Will notify user:", message);
const title = payload ? payload.title : "Custom Title"; const title = payload ? payload.title : "Custom Title";
const options = { const options = {
body: message, body: message,
@ -31,8 +32,11 @@ self.addEventListener("push", function (event) {
badge: payload ? payload.badge : "badge.png", badge: payload ? payload.badge : "badge.png",
}; };
await self.registration.showNotification(title, options); await self.registration.showNotification(title, options);
} else {
console.log("No notification message, so will not tell the user.");
}
} catch (error) { } catch (error) {
console.error("Error in processing the push event:", error); console.error("Error processing the push event:", error);
} }
})(), })(),
); );
@ -51,7 +55,7 @@ self.addEventListener("activate", (event) => {
}); });
self.addEventListener("fetch", (event) => { self.addEventListener("fetch", (event) => {
console.log(event.request); console.log("Got fetch event", event.request);
}); });
self.addEventListener("error", (event) => { self.addEventListener("error", (event) => {

11
sw_scripts/safari-notifications.js

@ -466,6 +466,7 @@ async function getNotificationCount() {
if ("secret" in self) { if ("secret" in self) {
secret = self.secret; secret = self.secret;
const secretUint8Array = self.decodeBase64(secret); const secretUint8Array = self.decodeBase64(secret);
// 1 is our master settings ID; see MASTER_SETTINGS_KEY
const settings = await getSettingById(1); const settings = await getSettingById(1);
let lastNotifiedClaimId = null; let lastNotifiedClaimId = null;
if ("lastNotifiedClaimId" in settings) { if ("lastNotifiedClaimId" in settings) {
@ -496,7 +497,7 @@ async function getNotificationCount() {
headers["Authorization"] = "Bearer " + (await accessToken(identifier)); headers["Authorization"] = "Bearer " + (await accessToken(identifier));
let response = await fetch( let response = await fetch(
"https://test-api.endorser.ch/api/v2/report/claims", settings["apiServer"] + "/api/v2/report/claims",
{ {
method: "GET", method: "GET",
headers: headers, headers: headers,
@ -513,15 +514,13 @@ async function getNotificationCount() {
} }
newClaims++; newClaims++;
} }
if (newClaims === 0) { if (newClaims > 0) {
result = "You have no new claims today."; result = `There are ${newClaims} new activities on TimeSafari`;
} else {
result = `${newClaims} have been shared with you`;
} }
const most_recent_notified = claims[0]["id"]; const most_recent_notified = claims[0]["id"];
await setMostRecentNotified(most_recent_notified); await setMostRecentNotified(most_recent_notified);
} else { } else {
console.error(response.status); console.error("The service worker got a bad response status when fetching claims:", response.status, response);
} }
break; break;
} }

Loading…
Cancel
Save