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.
440 lines
12 KiB
440 lines
12 KiB
import {
|
|
createRouter,
|
|
createWebHistory,
|
|
createMemoryHistory,
|
|
RouteLocationNormalized,
|
|
RouteRecordRaw,
|
|
} from "vue-router";
|
|
import { logger } from "../utils/logger";
|
|
import {
|
|
retrieveAccountDids,
|
|
generateSaveAndActivateIdentity,
|
|
} from "../libs/util";
|
|
|
|
const routes: Array<RouteRecordRaw> = [
|
|
{
|
|
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;
|
|
|