Browse Source

Merge pull request 'New branch for cleanup and web push' (#65) from new-web-push into master

Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/kick-starter-for-time-pwa/pulls/65
friend-tech-inspired-pwa-dialog
anomalist 1 year ago
parent
commit
2db662c125
  1. 177
      sample.txt
  2. 43
      src/db/tables/accounts.ts
  3. 116
      src/libs/endorserServer.ts
  4. 447
      src/views/AccountViewView.vue

177
sample.txt

@ -0,0 +1,177 @@
> kickstart-for-time-pwa@0.1.0 build
> vue-cli-service build
All browser targets in the browserslist configuration have supported ES module.
Therefore we don't build two separate bundles for differential loading.
WARNING Compiled with 5 warnings6:06:43 PM
[eslint]
/home/matthew/projects/kick-starter-for-time-pwa/src/components/World/components/objects/landmarks.js
98:11 warning Unexpected console statement no-console
133:7 warning Unexpected console statement no-console
144:5 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/router/index.ts
210:3 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/AccountViewView.vue
362:7 warning Unexpected console statement no-console
375:7 warning Unexpected console statement no-console
404:7 warning Unexpected console statement no-console
516:7 warning Unexpected console statement no-console
536:7 warning Unexpected console statement no-console
630:5 warning Unexpected console statement no-console
682:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ContactAmountsView.vue
206:9 warning Unexpected console statement no-console
233:9 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ContactGiftingView.vue
244:9 warning Unexpected console statement no-console
267:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ContactsView.vue
340:9 warning Unexpected console statement no-console
577:9 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/DiscoverView.vue
315:9 warning Unexpected console statement no-console
343:7 warning Unexpected console statement no-console
390:9 warning Unexpected console statement no-console
423:7 warning Unexpected console statement no-console
532:9 warning Unexpected console statement no-console
575:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/HomeView.vue
349:9 warning Unexpected console statement no-console
498:9 warning Unexpected console statement no-console
521:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/IdentitySwitcherView.vue
142:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ImportAccountView.vue
123:9 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ImportDerivedAccountView.vue
159:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/NewEditProjectView.vue
183:9 warning Unexpected console statement no-console
215:7 warning Unexpected console statement no-console
297:13 warning Unexpected console statement no-console
320:11 warning Unexpected console statement no-console
345:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ProjectViewView.vue
387:9 warning Unexpected console statement no-console
421:7 warning Unexpected console statement no-console
457:7 warning Unexpected console statement no-console
552:9 warning Unexpected console statement no-console
554:11 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ProjectsView.vue
131:9 warning Unexpected console statement no-console
144:7 warning Unexpected console statement no-console
221:9 warning Unexpected console statement no-console
237:7 warning Unexpected console statement no-console
/home/matthew/projects/kick-starter-for-time-pwa/src/views/SeedBackupView.vue
94:7 warning Unexpected console statement no-console
✖ 44 problems (0 errors, 44 warnings)
You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.
warning
/models/lupine_plant/textures/lambert2SG_baseColor.png is 3.75 MB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.
warning
/models/lupine_plant/textures/lambert2SG_normal.png is 4.91 MB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.
warning
asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
js/project.44f30c9f.js (318 KiB)
js/statistics.8a97010a.js (586 KiB)
js/chunk-vendors.a4845bfb.js (411 KiB)
js/705.f6a6ce2a.js (252 KiB)
img/textures/leafy-autumn-forest-floor.jpg (705 KiB)
models/lupine_plant/textures/lambert2SG_baseColor.png (3.58 MiB)
models/lupine_plant/textures/lambert2SG_normal.png (4.69 MiB)
warning
entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
app (447 KiB)
js/chunk-vendors.a4845bfb.js
css/app.8f21529c.css
js/app.8833cebc.js
File Size Gzipped
dist/js/statistics.8a97010a.js 585.72 KiB 148.80 KiB
dist/js/chunk-vendors.a4845bfb.js 411.44 KiB 137.82 KiB
dist/js/project.44f30c9f.js 317.61 KiB 78.67 KiB
dist/js/705.f6a6ce2a.js 251.66 KiB 87.12 KiB
dist/js/891.33615e4f.js 147.32 KiB 42.09 KiB
dist/js/153.e2c8e249.js 146.26 KiB 42.21 KiB
dist/js/820.13565d16.js 66.10 KiB 18.33 KiB
dist/js/contact-qr.e170ec33.js 54.85 KiB 15.63 KiB
dist/js/772.7b4c53a7.js 30.29 KiB 7.21 KiB
dist/js/361.898a4525.js 27.40 KiB 8.19 KiB
dist/js/account.77d86130.js 17.51 KiB 5.93 KiB
dist/js/app.8833cebc.js 17.31 KiB 5.84 KiB
dist/js/contacts.3fc90ff8.js 16.94 KiB 5.52 KiB
dist/js/discover.24106939.js 15.30 KiB 5.22 KiB
dist/js/536.3bb13201.js 15.23 KiB 4.84 KiB
dist/workbox-5b385ed2.js 14.11 KiB 4.93 KiB
dist/js/home.218b99dd.js 13.89 KiB 4.97 KiB
dist/js/help.50d3117b.js 12.49 KiB 4.38 KiB
dist/js/projects.417a6cb7.js 8.71 KiB 3.00 KiB
dist/js/contact-amounts.a32b0ccd.js 8.44 KiB 3.25 KiB
dist/js/229.120e09bf.js 7.99 KiB 2.72 KiB
dist/js/identity-switcher.c7937333.js 7.44 KiB 2.52 KiB
dist/js/new-edit-project.0552181b.js 7.36 KiB 3.11 KiB
dist/js/300.dcaeb2a3.js 6.56 KiB 3.24 KiB
dist/js/seed-backup.76a0f7b3.js 3.99 KiB 1.97 KiB
dist/js/import-derive.c688d4b8.js 3.81 KiB 1.82 KiB
dist/js/import-account.c3fa35fd.js 3.54 KiB 1.66 KiB
dist/js/new-edit-account.bb763be2.js 3.39 KiB 1.51 KiB
dist/js/431.5a6d64e0.js 3.38 KiB 2.56 KiB
dist/service-worker.js 3.37 KiB 1.38 KiB
dist/js/scan-contact.46be989a.js 2.79 KiB 1.18 KiB
dist/js/start.091a7740.js 2.70 KiB 1.30 KiB
dist/js/new-identifier.bb379420.js 2.12 KiB 1.18 KiB
dist/js/93.b873dbbf.js 2.08 KiB 1.61 KiB
dist/js/new-edit-commitment.9248d367.j 1.96 KiB 1.05 KiB
s
dist/js/confirm-contact.02004d1d.js 1.89 KiB 1.04 KiB
dist/js/858.ae4c08ec.js 0.97 KiB 0.78 KiB
dist/css/app.8f21529c.css 18.41 KiB 4.39 KiB
dist/css/discover.73ee9bd3.css 14.77 KiB 6.25 KiB
dist/css/new-edit-project.73ee9bd3.css 14.77 KiB 6.25 KiB
dist/css/contacts.abb5e493.css 0.40 KiB 0.23 KiB
dist/css/contact-amounts.5b26ccd4.css 0.31 KiB 0.20 KiB
dist/css/home.828bc66e.css 0.25 KiB 0.19 KiB
dist/css/project.828bc66e.css 0.25 KiB 0.19 KiB
dist/css/statistics.828bc66e.css 0.25 KiB 0.19 KiB
Images and other types of assets omitted.
Build at: 2023-09-07T10:06:43.972Z - Hash: 2b39fcd4d0e78263 - Time: 32016ms
DONE Build complete. The dist directory is ready to be deployed.
INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html

43
src/db/tables/accounts.ts

@ -1,17 +1,50 @@
/**
* Represents an account stored in the database.
*/
export type Account = { export type Account = {
id?: number; // auto-generated by Dexie /**
* Auto-generated ID by Dexie.
*/
id?: number;
/**
* The date the account was created.
*/
dateCreated: string; dateCreated: string;
/**
* The derivation path for the account.
*/
derivationPath: string; derivationPath: string;
/**
* Decentralized Identifier (DID) for the account.
*/
did: string; did: string;
// stringified JSON containing underlying key material of type IIdentifier
// https://github.com/uport-project/veramo/blob/next/packages/core-types/src/types/IIdentifier.ts /**
* Stringified JSON containing underlying key material.
* Based on the IIdentifier type from Veramo.
* @see {@link https://github.com/uport-project/veramo/blob/next/packages/core-types/src/types/IIdentifier.ts}
*/
identity: string; identity: string;
/**
* The public key in hexadecimal format.
*/
publicKeyHex: string; publicKeyHex: string;
/**
* The mnemonic passphrase for the account.
*/
mnemonic: string; mnemonic: string;
}; };
// mark encrypted field by starting with a $ character /**
// see https://github.com/PVermeer/dexie-addon-suite-monorepo/tree/master/packages/dexie-encrypted-addon * Schema for the accounts table in the database.
* Fields starting with a $ character are encrypted.
* @see {@link https://github.com/PVermeer/dexie-addon-suite-monorepo/tree/master/packages/dexie-encrypted-addon}
*/
export const AccountsSchema = { export const AccountsSchema = {
accounts: accounts:
"++id, dateCreated, derivationPath, did, $identity, $mnemonic, publicKeyHex", "++id, dateCreated, derivationPath, did, $identity, $mnemonic, publicKeyHex",

116
src/libs/endorserServer.ts

@ -115,24 +115,20 @@ export function isHiddenDid(did: string) {
export function didInfo( export function didInfo(
did: string, did: string,
activeDid: string, activeDid: string,
allMyDids: Array<string>, allMyDids: string[],
contacts: Array<Contact>, contacts: Contact[],
): string { ): string {
const myId: string | undefined = R.find(R.equals(did), allMyDids); const myId = R.find(R.equals(did), allMyDids);
if (myId) { if (myId) return `You${myId !== activeDid ? " (Alt ID)" : ""}`;
return "You" + (myId !== activeDid ? " (Alt ID)" : "");
} else { const contact = R.find((c) => c.did === did, contacts);
const contact: Contact | undefined = R.find((c) => c.did === did, contacts); return contact
if (contact) { ? contact.name || "Someone Unnamed in Contacts"
return contact.name || "Someone Unnamed in Contacts"; : !did
} else if (!did) { ? "Unspecified Person"
return "Unspecified Person"; : isHiddenDid(did)
} else if (isHiddenDid(did)) { ? "Someone Not In Network"
return "Someone Not In Network"; : "Someone Not In Contacts";
} else {
return "Someone Not In Contacts";
}
}
} }
export interface ResultWithType { export interface ResultWithType {
@ -171,30 +167,18 @@ export async function createAndSubmitGive(
fulfillsProjectHandleId?: string, fulfillsProjectHandleId?: string,
): Promise<CreateAndSubmitGiveResult> { ): Promise<CreateAndSubmitGiveResult> {
try { try {
// Make a claim
const vcClaim: GiveVerifiableCredential = { const vcClaim: GiveVerifiableCredential = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "GiveAction", "@type": "GiveAction",
recipient: toDid ? { identifier: toDid } : undefined,
agent: fromDid ? { identifier: fromDid } : undefined,
description: description || undefined,
object: hours ? { amountOfThisGood: hours, unitCode: "HUR" } : undefined,
fulfills: fulfillsProjectHandleId
? { "@type": "PlanAction", identifier: fulfillsProjectHandleId }
: undefined,
}; };
if (toDid) {
vcClaim.recipient = { identifier: toDid };
}
if (fromDid) {
vcClaim.agent = { identifier: fromDid };
}
if (description) {
vcClaim.description = description;
}
if (hours) {
vcClaim.object = { amountOfThisGood: hours, unitCode: "HUR" };
}
if (fulfillsProjectHandleId) {
vcClaim.fulfills = {
"@type": "PlanAction",
identifier: fulfillsProjectHandleId,
};
}
// Make a payload for the claim
const vcPayload = { const vcPayload = {
vc: { vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"], "@context": ["https://www.w3.org/2018/credentials/v1"],
@ -205,14 +189,7 @@ export async function createAndSubmitGive(
// Create a signature using private key of identity // Create a signature using private key of identity
const firstKey = identity.keys[0]; const firstKey = identity.keys[0];
if (!firstKey || !firstKey.privateKeyHex) { const privateKeyHex = firstKey?.privateKeyHex;
throw {
error: "No private key",
message: `Your identifier ${identity.did} is not configured correctly. Use a different identifier.`,
};
}
const privateKeyHex = firstKey.privateKeyHex;
if (!privateKeyHex) { if (!privateKeyHex) {
throw { throw {
@ -222,48 +199,35 @@ export async function createAndSubmitGive(
} }
const signer = await SimpleSigner(privateKeyHex); const signer = await SimpleSigner(privateKeyHex);
const alg = undefined;
// Create a JWT for the request // Create a JWT for the request
const vcJwt: string = await didJwt.createJWT(vcPayload, { const vcJwt: string = await didJwt.createJWT(vcPayload, {
alg: alg,
issuer: identity.did, issuer: identity.did,
signer: signer, signer,
}); });
// Make the xhr request payload // Make the xhr request payload
const payload = JSON.stringify({ jwtEncoded: vcJwt }); const payload = JSON.stringify({ jwtEncoded: vcJwt });
const url = apiServer + "/api/v2/claim"; const url = `${apiServer}/api/v2/claim`;
const token = await accessToken(identity); const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
const response = await axios.post(url, payload, { headers }); const response = await axios.post(url, payload, {
return { headers: {
type: "success", "Content-Type": "application/json",
response, Authorization: `Bearer ${token}`,
}; },
});
return { type: "success", response };
} catch (error: unknown) { } catch (error: unknown) {
let errorMessage: string; const errorMessage: string =
error === null
if (error instanceof Error) { ? "Null error"
// If it's a JavaScript Error object : error instanceof Error
errorMessage = error.message; ? error.message
} else if ( : typeof error === "object" && error !== null && "message" in error
typeof error === "object" && ? (error as { message: string }).message
error !== null && : "Unknown error";
"message" in error
) {
// If it's an object that has a 'message' property
errorMessage = (error as { message: string }).message;
} else {
// Unknown error shape, default message
errorMessage = "Unknown error";
}
return { return {
type: "error", type: "error",

447
src/views/AccountViewView.vue

@ -334,7 +334,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { AxiosError } from "axios/index"; import { AxiosError } from "axios";
import "dexie-export-import"; import "dexie-export-import";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
@ -357,6 +357,21 @@ interface Notification {
text: string; text: string;
} }
interface IAccount {
did: string;
publicKeyHex: string;
privateHex?: string;
derivationPath: string;
}
interface SettingsType {
activeDid?: string;
apiServer?: string;
firstName?: string;
lastName?: string;
showContactGivesInline?: boolean;
}
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class AccountViewView extends Vue { export default class AccountViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
@ -386,23 +401,56 @@ export default class AccountViewView extends Vue {
alertMessage = ""; alertMessage = "";
alertTitle = ""; alertTitle = "";
public async getIdentity(activeDid: string) { public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
await accountsDB.open(); try {
const account = await accountsDB.accounts // Open the accounts database
.where("did") await accountsDB.open();
.equals(activeDid) } catch (error) {
.first(); console.error("Failed to open accounts database:", error);
const identity = JSON.parse(account?.identity || "null"); return null;
return identity; }
let account: { identity?: string } | undefined;
try {
// Search for the account with the matching DID (decentralized identifier)
account = await accountsDB.accounts
.where("did")
.equals(activeDid)
.first();
} catch (error) {
console.error("Failed to find account:", error);
return null;
}
// Return parsed identity or null if not found
return JSON.parse(account?.identity || "null");
} }
public async getHeaders(identity: IIdentifier) { /**
const token = await accessToken(identity); * Asynchronously retrieves headers for HTTP requests.
const headers = { *
"Content-Type": "application/json", * @param {IIdentifier} identity - The identity object for which to generate the headers.
Authorization: "Bearer " + token, * @returns {Promise<Record<string,string>>} A Promise that resolves to an object containing the headers.
}; *
return headers; * @throws Will throw an error if unable to generate an access token.
*/
public async getHeaders(
identity: IIdentifier,
): Promise<Record<string, string>> {
try {
const token = await accessToken(identity);
const headers: Record<string, string> = {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
};
return headers;
} catch (error) {
console.error("Failed to get headers:", error);
return Promise.reject(error);
}
} }
// call fn, copy text to the clipboard, then redo fn after 2 seconds // call fn, copy text to the clipboard, then redo fn after 2 seconds
@ -427,60 +475,92 @@ export default class AccountViewView extends Vue {
this.numAccounts = await accountsDB.accounts.count(); this.numAccounts = await accountsDB.accounts.count();
} }
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
* handles identity-related tasks, and checks limitations.
*
* @throws Will display specific messages to the user based on different errors.
*/
async created() { async created() {
// Uncomment this to register this user on the test server.
// To manage within the vue devtools browser extension https://devtools.vuejs.org/
// assign this to a class variable, eg. "registerThisUser = testServerRegisterUser",
// select a component in the extension, and enter in the console: $vm.ctx.registerThisUser()
//testServerRegisterUser();
try { try {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.apiServerInput = settings?.apiServer || "";
this.firstName = settings?.firstName || "";
this.lastName = settings?.lastName || "";
this.showContactGives = !!settings?.showContactGivesInline;
const identity = await this.getIdentity(this.activeDid); // Initialize component state with values from the database or defaults
this.initializeState(settings);
// Get and process the identity
const identity = await this.getIdentity(this.activeDid);
if (identity) { if (identity) {
this.publicHex = identity.keys[0].publicKeyHex; this.processIdentity(identity);
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString(
"base64",
);
this.derivationPath = identity.keys[0].meta.derivationPath;
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: identity.did,
});
this.checkLimitsFor(identity);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: unknown) {
} catch (err: any) { this.handleError(err);
if ( }
err.message === }
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
*/
initializeState(settings: SettingsType | undefined) {
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.apiServerInput = settings?.apiServer || "";
this.firstName = settings?.firstName || "";
this.lastName = settings?.lastName || "";
this.showContactGives = !!settings?.showContactGivesInline;
}
/**
* Processes the identity and updates the component's state.
* @param {IdentityType} identity - Object containing identity information.
*/
processIdentity(identity: IIdentifier) {
if (
identity &&
identity.keys &&
identity.keys.length > 0 &&
identity.keys[0].meta
) {
this.publicHex = identity.keys[0].publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
this.derivationPath = identity.keys[0].meta.derivationPath;
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: identity.did,
});
this.checkLimitsFor(identity);
} else {
// Handle the case where any of these are null or undefined
}
}
/**
* Handles errors and updates the component's state accordingly.
* @param {Error} err - The error object.
*/
handleError(err: unknown) {
if (
err instanceof Error &&
err.message ===
"Attempted to load account records with no identity available." "Attempted to load account records with no identity available."
) { ) {
this.limitsMessage = "No identity."; this.limitsMessage = "No identity.";
this.loadingLimits = false; this.loadingLimits = false;
} else { } else {
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Error Creating Account", title: "Error Creating Account",
text: "Clear your cache and start over (after data backup).", text: "Clear your cache and start over (after data backup).",
}, },
-1, -1,
); );
console.error( console.error("Telling user to clear cache at page create because:", err);
"Telling user to clear cache at page create because:",
err,
);
}
} }
} }
@ -507,41 +587,96 @@ export default class AccountViewView extends Vue {
} }
} }
/**
* Asynchronously exports the database into a downloadable JSON file.
*
* @throws Will notify the user if there is an export error.
*/
public async exportDatabase() { public async exportDatabase() {
try { try {
const blob = await db.export({ prettyJson: true }); // Generate the blob from the database
const url = URL.createObjectURL(blob); const blob = await this.generateDatabaseBlob();
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement; // Create a temporary URL for the blob
downloadAnchor.href = url; const url = this.createBlobURL(blob);
downloadAnchor.download = db.name + "-backup.json";
downloadAnchor.click();
// Trigger the download
this.downloadDatabaseBackup(url);
// Revoke the temporary URL
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
this.$notify( // Notify the user that the download has started
{ this.notifyDownloadStarted();
group: "alert",
type: "toast",
title: "Download Started",
text: "See your downloads directory for the backup.",
},
5000,
);
} catch (error) { } catch (error) {
this.$notify( this.handleExportError(error);
{
group: "alert",
type: "danger",
title: "Export Error",
text: "See console logs for more info.",
},
-1,
);
console.error("Export Error:", error);
} }
} }
/**
* Generates a blob object representing the database.
*
* @returns {Promise<Blob>} The generated blob object.
*/
private async generateDatabaseBlob(): Promise<Blob> {
return await db.export({ prettyJson: true });
}
/**
* Creates a temporary URL for a blob object.
*
* @param {Blob} blob - The blob object.
* @returns {string} The temporary URL for the blob.
*/
private createBlobURL(blob: Blob): string {
return URL.createObjectURL(blob);
}
/**
* Triggers the download of the database backup.
*
* @param {string} url - The temporary URL for the blob.
*/
private downloadDatabaseBackup(url: string) {
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
downloadAnchor.href = url;
downloadAnchor.download = `${db.name}-backup.json`;
downloadAnchor.click();
}
/**
* Notifies the user that the download has started.
*/
private notifyDownloadStarted() {
this.$notify(
{
group: "alert",
type: "toast",
title: "Download Started",
text: "See your downloads directory for the backup.",
},
5000,
);
}
/**
* Handles errors during the database export process.
*
* @param {Error} error - The error object.
*/
private handleExportError(error: unknown) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Export Error",
text: "See console logs for more info.",
},
-1,
);
console.error("Export Error:", error);
}
async checkLimits() { async checkLimits() {
const identity = await this.getIdentity(this.activeDid); const identity = await this.getIdentity(this.activeDid);
if (identity) { if (identity) {
@ -549,68 +684,120 @@ export default class AccountViewView extends Vue {
} }
} }
async checkLimitsFor(identity: IIdentifier) { /**
* Asynchronously checks rate limits for the given identity.
*
* Updates component state variables `limits`, `limitsMessage`, and `loadingLimits`.
*/
public async checkLimitsFor(identity: IIdentifier) {
this.loadingLimits = true; this.loadingLimits = true;
this.limitsMessage = ""; this.limitsMessage = "";
try { try {
const url = this.apiServer + "/api/report/rateLimits"; const resp = await this.fetchRateLimits(identity);
const headers = await this.getHeaders(identity);
const resp = await this.axios.get(url, { headers });
// axios throws an exception on a 400
if (resp.status === 200) { if (resp.status === 200) {
this.limits = resp.data; this.limits = resp.data;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) {
} catch (error: any) { this.handleRateLimitsError(error);
if (
error.message ===
"Attempted to load Give records with no identity available."
) {
this.limitsMessage = "No identity.";
this.loadingLimits = false;
} else {
const serverError = error as AxiosError;
console.error("Bad response retrieving limits: ", serverError);
const data = (serverError.response &&
serverError.response.data) as ErrorResponse;
this.limitsMessage = data?.error?.message || "Bad server response.";
}
} }
this.loadingLimits = false; this.loadingLimits = false;
} }
async switchAccount(accountNum: number) { /**
// 0 means none * Fetches rate limits from the server.
if (accountNum === 0) { *
await db.open(); * @param {IIdentifier} identity - The identity object to check rate limits for.
db.settings.update(MASTER_SETTINGS_KEY, { * @returns {Promise<AxiosResponse>} The Axios response object.
activeDid: undefined, */
}); private async fetchRateLimits(identity: IIdentifier) {
this.activeDid = ""; const url = `${this.apiServer}/api/report/rateLimits`;
this.derivationPath = ""; const headers = await this.getHeaders(identity);
this.publicHex = ""; return await this.axios.get(url, { headers });
this.publicBase64 = ""; }
/**
* Handles errors that occur while fetching rate limits.
*
* @param {AxiosError | Error} error - The error object.
*/
private handleRateLimitsError(error: unknown) {
if (error instanceof AxiosError) {
const data = error.response?.data as ErrorResponse;
this.limitsMessage = data?.error?.message || "Bad server response.";
console.error("Bad response retrieving limits:", error);
} else if (
error instanceof Error &&
error.message ===
"Attempted to load Give records with no identity available."
) {
this.limitsMessage = "No identity.";
} else { } else {
await accountsDB.open(); // Handle other unknown errors
const accounts = await accountsDB.accounts.toArray(); }
const account = accounts[accountNum - 1]; }
await db.open(); /**
db.settings.update(MASTER_SETTINGS_KEY, { * Asynchronously switches the active account based on the provided account number.
activeDid: account.did, *
}); * @param {number} accountNum - The account number to switch to. 0 means none.
*/
public async switchAccount(accountNum: number) {
await db.open(); // Assumes db needs to be open for both cases
this.activeDid = account.did; if (accountNum === 0) {
this.derivationPath = account.derivationPath; this.switchToNoAccount();
this.publicHex = account.publicKeyHex; } else {
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64"); await this.switchToAccountNumber(accountNum);
} }
} }
/**
* Switches to no active account and clears relevant properties.
*/
private async switchToNoAccount() {
await db.settings.update(MASTER_SETTINGS_KEY, { activeDid: undefined });
this.clearActiveAccountProperties();
}
/**
* Clears properties related to the active account.
*/
private clearActiveAccountProperties() {
this.activeDid = "";
this.derivationPath = "";
this.publicHex = "";
this.publicBase64 = "";
}
/**
* Switches to an account based on its number in the list.
*
* @param {number} accountNum - The account number to switch to.
*/
private async switchToAccountNumber(accountNum: number) {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = accounts[accountNum - 1];
await db.settings.update(MASTER_SETTINGS_KEY, { activeDid: account.did });
this.updateActiveAccountProperties(account);
}
/**
* Updates properties related to the active account.
*
* @param {AccountType} account - The account object.
*/
private updateActiveAccountProperties(account: IAccount) {
this.activeDid = account.did;
this.derivationPath = account.derivationPath;
this.publicHex = account.publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
}
public showContactGivesClassNames() { public showContactGivesClassNames() {
return { return {
"bg-slate-900": !this.showContactGives, "bg-slate-900": !this.showContactGives,

Loading…
Cancel
Save