Compare commits

..

1 Commits

Author SHA1 Message Date
Jose Olarte III
a7f15fde25 Added gatekeeping dialogs for PWA install requirement 2023-09-11 16:35:29 +08:00
13 changed files with 21596 additions and 1087 deletions

22240
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,6 @@
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-axios": "^3.5.2", "vue-axios": "^3.5.2",
"vue-facing-decorator": "^2.1.20", "vue-facing-decorator": "^2.1.20",
"vue-qrcode-reader": "^5.3.4",
"vue-router": "^4.2.3", "vue-router": "^4.2.3",
"web-did-resolver": "^2.0.27" "web-did-resolver": "^2.0.27"
}, },

View File

@@ -1,13 +1,15 @@
tasks: tasks:
- 08 Scan QR code to import into contacts assignee:matthew
- SEE - https://github.com/gruhn/vue-qrcode-reader
- in endorser-push-server - mount folder for persistent sqlite DB outside of container - in endorser-push-server - mount folder for persistent sqlite DB outside of container
- test alerts on all pages -- or refactor to new "notify" (since AlertMessage refactoring may require a change, et. ContactQRScanShowView) - test alerts on all pages -- or refactor to new "notify" (since AlertMessage refactoring may require a change, et. ContactQRScanShowView)
- .2 bug - on contacts view, click on "to" & "from" and nothing happens - .2 bug - on contacts view, click on "to" & "from" and nothing happens
- 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
- .2 Rename repo to crowd-sourcing-for-time (because Kickstarter is a corporation)
- 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:matthew - 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:matthew
- 01 fix the Discovery map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui - 01 fix the Discovery map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
@@ -24,7 +26,6 @@ tasks:
- 24 Move to Vite assignee:matthew - 24 Move to Vite assignee:matthew
- .5 Allow edit of a contact name (but not the DID)
- .2 Edit Plan does not have icons across the bottom assignee-group:ui - .2 Edit Plan does not have icons across the bottom assignee-group:ui
- .5 include the hash of the latest commit, and maybe a version - .5 include the hash of the latest commit, and maybe a version
- .5 add link to further project / people when a project pays ahead - .5 add link to further project / people when a project pays ahead
@@ -36,7 +37,6 @@ tasks:
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent - .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164 - .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show" - .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
- .1 change default server in app.ts
- Discuss whether the remaining tasks are worthwhile before MVP release. - Discuss whether the remaining tasks are worthwhile before MVP release.

View File

@@ -246,6 +246,75 @@
</div> </div>
</div> </div>
</div> </div>
<div
v-if="notification.type === 'pwa-install-gate-ios'"
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
>
<div
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">
<fa
icon="mobile-screen-button"
class="inline-block text-7xl text-slate-400 mb-4"
>
</fa>
<h3 class="text-2xl font-semibold mb-4">Add to Home Screen</h3>
<p class="text-md mb-4">
To install the app, you need to add this website to your home
screen.
</p>
<p class="text-md">
In your Safari browser menu, tap the
<span class="whitespace-nowrap">
<fa
icon="arrow-up-from-bracket"
class="fa-fw text-slate-500 bg-slate-200 py-1 -my-1 px-0.5 rounded"
>
</fa>
Share
</span>
icon and choose
<b>Add to Home Screen</b> in the options. Then, open the Time
Safari app on your home screen.
</p>
</div>
</div>
</div>
<div
v-if="notification.type === 'pwa-install-gate-android'"
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
>
<div
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">
<fa
icon="mobile-screen-button"
class="inline-block text-7xl text-slate-400 mb-4"
>
</fa>
<h3 class="text-2xl font-semibold mb-4">Add to Home Screen</h3>
<p class="text-md mb-4">
To install the app, you need to add this website to your home
screen.
</p>
<p class="text-md">
In your Chrome browser menu, tap the
<span class="whitespace-nowrap">
<fa
icon="ellipsis-vertical"
class="fa-fw text-slate-500 bg-slate-200 py-1 -my-1 px-0.5 rounded"
>
</fa>
More
</span>
button and choose
<b>Install App</b> in the options.
</p>
</div>
</div>
</div>
</div> </div>
</Notification> </Notification>
</div> </div>

