Browse Source

add sharing & copying instructions when asking contacts for help, and list all the visibleTo DIDs with an English description of their path

kb/add-usage-guide
Trent Larson 10 months ago
parent
commit
1053b78ab8
  1. 6
      project.task.yaml
  2. 8
      src/libs/endorserServer.ts
  3. 21
      src/libs/util.ts
  4. 2
      src/main.ts
  5. 5
      src/views/AccountViewView.vue
  6. 113
      src/views/ClaimView.vue

6
project.task.yaml

@ -3,6 +3,8 @@ tasks:
- .5 give list of visible IDs on the claim detail page - .5 give list of visible IDs on the claim detail page
- instead of text, make links to share the data with contacts and ask for help - instead of text, make links to share the data with contacts and ask for help
- Add TimeSafari as a shareable app https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
- make a shortcut for BVC
- choose an agent via a contact chooser (not just copy-paste a DID) - choose an agent via a contact chooser (not just copy-paste a DID)
- .5 find out why clicking quickly back-and-forth onto the "my project" page often shows error "You need an identifier to load your projects." (easier to reproduce on desktop?) - .5 find out why clicking quickly back-and-forth onto the "my project" page often shows error "You need an identifier to load your projects." (easier to reproduce on desktop?)
- .5 bug - it didn't show the "fulfills offer" on the claim detail page for a give that had one - https://test.timesafari.app/claim/01HMFWRPA3PD6Q9EYFKX3MC41J - .5 bug - it didn't show the "fulfills offer" on the claim detail page for a give that had one - https://test.timesafari.app/claim/01HMFWRPA3PD6Q9EYFKX3MC41J
@ -17,7 +19,7 @@ tasks:
- make the "give" on contact screen work like other give (allowing donation vs current blank) - make the "give" on contact screen work like other give (allowing donation vs current blank)
- on ClaimView, the "ask someone" should refer to "visible" IDs, or to confirmations only if confirmations are visible - on ClaimView, the "ask someone" should refer to "visible" IDs, or to confirmations only if confirmations are visible
- notify user when data import is completed - notify user when data import is completed
- "send them to this page" on ClaimView should be a link (for installed app) - message "send them to this page" on ClaimView should be a link (for installed app)
- When we update a version, desktop browser users have seen nothing happen after clicking on the contact page QR and on the account page "Help"; errors show in the console. Reload fixed it. If this happens on mobile, ask the user to reload. - When we update a version, desktop browser users have seen nothing happen after clicking on the contact page QR and on the account page "Help"; errors show in the console. Reload fixed it. If this happens on mobile, ask the user to reload.
- 01 show my VCs - most interesting, or via search - 01 show my VCs - most interesting, or via search
@ -122,6 +124,8 @@ tasks:
- 16 From the home screen, make the quick action even easier. - 16 From the home screen, make the quick action even easier.
- allow some gives even if they aren't registered - maybe someday as a gift to the world, but we really want this to be built via personal connections - allow some gives even if they aren't registered - maybe someday as a gift to the world, but we really want this to be built via personal connections
- .1 When Chrome shows compatibility https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare
then change the canShare check in this app to check the real canShare() method.
log: log:
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29 - videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29

8
src/libs/endorserServer.ts

