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;