View File

@@ -1,10 +1,8 @@
/** /**
* Generic strings that could be used throughout the app. * Generic strings that could be used throughout the app.
*
* See also ../libs/veramo/setup.ts
*/ */
export enum AppString { export enum AppString {
APP_NAME = "Time Safari", APP_NAME = "Kick-Start with Time",
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch", PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch", TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
@@ -12,13 +10,3 @@ export enum AppString {
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER, DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
} }
/**
* See notiwind package
*/
export interface NotificationIface {
group: string;
type: string; // "toast" | "info" | "success" | "warning" | "danger"
title: string;
text: string;
}

View File

@@ -7,5 +7,5 @@ export interface Contact {
} }
export const ContactsSchema = { export const ContactsSchema = {
contacts: "&did, name, publicKeyBase64, registered, seesMe", contacts: "++did, name, publicKeyBase64, registered, seesMe",
}; };

View File

@@ -1,4 +1,5 @@
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
import { getRandomBytesSync } from "ethereum-cryptography/random"; import { getRandomBytesSync } from "ethereum-cryptography/random";
import { entropyToMnemonic } from "ethereum-cryptography/bip39"; import { entropyToMnemonic } from "ethereum-cryptography/bip39";
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english"; import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
@@ -6,9 +7,6 @@ import { HDNode } from "@ethersproject/hdnode";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import * as u8a from "uint8arrays"; import * as u8a from "uint8arrays";
import { ENDORSER_JWT_URL_LOCATION } from "@/libs/endorserServer";
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
export const DEFAULT_ROOT_DERIVATION_PATH = "m/76798669'/0'/0'/0'"; export const DEFAULT_ROOT_DERIVATION_PATH = "m/76798669'/0'/0'/0'";
/** /**
@@ -152,24 +150,3 @@ export function fromJose(signature: string): {
export function bytesToHex(b: Uint8Array): string { export function bytesToHex(b: Uint8Array): string {
return u8a.toString(b, "base16"); return u8a.toString(b, "base16");
} }
/**
@return results of uportJwtPayload:
{ iat: number, iss: string (DID), own: { name, publicEncKey (base64-encoded key) } }
Note that similar code is also contained in time-safari
*/
export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
let jwtText = jwtUrlText;
const endorserContextLoc = jwtText.indexOf(ENDORSER_JWT_URL_LOCATION);
if (endorserContextLoc > -1) {
jwtText = jwtText.substring(
endorserContextLoc + ENDORSER_JWT_URL_LOCATION.length,
);
}
// JWT format: { header, payload, signature, data }
const jwt = didJwt.decodeJWT(jwtText);
return jwt.payload;
};

View File

