import { createRouter, createWebHistory, createMemoryHistory, RouteLocationNormalized, RouteRecordRaw, } from "vue-router"; import { logger } from "../utils/logger"; import { retrieveAccountDids, generateSaveAndActivateIdentity, } from "../libs/util"; const routes: Array = [ { path: "/account", name: "account", component: () => import("../views/AccountViewView.vue"), }, { 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: "/contact-qr-scan-full", name: "contact-qr-scan-full", component: () => import("../views/ContactQRScanFullView.vue"), }, { path: "/contacts", name: "contacts", component: () => import("../views/ContactsView.vue"), }, { path: "/database-migration", name: "database-migration", component: () => import("../views/DatabaseMigration.vue"), }, { path: "/deep-link-error", name: "deep-link-error", component: () => import("../views/DeepLinkErrorView.vue"), meta: { title: "Invalid Deep Link", requiresAuth: false, }, }, { path: "/did/:did?", name: "did", component: () => import("../views/DIDView.vue"), }, { path: "/discover", name: "discover", component: () => import("../views/DiscoverView.vue"), }, { path: "/deep-link/:path*", name: "deep-link", component: () => import("../views/DeepLinkRedirectView.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"), }, { // optional because A) it could be a query param, and B) the page displays an input if things go wrong path: "/invite-one-accept/:jwt?", name: "invite-one-accept", 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"), }, { 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: "/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"), }, // Catch-all route for 404 errors - must be last { path: "/:pathMatch(.*)*", name: "not-found", component: () => import("../views/NotFoundView.vue"), meta: { title: "Page Not Found", 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 || "/"); const errorHandler = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any error: any, to: RouteLocationNormalized, from: RouteLocationNormalized, ) => { // Handle the error here logger.error("Caught in top level error handler:", error, to, from); alert("Something is very wrong. Try reloading or restarting the app."); // You can also perform additional actions, such as displaying an error message or redirecting the user to a specific page }; router.onError(errorHandler); // Assign the error handler to the router instance /** * Navigation guard to ensure user has an identity before accessing protected routes * @param to - Target route * @param _from - Source route (unused) * @param next - Navigation function */ router.beforeEach(async (to, _from, next) => { logger.debug(`[Router] 🧭 Navigation guard triggered:`, { from: _from?.path || "none", to: to.path, name: to.name, params: to.params, query: to.query, timestamp: new Date().toISOString(), }); try { // Log boot-time configuration on first navigation if (!_from) { logger.info( "[Router] 🚀 First navigation detected - logging boot-time configuration:", { platform: process.env.VITE_PLATFORM, defaultEndorserApiServer: process.env.VITE_DEFAULT_ENDORSER_API_SERVER, defaultPartnerApiServer: process.env.VITE_DEFAULT_PARTNER_API_SERVER, nodeEnv: process.env.NODE_ENV, targetRoute: to.path, timestamp: new Date().toISOString(), }, ); } // Skip identity check for routes that handle identity creation manually const skipIdentityRoutes = [ "/start", "/new-identifier", "/import-account", "/import-derive", "/database-migration", "/deep-link-error", ]; if (skipIdentityRoutes.includes(to.path)) { logger.debug(`[Router] ⏭️ Skipping identity check for route: ${to.path}`); return next(); } logger.debug(`[Router] 🔍 Checking user identity for route: ${to.path}`); // Check if user has any identities const allMyDids = await retrieveAccountDids(); logger.debug(`[Router] 📋 Found ${allMyDids.length} user identities`); if (allMyDids.length === 0) { logger.info("[Router] ⚠️ No identities found, creating default identity"); // Create identity automatically using seed-based method await generateSaveAndActivateIdentity(); logger.info("[Router] ✅ Default identity created successfully"); } else { logger.debug( `[Router] ✅ User has ${allMyDids.length} identities, proceeding`, ); } logger.debug(`[Router] ✅ Navigation guard passed for: ${to.path}`); next(); } catch (error) { logger.error("[Router] ❌ Identity creation failed in navigation guard:", { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, route: to.path, timestamp: new Date().toISOString(), }); // Redirect to start page if identity creation fails // This allows users to manually create an identity or troubleshoot logger.info( `[Router] 🔄 Redirecting to /start due to identity creation failure`, ); next("/start"); } }); // Add navigation success logging router.afterEach((to, from) => { logger.debug(`[Router] ✅ Navigation completed:`, { from: from?.path || "none", to: to.path, name: to.name, params: to.params, query: to.query, timestamp: new Date().toISOString(), }); }); // Add error logging router.onError((error) => { logger.error(`[Router] ❌ Navigation error:`, { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString(), }); }); export default router;