@ -174,21 +174,21 @@ export function isEmptyOrHiddenDid(did?: string) {
* Similar logic is found in endorser-mobile. * Similar logic is found in endorser-mobile.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function testRecursivelyOnString(func: (arg0: any) => boolean, input: any) { function testRecursivelyOnStrings(func: (arg0: any) => boolean, input: any) {
if (Object.prototype.toString.call(input) === "[object String]") { if (Object.prototype.toString.call(input) === "[object String]") {
return func(input); return func(input);
} else if (input instanceof Object) { } else if (input instanceof Object) {
if (!Array.isArray(input)) { if (!Array.isArray(input)) {
// it's an object // it's an object
for (const key in input) { for (const key in input) {
if (testRecursivelyOnString(func, input[key])) { if (testRecursivelyOnStrings(func, input[key])) {
return true; return true;
} }
} }
} else { } else {
// it's an array // it's an array
for (const value of input) { for (const value of input) {
if (testRecursivelyOnString(func, value)) { if (testRecursivelyOnStrings(func, value)) {
return true; return true;
} }
} }
@ -201,7 +201,7 @@ function testRecursivelyOnString(func: (arg0: any) => boolean, input: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function containsHiddenDid(obj: any) { export function containsHiddenDid(obj: any) {
return testRecursivelyOnString(isHiddenDid, obj); return testRecursivelyOnStrings(isHiddenDid, obj);
} }
export function stripEndorserPrefix(claimId: string) { export function stripEndorserPrefix(claimId: string) {

21
src/libs/util.ts

@ -99,13 +99,17 @@ export const canFulfillOffer = (veriClaim: GenericServerRecord) => {
export function findAllVisibleToDids( export function findAllVisibleToDids(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
input: any, input: any,
humanReadable = false,
): Record<string, Array<string>> { ): Record<string, Array<string>> {
if (Array.isArray(input)) { if (Array.isArray(input)) {
const result: Record<string, Array<string>> = {}; const result: Record<string, Array<string>> = {};
for (let i = 0; i < input.length; i++) { for (let i = 0; i < input.length; i++) {
const inside = findAllVisibleToDids(input[i]); const inside = findAllVisibleToDids(input[i], humanReadable);
for (const key in inside) { for (const key in inside) {
result["[" + i + "]" + key] = inside[key]; const pathKey = humanReadable
? "#" + (i + 1) + " " + key
: "[" + i + "]" + key;
result[pathKey] = inside[key];
} }
} }
return result; return result;
@ -115,11 +119,15 @@ export function findAllVisibleToDids(
for (const key in input) { for (const key in input) {
if (key.endsWith("VisibleToDids")) { if (key.endsWith("VisibleToDids")) {
const newKey = key.slice(0, -"VisibleToDids".length); const newKey = key.slice(0, -"VisibleToDids".length);
result["." + newKey] = input[key]; const pathKey = humanReadable ? newKey : "." + newKey;
result[pathKey] = input[key];
} else { } else {
const inside = findAllVisibleToDids(input[key]); const inside = findAllVisibleToDids(input[key], humanReadable);
for (const insideKey in inside) { for (const insideKey in inside) {
result["." + key + insideKey] = inside[insideKey]; const pathKey = humanReadable
? key + "'s " + insideKey
: "." + key + insideKey;
result[pathKey] = inside[insideKey];
} }
} }
} }
@ -135,7 +143,10 @@ export function findAllVisibleToDids(
pkgx +deno.land sh pkgx +deno.land sh
deno
import * as R from 'ramda'; import * as R from 'ramda';
//import { findAllVisibleToDids } from './src/libs/util'; // doesn't work because other dependencies fail so gotta copy-and-paste function
console.log(R.equals(findAllVisibleToDids(null), {})); console.log(R.equals(findAllVisibleToDids(null), {}));
console.log(R.equals(findAllVisibleToDids(9), {})); console.log(R.equals(findAllVisibleToDids(9), {}));

2
src/main.ts

@ -46,6 +46,7 @@ import {
faLongArrowAltRight, faLongArrowAltRight,
faMagnifyingGlass, faMagnifyingGlass,
faMessage, faMessage,
faMinus,
faPen, faPen,
faPersonCircleCheck, faPersonCircleCheck,
faPersonCircleQuestion, faPersonCircleQuestion,
@ -101,6 +102,7 @@ library.add(
faLongArrowAltRight, faLongArrowAltRight,
faMagnifyingGlass, faMagnifyingGlass,
faMessage, faMessage,
faMinus,
faPen, faPen,
faPersonCircleCheck, faPersonCircleCheck,
faPersonCircleQuestion, faPersonCircleQuestion,

5
src/views/AccountViewView.vue

@ -469,6 +469,7 @@
import { AxiosError, AxiosRequestConfig } from "axios"; import { AxiosError, AxiosRequestConfig } from "axios";
import Dexie from "dexie"; import Dexie from "dexie";
import "dexie-export-import"; import "dexie-export-import";
import { ImportProgress } from "dexie-export-import/dist/import";
import { ref } from "vue"; import { ref } from "vue";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
@ -481,7 +482,6 @@ import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import { ErrorResponse, RateLimits } from "@/libs/endorserServer"; import { ErrorResponse, RateLimits } from "@/libs/endorserServer";
import { ImportProgress } from "dexie-export-import/dist/import";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer; const Buffer = require("buffer/").Buffer;
@ -526,14 +526,11 @@ export default class AccountViewView extends Vue {
limitsMessage = ""; limitsMessage = "";
loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message
showContactGives = false; showContactGives = false;
showDidCopy = false; showDidCopy = false;
showDerCopy = false; showDerCopy = false;
showB64Copy = false; showB64Copy = false;
showPubCopy = false; showPubCopy = false;
showAdvanced = false; showAdvanced = false;
subscription: PushSubscription | null = null; subscription: PushSubscription | null = null;
warnIfProdServer = false; warnIfProdServer = false;
warnIfTestServer = false; warnIfTestServer = false;

113
src/views/ClaimView.vue

@ -235,29 +235,87 @@
</div> </div>
<div> <div>
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Visible Details</h2> <h2 class="font-bold uppercase text-xl mt-8 mb-2">
<div v-if="Object.keys(veriClaimDidsVisible).length > 0"> {{ serverUtil.containsHiddenDid(veriClaim) ? "Visible " : "" }}Details
</h2>
<div
v-if="
serverUtil.containsHiddenDid(veriClaim) &&
R.isEmpty(veriClaimDidsVisible)
"
class="mb-2"
>
Some of the details are not visible to you; they show as "HIDDEN". They
are not visible to any of your direct contacts, either.
<span v-if="canShare">
If you'd like to ask any of your contacts to take a look and see if
their contacts can see more details,
<a @click="onClickShareClaim()" class="text-blue-500"
>click to send them this info</a
>
and see if they are willing to make an introduction.
</span>
<span v-else>
If you'd like to ask any of your contacts to take a look and see if
their contacts can see more details,
<a @click="copyToClipboard(windowLocation)" class="text-blue-500"
>share this page with them</a
>
and see if they are willing to make an introduction.
</span>
</div>
<div v-if="!R.isEmpty(veriClaimDidsVisible)">
Some of the details are not visible to you but they are visible to some Some of the details are not visible to you but they are visible to some
of your contacts. If you'd like an introduction, share the information of your contacts.
with them and ask them to connect you. <span v-if="canShare">
If you'd like an introduction,
<a @click="onClickShareClaim()" class="text-blue-500"
>click to share the information with them and ask if they'll tell
you more about the participants.</a
>
</span>
<span v-else>
If you'd like an introduction,
<a @click="copyToClipboard(windowLocation)" class="text-blue-500"
>share this page with them and ask if they'll tell you more about
about the participants.</a
>
</span>
<div <div
v-for="(visibleDidPath, index) of Object.keys(veriClaimDidsVisible)" v-for="(visibleDidPath, index) of Object.keys(veriClaimDidsVisible)"
:key="index" :key="index"
class="list-disc flex justify-start p-2" class="list-disc p-4"
> >
<div class="text-sm p-4"> <div class="text-sm">
{{ visibleDidPath }} <fa icon="minus" class="fa-fw"></fa>
The {{ visibleDidPath }} is visible to:
</div> </div>
<div class="p-4"> <div class="ml-12 p-1">
<ul> <ul>
<li <li
v-for="(visDid, idx2) of veriClaimDidsVisible[visibleDidPath]" v-for="(visDid, idx2) of veriClaimDidsVisible[visibleDidPath]"
:key="idx2" :key="idx2"
class="list-disc" class="list-disc"
> >
<div class="text-sm"> <div class="text-sm mt-2">
{{ didInfo(visDid) }} <span>
{{ veriClaim.publicUrls?.[visDid] || "" }} {{ didInfo(visDid)
}}<span v-if="veriClaim.publicUrls?.[visDid]"
>, found at
<fa icon="globe" class="fa-fw text-slate-400"></fa
>&nbsp;<a
:href="veriClaim.publicUrls?.[visDid]"
class="text-blue-500"
>{{
veriClaim.publicUrls[visDid].substring(
veriClaim.publicUrls[visDid].indexOf("//") + 2,
)
}}
</a>
</span>
</span>
</div> </div>
</li> </li>
</ul> </ul>
@ -308,6 +366,7 @@ import * as yaml from "js-yaml";
import * as R from "ramda"; import * as R from "ramda";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import OfferDialog from "@/components/OfferDialog.vue"; import OfferDialog from "@/components/OfferDialog.vue";
@ -341,6 +400,7 @@ export default class ClaimView extends Vue {
allContacts: Array<Contact> = []; allContacts: Array<Contact> = [];
apiServer = ""; apiServer = "";
canShare = false;
confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer
confsVisibleErrorMessage = ""; confsVisibleErrorMessage = "";
confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer
@ -355,7 +415,9 @@ export default class ClaimView extends Vue {
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
veriClaimDump = ""; veriClaimDump = "";
veriClaimDidsVisible = {}; veriClaimDidsVisible = {};
windowLocation = window.location.href;
R = R;
yaml = yaml; yaml = yaml;
libsUtil = libsUtil; libsUtil = libsUtil;
serverUtil = serverUtil; serverUtil = serverUtil;
@ -405,6 +467,10 @@ export default class ClaimView extends Vue {
-1, -1,
); );
} }
// When Chrome compatibility is fixed https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare
// then use this truer check: navigator.canShare && navigator.canShare()
this.canShare = !!navigator.share;
} }
// insert a space before any capital letters except the initial letter // insert a space before any capital letters except the initial letter
@ -472,6 +538,7 @@ export default class ClaimView extends Vue {
this.veriClaimDump = yaml.dump(this.veriClaim); this.veriClaimDump = yaml.dump(this.veriClaim);
this.veriClaimDidsVisible = libsUtil.findAllVisibleToDids( this.veriClaimDidsVisible = libsUtil.findAllVisibleToDids(
this.veriClaim, this.veriClaim,
true,
); );
} else { } else {
// actually, axios typically throws an error so we never get here // actually, axios typically throws an error so we never get here
@ -688,5 +755,29 @@ export default class ClaimView extends Vue {
this.veriClaim.handleId, this.veriClaim.handleId,
); );
} }
copyToClipboard(text: string) {
useClipboard()
.copy(text)
.then(() => {
this.$notify(
{
group: "alert",
type: "toast",
title: "Copied",
text: "Location was copied to clipboard.",
},
2000,
);
});
}
onClickShareClaim() {
window.navigator.share({
title: "Help Connect Me",
text: "I'm trying to find the full details of this claim. Can you help me?",
url: this.windowLocation,
});
}
} }
</script> </script>

Loading…
Cancel
Save