@@ -6,12 +6,7 @@ import { Axios, AxiosResponse } from "axios";
import { Contact } from "@/db/tables/contacts"; 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
export const SERVICE_ID = "endorser.ch"; export const SERVICE_ID = "endorser.ch";
// the prefix for the contact URL
export const CONTACT_URL_PREFIX = "https://endorser.ch";
// the suffix for the contact URL
export const ENDORSER_JWT_URL_LOCATION = "/contact?jwt=";
export interface AgreeVerifiableCredential { export interface AgreeVerifiableCredential {
"@context": string; "@context": string;

View File

@@ -1,7 +1,151 @@
// see also ../constants/app.ts and // Created from the setup in https://veramo.io/docs/guides/react_native
// Core interfaces
/* import {
createAgent,
IDIDManager,
IResolver,
IDataStore,
IKeyManager,
} from "@veramo/core";
*/
// Core identity manager plugin
//import { DIDManager } from "@veramo/did-manager";
// Ethr did identity provider
//import { EthrDIDProvider } from "@veramo/did-provider-ethr";
// Core key manager plugin
//import { KeyManager } from "@veramo/key-manager";
// Custom key management system for RN
//import { KeyManagementSystem } from '@veramo/kms-local-react-native'
// Custom resolver
// Custom resolvers
//import { DIDResolverPlugin } from "@veramo/did-resolver";
/* import { Resolver } from "did-resolver";
import { getResolver as ethrDidResolver } from "ethr-did-resolver";
import { getResolver as webDidResolver } from "web-did-resolver";
*/
// for VCs and VPs https://veramo.io/docs/api/credential-w3c
//import { CredentialIssuer } from '@veramo/credential-w3c'
// Storage plugin using TypeOrm
/* import {
Entities,
KeyStore,
DIDStore,
IDataStoreORM,
} from "@veramo/data-store";
*/
// TypeORM is installed with @veramo/typeorm
//import { createConnection } from 'typeorm'
//import * as R from "ramda";
/*
import { Contact } from '../entity/contact'
import { Settings } from '../entity/settings'
import { PrivateData } from '../entity/privateData'
import { Initial1616938713828 } from '../migration/1616938713828-initial'
import { SettingsContacts1616967972293 } from '../migration/1616967972293-settings-contacts'
import { EncryptedSeed1637856484788 } from '../migration/1637856484788-EncryptedSeed'
import { HomeScreenConfig1639947962124 } from '../migration/1639947962124-HomeScreenConfig'
import { HandlePublicKeys1652142819353 } from '../migration/1652142819353-HandlePublicKeys'
import { LastClaimsSeen1656811846836 } from '../migration/1656811846836-LastClaimsSeen'
import { ContactRegistered1662256903367 }from '../migration/1662256903367-ContactRegistered'
import { PrivateData1663080623479 } from '../migration/1663080623479-PrivateData'
const ALL_ENTITIES = Entities.concat([Contact, Settings, PrivateData])
// Create react native DB connection configured by ormconfig.js
export const dbConnection = createConnection({
database: 'endorser-mobile.sqlite',
entities: ALL_ENTITIES,
location: 'default',
logging: ['error', 'info', 'warn'],
migrations: [ Initial1616938713828, SettingsContacts1616967972293, EncryptedSeed1637856484788, HomeScreenConfig1639947962124, HandlePublicKeys1652142819353, LastClaimsSeen1656811846836, ContactRegistered1662256903367, PrivateData1663080623479 ],
migrationsRun: true,
type: 'react-native',
})
*/
function didProviderName(netName: string) { function didProviderName(netName: string) {
return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName); return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName);
} }
export const DEFAULT_DID_PROVIDER_NAME = didProviderName("mainnet"); //const NETWORK_NAMES = ["mainnet", "rinkeby"];
const DEFAULT_DID_PROVIDER_NETWORK_NAME = "mainnet";
export const DEFAULT_DID_PROVIDER_NAME = didProviderName(
DEFAULT_DID_PROVIDER_NETWORK_NAME,
);
export const HANDY_APP = false;
// this is used as the object in RegisterAction claims
export const SERVICE_ID = "endorser.ch";
//const INFURA_PROJECT_ID = "INFURA_PROJECT_ID";
/*
const providers = {}
NETWORK_NAMES.forEach((networkName) => {
providers[didProviderName(networkName)] = new EthrDIDProvider({
defaultKms: 'local',
network: networkName,
rpcUrl: 'https://' + networkName + '.infura.io/v3/' + INFURA_PROJECT_ID,
gas: 1000001,
ttl: 60 * 60 * 24 * 30 * 12 + 1,
})
})
const didManager = new DIDManager({
store: new DIDStore(dbConnection),
defaultProvider: DEFAULT_DID_PROVIDER_NAME,
providers: providers,
})
*/
/* const basicDidResolvers = NETWORK_NAMES.map((networkName) => [
networkName,
new Resolver({
ethr: ethrDidResolver({
networks: [
{
name: networkName,
rpcUrl:
"https://" + networkName + ".infura.io/v3/" + INFURA_PROJECT_ID,
},
],
}).ethr,
web: webDidResolver().web,
}),
]);
const basicResolverMap = R.fromPairs(basicDidResolvers)
export const DEFAULT_BASIC_RESOLVER = basicResolverMap[DEFAULT_DID_PROVIDER_NETWORK_NAME]
const agentDidResolvers = NETWORK_NAMES.map((networkName) => {
return new DIDResolverPlugin({
resolver: basicResolverMap[networkName],
})
})
let allPlugins = [
new CredentialIssuer(),
new KeyManager({
store: new KeyStore(dbConnection),
kms: {
local: new KeyManagementSystem(),
},
}),
didManager,
].concat(agentDidResolvers)
*/
//export const agent = createAgent<IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver>({ plugins: allPlugins })

