add ability to import from Endorser Mobile CSV
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
|
- anchor hash into BTC
|
||||||
- image on give
|
- image on give
|
||||||
- Show a camera to take a picture
|
- Show a camera to take a picture
|
||||||
- Scale the image to a reasonable size
|
- Scale the image to a reasonable size
|
||||||
@@ -11,7 +12,6 @@ tasks:
|
|||||||
- Rates - images erased?
|
- Rates - images erased?
|
||||||
- image not associated with JWT ULID since that's assigned later
|
- image not associated with JWT ULID since that's assigned later
|
||||||
- mark a project as inactive
|
- mark a project as inactive
|
||||||
- make a shortcut for BVC
|
|
||||||
- add share button for sending a message to confirmers when we can't see the claim
|
- add share button for sending a message to confirmers when we can't see the claim
|
||||||
- add TimeSafari as a shareable app https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
|
- add TimeSafari as a shareable app https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
|
||||||
- choose a project's alternative agent ("authorized representative") via a contact chooser (not just copy-paste a DID)
|
- choose a project's alternative agent ("authorized representative") via a contact chooser (not just copy-paste a DID)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { Contact } from "@/db/tables/contacts";
|
|||||||
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
||||||
// the object in RegisterAction claims
|
// the object in RegisterAction claims
|
||||||
export const SERVICE_ID = "endorser.ch";
|
export const SERVICE_ID = "endorser.ch";
|
||||||
|
// the header line for contacts exported via Endorser Mobile
|
||||||
|
export const CONTACT_CSV_HEADER = "name,did,pubKeyBase64,seesMe,registered";
|
||||||
// the prefix for the contact URL
|
// the prefix for the contact URL
|
||||||
export const CONTACT_URL_PREFIX = "https://endorser.ch";
|
export const CONTACT_URL_PREFIX = "https://endorser.ch";
|
||||||
// the suffix for the contact URL
|
// the suffix for the contact URL
|
||||||
|
|||||||
@@ -26,10 +26,10 @@
|
|||||||
>
|
>
|
||||||
<fa icon="qrcode" class="fa-fw text-2xl" />
|
<fa icon="qrcode" class="fa-fw text-2xl" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<input
|
<textarea
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="URL or DID, Name, Public Key, Next Public Key Hash"
|
placeholder="URL or 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"
|
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2 h-10"
|
||||||
v-model="contactInput"
|
v-model="contactInput"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -297,6 +297,7 @@ import {
|
|||||||
SimpleSigner,
|
SimpleSigner,
|
||||||
} from "@/libs/crypto";
|
} from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
|
CONTACT_CSV_HEADER,
|
||||||
CONTACT_URL_PREFIX,
|
CONTACT_URL_PREFIX,
|
||||||
GiveServerRecord,
|
GiveServerRecord,
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
@@ -307,6 +308,7 @@ import * as libsUtil from "@/libs/util";
|
|||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
|
import { IndexableType } from "dexie";
|
||||||
|
|
||||||
// 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;
|
||||||
@@ -365,7 +367,7 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (this.contactEndorserUrl) {
|
if (this.contactEndorserUrl) {
|
||||||
await this.newContactFromScan(this.contactEndorserUrl);
|
await this.addContactFromScan(this.contactEndorserUrl);
|
||||||
localStorage.removeItem("contactEndorserUrl");
|
localStorage.removeItem("contactEndorserUrl");
|
||||||
this.contactEndorserUrl = "";
|
this.contactEndorserUrl = "";
|
||||||
}
|
}
|
||||||
@@ -535,7 +537,46 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.contactInput.startsWith(CONTACT_URL_PREFIX)) {
|
if (this.contactInput.startsWith(CONTACT_URL_PREFIX)) {
|
||||||
await this.newContactFromScan(this.contactInput);
|
await this.addContactFromScan(this.contactInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.contactInput.startsWith(CONTACT_CSV_HEADER)) {
|
||||||
|
const lines = this.contactInput.split(/\n/);
|
||||||
|
const lineAdded = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!line.trim() || line.startsWith(CONTACT_CSV_HEADER)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lineAdded.push(this.addContactFromEndorserMobileLine(line));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await Promise.all(lineAdded);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Contacts Added",
|
||||||
|
text: "Each contact was added. Nothing was sent to the server.",
|
||||||
|
},
|
||||||
|
-1, // keeping it up so that the "visibility" message is seen
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Contacts Maybe Added",
|
||||||
|
text: "An error occurred. Some contacts may have been added.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const allContacts = await db.contacts.toArray();
|
||||||
|
this.contacts = R.sort(
|
||||||
|
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
||||||
|
allContacts,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +617,48 @@ export default class ContactsView extends Vue {
|
|||||||
await this.addContact(newContact);
|
await this.addContact(newContact);
|
||||||
}
|
}
|
||||||
|
|
||||||
async newContactFromScan(url: string): Promise<void> {
|
async addContactFromEndorserMobileLine(line: string): Promise<IndexableType> {
|
||||||
|
// Note that Endorser Mobile puts name first, then did, etc.
|
||||||
|
let name = line;
|
||||||
|
let did = "";
|
||||||
|
let publicKeyInput, seesMe, registered;
|
||||||
|
const commaPos1 = line.indexOf(",");
|
||||||
|
if (commaPos1 > -1) {
|
||||||
|
name = line.substring(0, commaPos1).trim();
|
||||||
|
did = line.substring(commaPos1 + 1).trim();
|
||||||
|
const commaPos2 = line.indexOf(",", commaPos1 + 1);
|
||||||
|
if (commaPos2 > -1) {
|
||||||
|
did = line.substring(commaPos1 + 1, commaPos2).trim();
|
||||||
|
publicKeyInput = line.substring(commaPos2 + 1).trim();
|
||||||
|
const commaPos3 = line.indexOf(",", commaPos2 + 1);
|
||||||
|
if (commaPos3 > -1) {
|
||||||
|
publicKeyInput = line.substring(commaPos2 + 1, commaPos3).trim();
|
||||||
|
seesMe = line.substring(commaPos3 + 1).trim() == "true";
|
||||||
|
const commaPos4 = line.indexOf(",", commaPos3 + 1);
|
||||||
|
if (commaPos4 > -1) {
|
||||||
|
seesMe = line.substring(commaPos3 + 1, commaPos4).trim() == "true";
|
||||||
|
registered = line.substring(commaPos4 + 1).trim() == "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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,
|
||||||
|
seesMe,
|
||||||
|
registered,
|
||||||
|
};
|
||||||
|
return db.contacts.add(newContact);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addContactFromScan(url: string): Promise<void> {
|
||||||
const payload = getContactPayloadFromJwtUrl(url);
|
const payload = getContactPayloadFromJwtUrl(url);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -661,7 +743,7 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
if (err.name === "ConstraintError") {
|
if (err.name === "ConstraintError") {
|
||||||
message +=
|
message +=
|
||||||
"Check that the contact doesn't conflict with any you already have.";
|
" Check that the contact doesn't conflict with any you already have.";
|
||||||
}
|
}
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user