Compare commits

...

8 Commits

Author SHA1 Message Date
Matthew Raymer
3c515719e0 chore: update packages after merging electron fixes 2025-03-26 11:45:54 +00:00
Matthew Raymer
5bc7e93b4b Merge branch 'electron_fix_20250317' into fix-service-worker 2025-03-26 11:00:22 +00:00
Matthew Raymer
ee6124021d fix: improve service worker registration and debugging
- Add detailed logging for service worker registration process
- Fix PWA enabling logic in vite config to properly handle web builds
- Update serve script to explicitly set production mode
- Add better error handling and registration status reporting
2025-03-26 10:21:36 +00:00
Jose Olarte III
5143c65337 Reinforce entity icon sizes 2025-03-25 20:05:17 +08:00
Jose Olarte III
09ee94d5a3 Linting 2025-03-25 19:49:24 +08:00
071792b97c on home page: fix images for all persons, remove excessive verbiage, fix project icon, allow click on image to close 2025-03-24 20:51:16 -06:00
bf2f23021f change 'fa' to 'font-awesome' 2025-03-24 19:29:01 -06:00
829870b16c add some logging to the DB (especially for iOS app feed debugging) 2025-03-24 19:26:47 -06:00
11 changed files with 986 additions and 831 deletions

1475
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
}, },
"scripts": { "scripts": {
"dev": "vite --config vite.config.dev.mts", "dev": "vite --config vite.config.dev.mts",
"serve": "vite preview", "serve": "NODE_ENV=production vite preview --mode production --host",
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.mts", "build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.mts",
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src", "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",

View File

@@ -12,16 +12,22 @@
<div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4"> <div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4">
<div class="flex items-center gap-2 mb-6"> <div class="flex items-center gap-2 mb-6">
<img <div v-if="record.issuerDid">
src="https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg" <EntityIcon
class="size-8 object-cover rounded-full" :entity-id="record.issuerDid"
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
/> />
</div>
<div v-else>
<font-awesome
icon="person-circle-question"
class="text-slate-300 text-[2rem]"
/>
</div>
<div> <div>
<h3 class="font-semibold"> <h3 class="font-semibold">
{{ {{ record.issuer.known ? record.issuer.displayName : "" }}
record.giver.known ? record.giver.displayName : "Anonymous Giver"
}}
</h3> </h3>
<p class="ms-auto text-xs text-slate-500 italic"> <p class="ms-auto text-xs text-slate-500 italic">
{{ friendlyDate }} {{ friendlyDate }}
@@ -54,48 +60,40 @@
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3" class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
> >
<div class="relative w-fit mx-auto"> <div class="relative w-fit mx-auto">
<template v-if="record.giver.profileImageUrl"> <div>
<EntityIcon
:profile-image-url="record.giver.profileImageUrl"
:class="[
!record.providerPlanName
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
]"
/>
</template>
<template v-else>
<!-- Project Icon --> <!-- Project Icon -->
<template v-if="record.providerPlanName"> <div v-if="record.providerPlanName">
<ProjectIcon <ProjectIcon
:entity-id="record.providerPlanName" :entity-id="record.providerPlanName"
:icon-size="48" :icon-size="48"
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full" class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
/> />
</template> </div>
<!-- Identicon for DIDs --> <!-- Identicon for DIDs -->
<template v-else-if="record.giver.did"> <div v-else-if="record.agentDid">
<img <EntityIcon
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`" :entity-id="record.agentDid"
class="rounded-full size-[3rem] sm:size-[4rem]" :profile-image-url="record.issuer.profileImageUrl"
alt="Identicon" class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
/> />
</template> </div>
<!-- Unknown Person --> <!-- Unknown Person -->
<template v-else> <div v-else>
<fa <font-awesome
icon="person-circle-question" icon="person-circle-question"
class="text-slate-300 text-[3rem] sm:text-[4rem]" class="text-slate-300 text-[3rem] sm:text-[4rem]"
/> />
</template> </div>
</template> </div>
</div> </div>
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2"> <div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
<fa <div v-if="record.providerPlanName || record.giver.known">
:icon="record.providerPlanName ? 'building' : 'user'" <font-awesome
:icon="record.providerPlanName ? 'users' : 'user'"
class="fa-fw text-slate-400" class="fa-fw text-slate-400"
/> />
{{ record.giver.displayName }} {{ record.providerPlanName || record.giver.displayName }}
</div>
</div> </div>
</div> </div>
@@ -123,48 +121,40 @@
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3" class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
> >
<div class="relative w-fit mx-auto"> <div class="relative w-fit mx-auto">
<template v-if="record.receiver.profileImageUrl"> <div>
<EntityIcon
:profile-image-url="record.receiver.profileImageUrl"
:class="[
!record.recipientProjectName
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
]"
/>
</template>
<template v-else>
<!-- Project Icon --> <!-- Project Icon -->
<template v-if="record.recipientProjectName"> <div v-if="record.recipientProjectName">
<ProjectIcon <ProjectIcon
:entity-id="record.recipientProjectName" :entity-id="record.recipientProjectName"
:icon-size="48" :icon-size="48"
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full" class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
/> />
</template> </div>
<!-- Identicon for DIDs --> <!-- Identicon for DIDs -->
<template v-else-if="record.receiver.did"> <div v-else-if="record.recipientDid">
<img <EntityIcon
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`" :entity-id="record.recipientDid"
class="rounded-full size-[3rem] sm:size-[4rem]" :profile-image-url="record.receiver.profileImageUrl"
alt="Identicon" class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
/> />
</template> </div>
<!-- Unknown Person --> <!-- Unknown Person -->
<template v-else> <div v-else>
<fa <font-awesome
icon="person-circle-question" icon="person-circle-question"
class="text-slate-300 text-[3rem] sm:text-[4rem]" class="text-slate-300 text-[3rem] sm:text-[4rem]"
/> />
</template> </div>
</template> </div>
</div> </div>
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2"> <div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
<fa <div v-if="record.recipientProjectName || record.receiver.known">
:icon="record.recipientProjectName ? 'building' : 'user'" <font-awesome
:icon="record.recipientProjectName ? 'users' : 'user'"
class="fa-fw text-slate-400" class="fa-fw text-slate-400"
/> />
{{ record.receiver.displayName }} {{ record.recipientProjectName || record.receiver.displayName }}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -175,14 +165,13 @@
{{ description }} {{ description }}
</a> </a>
</p> </p>
<p class="text-sm">{{ subDescription }}</p>
</div> </div>
<div <div
class="flex items-center gap-2 text-lg bg-slate-300 rounded-b-md px-3 sm:px-4 py-1 sm:py-2" class="flex items-center gap-2 text-lg bg-slate-300 rounded-b-md px-3 sm:px-4 py-1 sm:py-2"
> >
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)"> <a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
<fa icon="circle-info" class="fa-fw text-slate-500" /> <font-awesome icon="circle-info" class="fa-fw text-slate-500" />
</a> </a>
</div> </div>
</li> </li>
@@ -220,53 +209,6 @@ export default class ActivityListItem extends Vue {
return amount; return amount;
} }
private formatParticipantInfo(): string {
const { giver, receiver } = this.record;
// Both participants are known contacts
if (giver.known && receiver.known) {
return `${giver.displayName} gave to ${receiver.displayName}`;
}
// Only giver is known
if (giver.known) {
const recipient = this.record.recipientProjectName
? `the project "${this.record.recipientProjectName}"`
: receiver.displayName;
return `${giver.displayName} gave to ${recipient}`;
}
// Only receiver is known
if (receiver.known) {
const provider = this.record.providerPlanName
? `the project "${this.record.providerPlanName}"`
: giver.displayName;
return `${receiver.displayName} received from ${provider}`;
}
// Neither is known
return this.formatUnknownParticipants();
}
private formatUnknownParticipants(): string {
const { giver, receiver, providerPlanName, recipientProjectName } =
this.record;
if (providerPlanName || recipientProjectName) {
const from = providerPlanName
? `the project "${providerPlanName}"`
: giver.displayName;
const to = recipientProjectName
? `the project "${recipientProjectName}"`
: receiver.displayName;
return `from ${from} to ${to}`;
}
return giver.displayName === receiver.displayName
? `between two who are ${giver.displayName}`
: `from ${giver.displayName} to ${receiver.displayName}`;
}
get description(): string { get description(): string {
const claim = const claim =
(this.record.fullClaim as unknown).claim || this.record.fullClaim; (this.record.fullClaim as unknown).claim || this.record.fullClaim;
@@ -278,12 +220,6 @@ export default class ActivityListItem extends Vue {
return `${claim.description}`; return `${claim.description}`;
} }
get subDescription(): string {
const participants = this.formatParticipantInfo();
return `${participants}`;
}
private displayAmount(code: string, amt: number) { private displayAmount(code: string, amt: number) {
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`; return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
} }
@@ -292,11 +228,6 @@ export default class ActivityListItem extends Vue {
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode; return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
} }
get formattedTimestamp() {
// Add your timestamp formatting logic here
return this.record.timestamp;
}
get canConfirm(): boolean { get canConfirm(): boolean {
if (!this.isRegistered) return false; if (!this.isRegistered) return false;
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false; if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;

View File

@@ -28,7 +28,7 @@
:src="imageUrl" :src="imageUrl"
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain" class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
alt="expanded shared content" alt="expanded shared content"
@click.stop @click="close"
/> />
</div> </div>
</div> </div>

View File

@@ -112,7 +112,6 @@ db.on("populate", async () => {
// ended up throwing lots of errors to the user... and they'd end up in a state // ended up throwing lots of errors to the user... and they'd end up in a state
// where they couldn't take action because they couldn't unlock that identity.) // where they couldn't take action because they couldn't unlock that identity.)
// check for the secret in storage
async function useSecretAndInitializeAccountsDB( async function useSecretAndInitializeAccountsDB(
secretDB: SecretDexie, secretDB: SecretDexie,
accountsDB: SensitiveDexie, accountsDB: SensitiveDexie,
@@ -214,6 +213,22 @@ export async function updateAccountSettings(
} }
} }
export async function logToDb(message: string): Promise<void> {
await db.open();
const todayKey = new Date().toDateString();
// only keep one day's worth of logs
const previous = await db.logs.get(todayKey);
if (!previous) {
// when this is today's first log, clear out everything previous
// to avoid the log table getting too large
// (let's limit a different way someday)
await db.logs.clear();
}
const prevMessages = (previous && previous.message) || "";
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
await db.logs.update(todayKey, { message: fullMessage });
}
// similar method is in the sw_scripts/additional-scripts.js file // similar method is in the sw_scripts/additional-scripts.js file
export async function logConsoleAndDb( export async function logConsoleAndDb(
message: string, message: string,
@@ -224,16 +239,5 @@ export async function logConsoleAndDb(
} else { } else {
logger.log(`${new Date().toISOString()} ${message}`); logger.log(`${new Date().toISOString()} ${message}`);
} }
await logToDb(message);
await db.open();
const todayKey = new Date().toDateString();
// only keep one day's worth of logs
const previous = await db.logs.get(todayKey);
if (!previous) {
// when this is today's first log, clear out everything previous
await db.logs.clear();
}
const prevMessages = (previous && previous.message) || "";
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
await db.logs.update(todayKey, { message: fullMessage });
} }

View File

@@ -87,7 +87,6 @@ import {
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
faBuilding,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
library.add( library.add(
@@ -168,7 +167,6 @@ library.add(
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
faBuilding,
); );
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";

View File

@@ -2,39 +2,54 @@
import { register } from "register-service-worker"; import { register } from "register-service-worker";
// Only register service worker if explicitly enabled and in production // Add debug logging for environment variables
console.log('[ServiceWorker] Environment variables:', {
VITE_PWA_ENABLED: process.env.VITE_PWA_ENABLED,
NODE_ENV: process.env.NODE_ENV,
BASE_URL: process.env.BASE_URL,
CAN_REGISTER: process.env.VITE_PWA_ENABLED === "true" && process.env.NODE_ENV === "production"
});
// Modified condition to handle both string and boolean true
if ( if (
process.env.VITE_PWA_ENABLED === "true" && process.env.VITE_PWA_ENABLED === "true" &&
process.env.NODE_ENV === "production" process.env.NODE_ENV === "production"
) { ) {
register(`${process.env.BASE_URL}sw.js`, { console.log('[ServiceWorker] Attempting to register service worker...');
// Use '/' as fallback if BASE_URL is undefined
const baseUrl = process.env.BASE_URL || '/';
register(`${baseUrl}sw.js`, {
ready() { ready() {
console.log("Service worker is active."); console.log("[ServiceWorker] Service worker is active.");
}, },
registered() { registered(registration) {
console.log("Service worker has been registered."); console.log("[ServiceWorker] Service worker has been registered:", registration);
}, },
cached() { cached(registration) {
console.log("Content has been cached for offline use."); console.log("[ServiceWorker] Content has been cached for offline use:", registration);
}, },
updatefound() { updatefound(registration) {
console.log("New content is downloading."); console.log("[ServiceWorker] New content is downloading:", registration);
}, },
updated() { updated(registration) {
console.log("New content is available; please refresh."); console.log("[ServiceWorker] New content is available:", registration);
}, },
offline() { offline() {
console.log( console.log("[ServiceWorker] No internet connection found. App is running in offline mode.");
"No internet connection found. App is running in offline mode.",
);
}, },
error(error) { error(error) {
console.error("Error during service worker registration:", error); console.error("[ServiceWorker] Error during service worker registration:", error);
}, },
}); });
} else { } else {
console.log( console.log(
"Service worker registration skipped - not enabled or not in production", "[ServiceWorker] Registration skipped:",
{
enabled: process.env.VITE_PWA_ENABLED === "true",
production: process.env.NODE_ENV === "production",
value: process.env.VITE_PWA_ENABLED,
type: typeof process.env.VITE_PWA_ENABLED
}
); );
} }

View File

@@ -1,11 +1,18 @@
export interface GiveRecordWithContactInfo { import { GiveSummaryRecord, GiveVerifiableCredential } from "interfaces";
export interface GiveRecordWithContactInfo extends GiveSummaryRecord {
jwtId: string; jwtId: string;
fullClaim: unknown; // Replace with proper type fullClaim: GiveVerifiableCredential;
giver: { giver: {
known: boolean; known: boolean;
displayName: string; displayName: string;
profileImageUrl?: string; profileImageUrl?: string;
}; };
issuer: {
known: boolean;
displayName: string;
profileImageUrl?: string;
};
receiver: { receiver: {
known: boolean; known: boolean;
displayName: string; displayName: string;
@@ -13,8 +20,6 @@ export interface GiveRecordWithContactInfo {
}; };
providerPlanName?: string; providerPlanName?: string;
recipientProjectName?: string; recipientProjectName?: string;
description?: string; description: string;
subDescription?: string;
image?: string; image?: string;
timestamp: string;
} }

View File

@@ -1,18 +1,46 @@
import { logToDb } from "../db";
function safeStringify(obj: unknown) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
}
if (typeof value === "function") {
return `[Function: ${value.name || "anonymous"}]`;
}
return value;
});
}
export const logger = { export const logger = {
log: (message: string, ...args: unknown[]) => { log: (message: string, ...args: unknown[]) => {
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(message, ...args); console.log(message, ...args);
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
logToDb(message + argsString);
} }
}, },
warn: (message: string, ...args: unknown[]) => { warn: (message: string, ...args: unknown[]) => {
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn(message, ...args); console.warn(message, ...args);
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
logToDb(message + argsString);
} }
}, },
error: (message: string, ...args: unknown[]) => { error: (message: string, ...args: unknown[]) => {
// Errors will always be logged
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(message, ...args); // Errors should always be logged console.error(message, ...args);
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
logToDb(message + argsString);
}, },
}; };

View File

@@ -196,14 +196,14 @@
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white" class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
@click="openFeedFilters()" @click="openFeedFilters()"
> >
<fa icon="filter" class="fa-fw" /> <font-awesome icon="filter" class="fa-fw" />
</button> </button>
<button <button
v-else v-else
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white" class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
@click="openFeedFilters()" @click="openFeedFilters()"
> >
<fa icon="filter" class="fa-fw" /> <font-awesome icon="filter" class="fa-fw" />
</button> </button>
</h2> </h2>
</div> </div>
@@ -341,25 +341,9 @@ import {
} from "../libs/util"; } from "../libs/util";
import { GiveSummaryRecord } from "../interfaces"; import { GiveSummaryRecord } from "../interfaces";
import * as serverUtil from "../libs/endorserServer"; import * as serverUtil from "../libs/endorserServer";
// import { fa0 } from "@fortawesome/free-solid-svg-icons";
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
jwtId: string;
giver: {
displayName: string;
known: boolean;
profileImageUrl?: string;
};
image?: string;
providerPlanName?: string;
recipientProjectName?: string;
receiver: {
displayName: string;
known: boolean;
profileImageUrl?: string;
};
}
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { GiveRecordWithContactInfo } from "types";
/** /**
* HomeView - Main view component for the application's home page * HomeView - Main view component for the application's home page
* *
@@ -821,6 +805,12 @@ export default class HomeView extends Vue {
this.allMyDids, this.allMyDids,
), ),
image: claim.image, image: claim.image,
issuer: didInfoForContact(
record.issuerDid,
this.activeDid,
contactForDid(record.issuerDid, this.allContacts),
this.allMyDids,
),
providerPlanHandleId: provider?.identifier as string, providerPlanHandleId: provider?.identifier as string,
providerPlanName: providedByPlan?.name as string, providerPlanName: providedByPlan?.name as string,
recipientProjectName: fulfillsPlan?.name as string, recipientProjectName: fulfillsPlan?.name as string,

View File

@@ -16,11 +16,14 @@ export async function createBuildConfig(mode: string) {
const isElectron = mode === "electron"; const isElectron = mode === "electron";
const isCapacitor = mode === "capacitor"; const isCapacitor = mode === "capacitor";
const isPyWebView = mode === "pywebview"; const isPyWebView = mode === "pywebview";
const isWeb = mode === "web";
// Explicitly set platform // Explicitly set platform
process.env.VITE_PLATFORM = mode; process.env.VITE_PLATFORM = mode;
if (isElectron || isPyWebView || isCapacitor) { if (isWeb) {
process.env.VITE_PWA_ENABLED = 'true';
} else {
process.env.VITE_PWA_ENABLED = 'false'; process.env.VITE_PWA_ENABLED = 'false';
} }
@@ -42,7 +45,7 @@ export async function createBuildConfig(mode: string) {
define: { define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.VITE_PLATFORM': JSON.stringify(mode), 'process.env.VITE_PLATFORM': JSON.stringify(mode),
'process.env.VITE_PWA_ENABLED': JSON.stringify(!(isElectron || isPyWebView || isCapacitor)), 'process.env.VITE_PWA_ENABLED': isWeb ? "true" : "false",
__dirname: isElectron ? JSON.stringify(process.cwd()) : '""', __dirname: isElectron ? JSON.stringify(process.cwd()) : '""',
}, },
resolve: { resolve: {