View File

@@ -13,6 +13,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import { import {
faArrowLeft, faArrowLeft,
faArrowRight, faArrowRight,
faArrowUpFromBracket,
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
@@ -39,6 +40,7 @@ import {
faLongArrowAltLeft, faLongArrowAltLeft,
faLongArrowAltRight, faLongArrowAltRight,
faMagnifyingGlass, faMagnifyingGlass,
faMobileScreenButton,
faPen, faPen,
faPersonCircleCheck, faPersonCircleCheck,
faPersonCircleQuestion, faPersonCircleQuestion,
@@ -59,6 +61,7 @@ import {
library.add( library.add(
faArrowLeft, faArrowLeft,
faArrowRight, faArrowRight,
faArrowUpFromBracket,
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
@@ -85,6 +88,7 @@ library.add(
faLongArrowAltLeft, faLongArrowAltLeft,
faLongArrowAltRight, faLongArrowAltRight,
faMagnifyingGlass, faMagnifyingGlass,
faMobileScreenButton,
faPen, faPen,
faPersonCircleCheck, faPersonCircleCheck,
faPersonCircleQuestion, faPersonCircleQuestion,

View File

@@ -2,7 +2,7 @@ import axios from "axios";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import { AppString } from "@/constants/app"; import { AppString } from "@/constants/app";
import { db } from "../db"; import { db } from "../db";
import { SERVICE_ID } from "../libs/endorserServer"; import { SERVICE_ID } from "../libs/veramo/setup";
import { deriveAddress, newIdentifier } from "../libs/crypto"; import { deriveAddress, newIdentifier } from "../libs/crypto";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";

View File

@@ -3,7 +3,7 @@
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Contact Info Your Contact Info
</h1> </h1>
@@ -17,17 +17,12 @@
:dotsOptions="{ type: 'square' }" :dotsOptions="{ type: 'square' }"
class="flex justify-center" class="flex justify-center"
/> />
<h1 class="text-4xl text-center font-light pt-4">Scan Contact Info</h1>
<qrcode-stream @detect="onScanDetect" @error="onScanError" />
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import QRCodeVue3 from "qr-code-generator-vue3"; import QRCodeVue3 from "qr-code-generator-vue3";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { QrcodeStream } from "vue-qrcode-reader";
import { accountsDB, db } from "@/db/index"; import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import * as R from "ramda"; import * as R from "ramda";
@@ -35,10 +30,6 @@ import { SimpleSigner } from "@/libs/crypto";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import {
CONTACT_URL_PREFIX,
ENDORSER_JWT_URL_LOCATION,
} from "@/libs/endorserServer";
// 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;
@@ -52,7 +43,6 @@ interface Notification {
@Component({ @Component({
components: { components: {
QrcodeStream,
QRCodeVue3, QRCodeVue3,
QuickNav, QuickNav,
}, },
@@ -122,47 +112,9 @@ export default class ContactQRScanShow extends Vue {
issuer: identity.did, issuer: identity.did,
signer: signer, signer: signer,
}); });
const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION; const viewPrefix = "https://endorser.ch/contact?jwt=";
this.qrValue = viewPrefix + vcJwt; this.qrValue = viewPrefix + vcJwt;
} }
} }
/**
*
* @param content is the result of a QR scan, an array with one item with a rawValue property
*/
// Unfortunately, there are not typescript definitions for the qrcode-stream component yet.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onScanDetect(content: any) {
if (content[0]?.rawValue) {
console.log("onDetect", content[0].rawValue);
localStorage.setItem("contactEndorserUrl", content[0].rawValue);
this.$router.push({ name: "contacts" });
} else {
this.$notify(
{
group: "alert",
type: "warning",
title: "Invalid Contact QR Code",
text: "No QR code detected with contact information.",
},
-1,
);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onScanError(error: any) {
console.log("Scan was invalid:", error);
this.$notify(
{
group: "alert",
type: "warning",
title: "Invalid Scan",
text: "The scan was invalid.",
},
-1,
);
}
} }
</script> </script>

View File

@@ -20,11 +20,6 @@
<!-- New Contact --> <!-- New Contact -->
<div class="mb-4 flex"> <div class="mb-4 flex">
<span class="self-center bg-slate-500 text-white px-1.5 py-1 rounded-md">
<router-link :to="{ name: 'contact-qr' }">
<fa icon="qrcode" class="fa-fw" />
</router-link>
</span>
<input <input
type="text" type="text"
placeholder="DID, Name, Public Key" placeholder="DID, Name, Public Key"
@@ -216,17 +211,11 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import * as R from "ramda"; import * as R from "ramda";
import { NotificationIface } from "@/constants/app";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import { accountsDB, db } from "@/db/index"; import { accountsDB, db } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { import { accessToken, SimpleSigner } from "@/libs/crypto";
accessToken,
getContactPayloadFromJwtUrl,
SimpleSigner,
} from "@/libs/crypto";
import { import {
GiveServerRecord, GiveServerRecord,
GiveVerifiableCredential, GiveVerifiableCredential,
@@ -240,16 +229,22 @@ import EntityIcon from "@/components/EntityIcon.vue";
// 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;
interface Notification {
group: string;
type: string;
title: string;
text: string;
}
@Component({ @Component({
components: { QuickNav, EntityIcon }, components: { QuickNav, EntityIcon },
}) })
export default class ContactsView extends Vue { export default class ContactsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
activeDid = ""; activeDid = "";
apiServer = ""; apiServer = "";
contacts: Array<Contact> = []; contacts: Array<Contact> = [];
contactEndorserUrl = localStorage.getItem("contactEndorserUrl") || "";
contactInput = ""; contactInput = "";
// { "did:...": concatenated-descriptions } entry for each contact // { "did:...": concatenated-descriptions } entry for each contact
givenByMeDescriptions: Record<string, string> = {}; givenByMeDescriptions: Record<string, string> = {};
@@ -284,12 +279,6 @@ export default class ContactsView extends Vue {
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""), (a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
allContacts, allContacts,
); );
if (this.contactEndorserUrl) {
await this.newContactFromScan(this.contactEndorserUrl);
localStorage.removeItem("contactEndorserUrl");
this.contactEndorserUrl = "";
}
} }
public async getIdentity(activeDid: string) { public async getIdentity(activeDid: string) {
@@ -425,18 +414,6 @@ export default class ContactsView extends Vue {
} }
async onClickNewContact(): Promise<void> { async onClickNewContact(): Promise<void> {
if (!this.contactInput) {
this.$notify(
{
group: "alert",
type: "warning",
title: "No Contact",
text: "There was no contact info to add.",
},
-1,
);
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(",");
@@ -455,74 +432,12 @@ 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 db.contacts.add(newContact);
}
async newContactFromScan(url: string): Promise<void> {
const payload = getContactPayloadFromJwtUrl(url);
if (!payload) {
this.$notify(
{
group: "alert",
type: "danger",
title: "No Contact Info",
text: "The contact info could not be parsed.",
},
-1,
);
return;
} else {
return this.addContact({
did: payload.iss,
name: payload.own.name,
publicKeyBase64: payload.own.publicEncKey,
} as Contact);
}
}
async addContact(newContact: Contact) {
if (!newContact.did) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Incomplete Contact",
text: "Cannot add a contact without a DID.",
},
-1,
);
return;
}
return db.contacts
.add(newContact)
.then(() => {
const allContacts = this.contacts.concat([newContact]); const allContacts = this.contacts.concat([newContact]);
this.contacts = R.sort( this.contacts = R.sort(
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""), (a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
allContacts, allContacts,
); );
this.$notify(
{
group: "alert",
type: "success",
title: "Contact added",
text: newContact.name + " was added.",
},
-1,
);
})
.catch((err) => {
console.error("Error when adding contact to storage:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Contact Not Added",
text: "An error prevented importing.",
},
-1,
);
});
} }
async deleteContact(contact: Contact) { async deleteContact(contact: Contact) {