You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
662 lines
19 KiB
662 lines
19 KiB
import {
|
|
createRouter,
|
|
createWebHistory,
|
|
createMemoryHistory,
|
|
NavigationGuardNext,
|
|
RouteLocationNormalized,
|
|
RouteRecordRaw,
|
|
} from "vue-router";
|
|
import {
|
|
accountsDBPromise,
|
|
retrieveSettingsForActiveAccount,
|
|
} from "../db/index";
|
|
import { logger } from "../utils/logger";
|
|
import { Component as VueComponent } from "vue-facing-decorator";
|
|
import { defineComponent } from "vue";
|
|
|
|
/**
|
|
*
|
|
* @param to :RouteLocationNormalized
|
|
* @param from :RouteLocationNormalized
|
|
* @param next :NavigationGuardNext
|
|
*/
|
|
const enterOrStart = async (
|
|
to: RouteLocationNormalized,
|
|
from: RouteLocationNormalized,
|
|
next: NavigationGuardNext,
|
|
) => {
|
|
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
|
const accountsDB = await accountsDBPromise;
|
|
const num_accounts = await accountsDB.accounts.count();
|
|
|
|
if (num_accounts > 0) {
|
|
next();
|
|
} else {
|
|
next({ name: "start" });
|
|
}
|
|
};
|
|
|
|
const routes: Array<RouteRecordRaw> = [
|
|
{
|
|
path: "/account",
|
|
name: "account",
|
|
component: () => {
|
|
logger.log("Starting lazy load of AccountViewView");
|
|
return new Promise((resolve) => {
|
|
import("../views/AccountViewView.vue")
|
|
.then((module) => {
|
|
if (!module?.default) {
|
|
logger.error(
|
|
"AccountViewView module loaded but default export is missing",
|
|
{
|
|
module: {
|
|
hasDefault: !!module?.default,
|
|
keys: Object.keys(module || {}),
|
|
},
|
|
},
|
|
);
|
|
resolve(createErrorComponent());
|
|
return;
|
|
}
|
|
|
|
// Check if the component has the required dependencies
|
|
const component = module.default;
|
|
logger.log("AccountViewView loaded, checking dependencies...", {
|
|
componentName: component.name,
|
|
hasVueComponent: component instanceof VueComponent,
|
|
hasClass: typeof component === "function",
|
|
type: typeof component,
|
|
});
|
|
|
|
resolve(component);
|
|
})
|
|
.catch((err) => {
|
|
logger.error("Failed to load AccountViewView:", {
|
|
error:
|
|
err instanceof Error
|
|
? {
|
|
name: err.name,
|
|
message: err.message,
|
|
stack: err.stack,
|
|
}
|
|
: err,
|
|
type: typeof err,
|
|
});
|
|
|
|
resolve(createErrorComponent());
|
|
});
|
|
});
|
|
},
|
|
beforeEnter: async (to, from, next) => {
|
|
try {
|
|
logger.log("Account route beforeEnter guard starting");
|
|
|
|
// Check if required dependencies are available
|
|
const settings = await retrieveSettingsForActiveAccount();
|
|
logger.log("Account route: settings loaded", {
|
|
hasActiveDid: !!settings.activeDid,
|
|
isRegistered: !!settings.isRegistered,
|
|
});
|
|
|
|
next();
|
|
} catch (error) {
|
|
logger.error("Error in account route beforeEnter:", {
|
|
error:
|
|
error instanceof Error
|
|
? {
|
|
name: error.name,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
}
|
|
: error,
|
|
});
|
|
next({ name: "home" });
|
|
}
|
|
},
|
|
},
|
|
{
|
|
path: "/claim/:id?",
|
|
name: "claim",
|
|
component: () => import("../views/ClaimView.vue"),
|
|
},
|
|
{
|
|
path: "/claim-add-raw/:id?",
|
|
name: "claim-add-raw",
|
|
component: () => import("../views/ClaimAddRawView.vue"),
|
|
},
|
|
{
|
|
path: "/claim-cert/:id",
|
|
name: "claim-cert",
|
|
component: () => import("../views/ClaimCertificateView.vue"),
|
|
},
|
|
{
|
|
path: "/confirm-contact",
|
|
name: "confirm-contact",
|
|
component: () => import("../views/ConfirmContactView.vue"),
|
|
},
|
|
{
|
|
path: "/confirm-gift/:id?",
|
|
name: "confirm-gift",
|
|
component: () => import("../views/ConfirmGiftView.vue"),
|
|
},
|
|
{
|
|
path: "/contact-amounts",
|
|
name: "contact-amounts",
|
|
component: () => import("../views/ContactAmountsView.vue"),
|
|
},
|
|
{
|
|
path: "/contact-edit/:did",
|
|
name: "contact-edit",
|
|
component: () => import("../views/ContactEditView.vue"),
|
|
},
|
|
{
|
|
path: "/contact-gift",
|
|
name: "contact-gift",
|
|
component: () => import("../views/ContactGiftingView.vue"),
|
|
},
|
|
{
|
|
path: "/contact-import/:jwt?",
|
|
name: "contact-import",
|
|
component: () => import("../views/ContactImportView.vue"),
|
|
},
|
|
{
|
|
path: "/contact-qr",
|
|
name: "contact-qr",
|
|
component: () => import("../views/ContactQRScanShowView.vue"),
|
|
},
|
|
{
|
|
path: "/contacts",
|
|
name: "contacts",
|
|
component: () => import("../views/ContactsView.vue"),
|
|
},
|
|
{
|
|
path: "/did/:did?",
|
|
name: "did",
|
|
component: () => import("../views/DIDView.vue"),
|
|
},
|
|
{
|
|
path: "/discover",
|
|
name: "discover",
|
|
component: () => import("../views/DiscoverView.vue"),
|
|
},
|
|
{
|
|
path: "/gifted-details",
|
|
name: "gifted-details",
|
|
component: () => import("../views/GiftedDetailsView.vue"),
|
|
},
|
|
{
|
|
path: "/help",
|
|
name: "help",
|
|
component: () => import("../views/HelpView.vue"),
|
|
},
|
|
{
|
|
path: "/help-notifications",
|
|
name: "help-notifications",
|
|
component: () => import("../views/HelpNotificationsView.vue"),
|
|
},
|
|
{
|
|
path: "/help-notification-types",
|
|
name: "help-notification-types",
|
|
component: () => import("../views/HelpNotificationTypesView.vue"),
|
|
},
|
|
{
|
|
path: "/help-onboarding",
|
|
name: "help-onboarding",
|
|
component: () => import("../views/HelpOnboardingView.vue"),
|
|
},
|
|
{
|
|
path: "/",
|
|
name: "home",
|
|
component: () => import("../views/HomeView.vue"),
|
|
},
|
|
{
|
|
path: "/identity-switcher",
|
|
name: "identity-switcher",
|
|
component: () => import("../views/IdentitySwitcherView.vue"),
|
|
},
|
|
{
|
|
path: "/import-account",
|
|
name: "import-account",
|
|
component: () => import("../views/ImportAccountView.vue"),
|
|
},
|
|
{
|
|
path: "/import-derive",
|
|
name: "import-derive",
|
|
component: () => import("../views/ImportDerivedAccountView.vue"),
|
|
},
|
|
{
|
|
path: "/invite-one",
|
|
name: "invite-one",
|
|
component: () => import("../views/InviteOneView.vue"),
|
|
},
|
|
{
|
|
path: "/invite-one-accept/:jwt?",
|
|
name: "InviteOneAcceptView",
|
|
component: () => import("../views/InviteOneAcceptView.vue"),
|
|
},
|
|
{
|
|
path: "/logs",
|
|
name: "logs",
|
|
component: () => import("../views/LogView.vue"),
|
|
},
|
|
{
|
|
path: "/new-activity",
|
|
name: "new-activity",
|
|
component: () => import("../views/NewActivityView.vue"),
|
|
},
|
|
{
|
|
path: "/new-edit-account",
|
|
name: "new-edit-account",
|
|
component: () => import("../views/NewEditAccountView.vue"),
|
|
},
|
|
{
|
|
path: "/new-edit-project",
|
|
name: "new-edit-project",
|
|
component: () => import("../views/NewEditProjectView.vue"),
|
|
},
|
|
{
|
|
path: "/new-identifier",
|
|
name: "new-identifier",
|
|
component: () => import("../views/NewIdentifierView.vue"),
|
|
},
|
|
{
|
|
path: "/offer-details/:id?",
|
|
name: "offer-details",
|
|
component: () => import("../views/OfferDetailsView.vue"),
|
|
},
|
|
{
|
|
path: "/onboard-meeting-list",
|
|
name: "onboard-meeting-list",
|
|
component: () => import("../views/OnboardMeetingListView.vue"),
|
|
},
|
|
{
|
|
path: "/onboard-meeting-members/:groupId",
|
|
name: "onboard-meeting-members",
|
|
component: () => import("../views/OnboardMeetingMembersView.vue"),
|
|
},
|
|
{
|
|
path: "/onboard-meeting-setup",
|
|
name: "onboard-meeting-setup",
|
|
component: () => import("../views/OnboardMeetingSetupView.vue"),
|
|
},
|
|
{
|
|
path: "/project/:id?",
|
|
name: "project",
|
|
component: () => import("../views/ProjectViewView.vue"),
|
|
},
|
|
{
|
|
path: "/projects",
|
|
name: "projects",
|
|
component: () => import("../views/ProjectsView.vue"),
|
|
beforeEnter: enterOrStart,
|
|
},
|
|
{
|
|
path: "/quick-action-bvc",
|
|
name: "quick-action-bvc",
|
|
component: () => import("../views/QuickActionBvcView.vue"),
|
|
},
|
|
{
|
|
path: "/quick-action-bvc-begin",
|
|
name: "quick-action-bvc-begin",
|
|
component: () => import("../views/QuickActionBvcBeginView.vue"),
|
|
},
|
|
{
|
|
path: "/quick-action-bvc-end",
|
|
name: "quick-action-bvc-end",
|
|
component: () => import("../views/QuickActionBvcEndView.vue"),
|
|
},
|
|
{
|
|
path: "/recent-offers-to-user",
|
|
name: "recent-offers-to-user",
|
|
component: () => import("../views/RecentOffersToUserView.vue"),
|
|
},
|
|
{
|
|
path: "/recent-offers-to-user-projects",
|
|
name: "recent-offers-to-user-projects",
|
|
component: () => import("../views/RecentOffersToUserProjectsView.vue"),
|
|
},
|
|
{
|
|
path: "/scan-contact",
|
|
name: "scan-contact",
|
|
component: () => import("../views/ContactScanView.vue"),
|
|
},
|
|
{
|
|
path: "/search-area",
|
|
name: "search-area",
|
|
component: () => import("../views/SearchAreaView.vue"),
|
|
},
|
|
{
|
|
path: "/seed-backup",
|
|
name: "seed-backup",
|
|
component: () => import("../views/SeedBackupView.vue"),
|
|
},
|
|
{
|
|
path: "/share-my-contact-info",
|
|
name: "share-my-contact-info",
|
|
component: () => import("../views/ShareMyContactInfoView.vue"),
|
|
},
|
|
{
|
|
path: "/shared-photo",
|
|
name: "shared-photo",
|
|
component: () => import("../views/SharedPhotoView.vue"),
|
|
},
|
|
|
|
// /share-target is also an endpoint in the service worker
|
|
|
|
{
|
|
path: "/start",
|
|
name: "start",
|
|
component: () => import("../views/StartView.vue"),
|
|
},
|
|
{
|
|
path: "/statistics",
|
|
name: "statistics",
|
|
component: () => import("../views/StatisticsView.vue"),
|
|
},
|
|
{
|
|
path: "/test",
|
|
name: "test",
|
|
component: () => import("../views/TestView.vue"),
|
|
},
|
|
{
|
|
path: "/user-profile/:id?",
|
|
name: "user-profile",
|
|
component: () => import("../views/UserProfileView.vue"),
|
|
},
|
|
{
|
|
path: "/deep-link-error",
|
|
name: "deep-link-error",
|
|
component: () => import("../views/DeepLinkErrorView.vue"),
|
|
meta: {
|
|
title: "Invalid Deep Link",
|
|
requiresAuth: false,
|
|
},
|
|
},
|
|
];
|
|
|
|
const isElectron = window.location.protocol === "file:";
|
|
const initialPath = isElectron
|
|
? window.location.pathname.split("/dist-electron/www/")[1] || "/"
|
|
: window.location.pathname;
|
|
|
|
const history = isElectron
|
|
? createMemoryHistory() // Memory history for Electron
|
|
: createWebHistory("/"); // Add base path for web apps
|
|
|
|
/** @type {*} */
|
|
const router = createRouter({
|
|
history,
|
|
routes,
|
|
});
|
|
|
|
// Replace initial URL to start at `/` if necessary
|
|
router.replace(initialPath || "/");
|
|
|
|
// Add global error handler
|
|
router.onError((error, to, from) => {
|
|
logger.error("Router error:", {
|
|
error:
|
|
error instanceof Error
|
|
? {
|
|
name: error.name,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
}
|
|
: error,
|
|
to: {
|
|
name: to.name,
|
|
path: to.path,
|
|
},
|
|
from: {
|
|
name: from.name,
|
|
path: from.path,
|
|
},
|
|
});
|
|
|
|
// If it's a reference error during account view import, try to handle it gracefully
|
|
if (error instanceof ReferenceError && to.name === "account") {
|
|
logger.error("Account view import error:", {
|
|
error:
|
|
error instanceof Error
|
|
? {
|
|
name: error.name,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
}
|
|
: error,
|
|
});
|
|
// Instead of redirecting, let the component's error handling take over
|
|
return;
|
|
}
|
|
});
|
|
|
|
// Add navigation guard for debugging
|
|
router.beforeEach((to, from, next) => {
|
|
logger.log("Navigation debug:", {
|
|
to: {
|
|
fullPath: to.fullPath,
|
|
path: to.path,
|
|
name: to.name,
|
|
params: to.params,
|
|
query: to.query,
|
|
},
|
|
from: {
|
|
fullPath: from.fullPath,
|
|
path: from.path,
|
|
name: from.name,
|
|
params: from.params,
|
|
query: from.query,
|
|
},
|
|
});
|
|
|
|
// For account route, try to preload the component
|
|
if (to.name === "account") {
|
|
logger.log("Preloading account component...");
|
|
|
|
// Wrap in try-catch and use Promise
|
|
new Promise((resolve) => {
|
|
logger.log("Starting dynamic import of AccountViewView");
|
|
|
|
// Add immediate try-catch to get more context
|
|
try {
|
|
const importPromise = import("../views/AccountViewView.vue");
|
|
logger.log("Import initiated successfully");
|
|
|
|
importPromise
|
|
.then((module) => {
|
|
try {
|
|
logger.log("Import completed, analyzing module:", {
|
|
moduleExists: !!module,
|
|
moduleType: typeof module,
|
|
moduleKeys: Object.keys(module || {}),
|
|
hasDefault: !!module?.default,
|
|
defaultType: module?.default
|
|
? typeof module.default
|
|
: "undefined",
|
|
defaultConstructor: module?.default?.constructor?.name,
|
|
moduleContent: {
|
|
...Object.fromEntries(
|
|
Object.entries(module).map(([key, value]) => [
|
|
key,
|
|
typeof value === "function"
|
|
? "function"
|
|
: typeof value === "object"
|
|
? Object.keys(value || {})
|
|
: typeof value,
|
|
]),
|
|
),
|
|
},
|
|
});
|
|
|
|
if (!module?.default) {
|
|
logger.error(
|
|
"AccountViewView preload: module loaded but default export is missing",
|
|
{
|
|
module: {
|
|
hasDefault: !!module?.default,
|
|
keys: Object.keys(module || {}),
|
|
moduleType: typeof module,
|
|
exports: Object.keys(module || {}).map((key) => ({
|
|
key,
|
|
type: typeof (module as any)[key],
|
|
value:
|
|
typeof (module as any)[key] === "function"
|
|
? "function"
|
|
: typeof (module as any)[key] === "object"
|
|
? Object.keys((module as any)[key] || {})
|
|
: (module as any)[key],
|
|
})),
|
|
},
|
|
},
|
|
);
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
const component = module.default;
|
|
|
|
// Try to safely inspect the component
|
|
const componentDetails = {
|
|
componentName: component.name,
|
|
hasVueComponent: component instanceof VueComponent,
|
|
hasClass: typeof component === "function",
|
|
type: typeof component,
|
|
properties: Object.keys(component),
|
|
decorators: Object.getOwnPropertyDescriptor(
|
|
component,
|
|
"__decorators",
|
|
),
|
|
vueOptions:
|
|
(component as any).__vccOpts ||
|
|
(component as any).options ||
|
|
null,
|
|
setup: typeof (component as any).setup === "function",
|
|
render: typeof (component as any).render === "function",
|
|
components: (component as any).components
|
|
? Object.keys((component as any).components)
|
|
: null,
|
|
imports: Object.keys(module).filter((key) => key !== "default"),
|
|
};
|
|
|
|
logger.log("Successfully analyzed component:", componentDetails);
|
|
resolve(component);
|
|
} catch (analysisError) {
|
|
logger.error("Error during component analysis:", {
|
|
error:
|
|
analysisError instanceof Error
|
|
? {
|
|
name: analysisError.name,
|
|
message: analysisError.message,
|
|
stack: analysisError.stack,
|
|
keys: Object.keys(analysisError),
|
|
properties: Object.getOwnPropertyNames(analysisError),
|
|
}
|
|
: analysisError,
|
|
type: typeof analysisError,
|
|
phase: "analysis",
|
|
});
|
|
resolve(null);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
logger.error("Failed to preload account component:", {
|
|
error:
|
|
err instanceof Error
|
|
? {
|
|
name: err.name,
|
|
message: err.message,
|
|
stack: err.stack,
|
|
keys: Object.keys(err),
|
|
properties: Object.getOwnPropertyNames(err),
|
|
}
|
|
: err,
|
|
type: typeof err,
|
|
context: {
|
|
routeName: to.name,
|
|
routePath: to.path,
|
|
fromRoute: from.name,
|
|
},
|
|
phase: "module-load",
|
|
});
|
|
resolve(null);
|
|
});
|
|
} catch (immediateError) {
|
|
logger.error("Immediate error during import initiation:", {
|
|
error:
|
|
immediateError instanceof Error
|
|
? {
|
|
name: immediateError.name,
|
|
message: immediateError.message,
|
|
stack: immediateError.stack,
|
|
keys: Object.keys(immediateError),
|
|
properties: Object.getOwnPropertyNames(immediateError),
|
|
}
|
|
: immediateError,
|
|
type: typeof immediateError,
|
|
context: {
|
|
routeName: to.name,
|
|
routePath: to.path,
|
|
fromRoute: from.name,
|
|
importPath: "../views/AccountViewView.vue",
|
|
},
|
|
phase: "import",
|
|
});
|
|
resolve(null);
|
|
}
|
|
}).catch((err) => {
|
|
logger.error("Critical error in account component preload:", {
|
|
error:
|
|
err instanceof Error
|
|
? {
|
|
name: err.name,
|
|
message: err.message,
|
|
stack: err.stack,
|
|
}
|
|
: err,
|
|
context: {
|
|
routeName: to.name,
|
|
routePath: to.path,
|
|
fromRoute: from.name,
|
|
},
|
|
phase: "wrapper",
|
|
});
|
|
});
|
|
}
|
|
|
|
// Always call next() to continue navigation
|
|
next();
|
|
});
|
|
|
|
function createErrorComponent() {
|
|
return defineComponent({
|
|
name: "AccountViewError",
|
|
components: {
|
|
// Add any required components here
|
|
},
|
|
setup() {
|
|
const goHome = () => {
|
|
router.push({ name: "home" });
|
|
};
|
|
|
|
return {
|
|
goHome,
|
|
};
|
|
},
|
|
template: `
|
|
<section class="p-6 pb-24 max-w-3xl mx-auto">
|
|
<h1 class="text-4xl text-center font-light mb-8">Error Loading Account View</h1>
|
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
|
<strong class="font-bold">Failed to load account view.</strong>
|
|
<span class="block sm:inline"> Please try refreshing the page.</span>
|
|
</div>
|
|
<div class="mt-4 text-center">
|
|
<button @click="goHome" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
|
Return to Home
|
|
</button>
|
|
</div>
|
|
</section>
|
|
`,
|
|
});
|
|
}
|
|
|
|
export default router;
|
|
|