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. 141
      src/views/ContactsView.vue
  5. 4
      src/views/DiscoverView.vue
  6. 10
      src/views/HomeView.vue
  7. 22
      src/views/NewEditProjectView.vue
  8. 12
      src/views/ProjectViewView.vue
  9. 26
      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.
- 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).)
- 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:
- 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 :
- 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
- .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.
- .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?
- 04 allow user to download claims, mine + ones I can see about me from others
- .5 customize favicon assignee-group:ui
- .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
- .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 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
- 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+ :
- 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
@ -69,6 +58,7 @@ tasks:
- .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 don't show "Offer" on project screen if they aren't registered
- 24 Move to Vite
- 32 accept images for projects

25
src/views/ContactQRScanShowView.vue

@ -20,7 +20,7 @@
</h1>
</div>
<div @click="onCopyToClipboard()">
<div @click="onCopyToClipboard()" v-if="activeDid">
<!--
Play with display options: https://qr-code-styling.com/
See docs: https://www.npmjs.com/package/qr-code-generator-vue3
@ -32,6 +32,15 @@
class="flex justify-center"
/>
</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>
<qrcode-stream @detect="onScanDetect" @error="onScanError" />
@ -91,7 +100,7 @@ export default class ContactQRScanShow extends Vue {
if (!identity) {
throw new Error(
"Attempted to load Give records with no identity available.",
"Attempted to show contact info with no identity available.",
);
}
return identity;
@ -106,17 +115,7 @@ export default class ContactQRScanShow extends Vue {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts);
if (!account) {
this.$notify(
{
group: "alert",
type: "warning",
title: "",
text: "You have no identity yet.",
},
-1,
);
} else {
if (account) {
const identity = await this.getIdentity(this.activeDid);
const publicKeyHex = identity.keys[0].publicKeyHex;
const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64");

141
src/views/ContactsView.vue

@ -27,7 +27,7 @@
</span>
<input
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"
v-model="contactInput"
/>
@ -110,48 +110,50 @@
</div>
<div id="ContactActions" class="flex gap-1.5 mt-2">
<button
v-if="contact.seesMe"
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
@click="setVisibility(contact, false, true)"
title="They can see you"
>
<fa icon="eye" class="fa-fw" />
</button>
<button
v-else
class="text-sm uppercase bg-slate-500 text-white 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"
@click="checkVisibility(contact)"
title="Check Visibility"
>
<fa icon="rotate" class="fa-fw" />
</button>
<button
@click="register(contact)"
class="text-sm uppercase bg-slate-500 text-white ml-6 px-2 py-1.5 rounded-md"
>
<fa
v-if="contact.registered"
icon="person-circle-check"
class="fa-fw"
title="Registered"
/>
<fa
<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"
@click="setVisibility(contact, false, true)"
title="They can see you"
>
<fa icon="eye" class="fa-fw" />
</button>
<button
v-else
icon="person-circle-question"
class="fa-fw"
title="Registration Unknown"
/>
</button>
class="text-sm uppercase bg-slate-500 text-white 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"
@click="checkVisibility(contact)"
title="Check Visibility"
v-if="activeDid"
>
<fa icon="rotate" class="fa-fw" />
</button>
<button
@click="register(contact)"
class="text-sm uppercase bg-slate-500 text-white ml-6 px-2 py-1.5 rounded-md"
v-if="activeDid"
>
<fa
v-if="contact.registered"
icon="person-circle-check"
class="fa-fw"
title="Registered"
/>
<fa
v-else
icon="person-circle-question"
class="fa-fw"
title="Registration Unknown"
/>
</button>
</div>
<button
@click="deleteContact(contact)"
@ -263,6 +265,7 @@ import {
SimpleSigner,
} from "@/libs/crypto";
import {
CONTACT_URL_PREFIX,
GiveServerRecord,
GiveVerifiableCredential,
RegisterVerifiableCredential,
@ -303,6 +306,7 @@ export default class ContactsView extends Vue {
givenToMeUnconfirmed: Record<string, number> = {};
hourDescriptionInput = "";
hourInput = "0";
isRegistered = false;
showGiveNumbers = false;
showGiveTotals = true;
showGiveConfirmed = true;
@ -312,6 +316,7 @@ export default class ContactsView extends Vue {
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.isRegistered = !!settings?.isRegistered;
this.showGiveNumbers = !!settings?.showContactGivesInline;
if (this.showGiveNumbers) {
@ -475,6 +480,12 @@ export default class ContactsView extends Vue {
);
return;
}
if (this.contactInput.startsWith(CONTACT_URL_PREFIX)) {
await this.newContactFromScan(this.contactInput);
return;
}
let did = this.contactInput;
let name, publicKeyBase64;
const commaPos1 = this.contactInput.indexOf(",");
@ -493,7 +504,7 @@ export default class ContactsView extends Vue {
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
}
const newContact = { did, name, publicKeyBase64 };
return this.addContact(newContact);
await this.addContact(newContact);
}
async newContactFromScan(url: string): Promise<void> {
@ -540,31 +551,39 @@ export default class ContactsView extends Vue {
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
allContacts,
);
this.setVisibility(newContact, true, false);
let addedMessage;
if (this.activeDid) {
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(
{
group: "alert",
type: "success",
title: "Contact Added",
text:
newContact.name +
" was added, and your activity is visible to them.",
text: addedMessage,
},
-1,
);
// putting this last so that it shows on the top
this.$notify(
{
group: "alert",
type: "info",
title: "New User?",
text:
"If " +
newContact.name +
" is a new user, be sure to register them.",
},
-1,
5000,
);
if (this.isRegistered) {
// putting this last so that it shows on the top
this.$notify(
{
group: "alert",
type: "info",
title: "New User?",
text:
"If " +
newContact.name +
" is a new user, be sure to register them.",
},
-1,
);
}
})
.catch((err) => {
console.error("Error when adding contact to storage:", err);

4
src/views/DiscoverView.vue

@ -358,9 +358,7 @@ export default class DiscoverView extends Vue {
const plans: ProjectData[] = results.data;
for (const plan of plans) {
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 {
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"
v-if="record.jwtId == feedLastViewedId"
>
You've seen all the following before
You've seen all the following
</div>
<div class="flex">
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
@ -171,13 +171,7 @@ export default class HomeView extends Vue {
.equals(activeDid)
.first()) as Account;
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load Give records with no identity available.",
);
}
return identity;
return identity; // may be null
}
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"
><fa icon="chevron-left" class="fa-fw"></fa
></router-link>
[New/Edit] Plan
Edit Idea
</h1>
</div>
@ -24,7 +24,7 @@
<input
type="text"
placeholder="Project Name"
placeholder="Idea Name"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
v-model="projectName"
/>
@ -34,10 +34,10 @@
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
rows="5"
v-model="description"
maxlength="500"
maxlength="5000"
></textarea>
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
{{ description.length }}/500 max. characters
{{ description.length }}/5000 max. characters
</div>
<div class="flex items-center mb-4">
@ -293,6 +293,20 @@ export default class NewEditProjectView extends Vue {
2000,
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) {
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>
</button>
View Plan
Idea
</h1>
</div>
@ -148,7 +148,7 @@
<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">
<h3 class="text-sm uppercase font-semibold mb-3">
Offered To This Project
Offered To This Idea
</h3>
<div v-if="offersToThis.length === 0">
@ -180,9 +180,7 @@
</div>
<div class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3">
Given To This Project
</h3>
<h3 class="text-sm uppercase font-semibold mb-3">Given To This Idea</h3>
<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"
>
<h3 class="text-sm uppercase font-semibold mb-3">
Contributions To This Project
Contributions To This Idea
</h3>
<ul>
<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">
<h3 class="text-sm uppercase font-semibold mb-3">
Contributions By This Project
Contributions By This Idea
</h3>
<button
@click="onClickLoadProject(fulfilledByThis.handleId)"

26
sw_scripts/additional-scripts.js

@ -5,7 +5,7 @@ importScripts(
);
self.addEventListener("install", (event) => {
console.error(event);
console.error("Adding event listener for:", event);
importScripts(
"safari-notifications.js",
"nacl.js",
@ -23,16 +23,20 @@ self.addEventListener("push", function (event) {
payload = JSON.parse(event.data.text());
}
const message = await self.getNotificationCount();
console.error(message);
const title = payload ? payload.title : "Custom Title";
const options = {
body: message,
icon: payload ? payload.icon : "icon.png",
badge: payload ? payload.badge : "badge.png",
};
await self.registration.showNotification(title, options);
if (message) {
console.log("Will notify user:", message);
const title = payload ? payload.title : "Custom Title";
const options = {
body: message,
icon: payload ? payload.icon : "icon.png",
badge: payload ? payload.badge : "badge.png",
};
await self.registration.showNotification(title, options);
} else {
console.log("No notification message, so will not tell the user.");
}
} 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) => {
console.log(event.request);
console.log("Got fetch event", event.request);
});
self.addEventListener("error", (event) => {

11
sw_scripts/safari-notifications.js

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

Loading…
Cancel
Save