/** * Route guard for: * 1. ensuring app bridge connection * 2. loading identity information * 3. redirecting users to pages they can access */ import { getUserIdentity, getMemberProfile } from '../api/wallet.js' import { isPublicPath } from '@/config/security.js' import { isDebugMode } from './env.js' import { permissionManager, PAGES, USER_ROLES } from './permissionManager.js' import { getUserId, setUserInfo } from './userStore.js' import { connectApplication, isInApp, parseAccessOrigin, parseHeader, setHttpHeaders, } from './appBridge.js' import { appConnectionManager, isAppConnected } from './appConnectionManager.js' class AppConnectionChecker { async ensureConnection() { console.debug('Checking APP connection...') if (!isInApp()) { if (!isDebugMode()) { console.warn('Blocked protected route outside official app') return false } console.debug('Browser environment detected in debug mode') return true } if (isAppConnected()) { console.debug('APP already connected') return true } if (appConnectionManager.isConnecting()) { console.debug('Connection already in progress, waiting...') try { await appConnectionManager.getConnectionPromise() return isAppConnected() } catch (error) { console.error('Connection wait failed:', error) return false } } return this._performConnection() } async _performConnection() { try { const connectionPromise = new Promise((resolve, reject) => { connectApplication(async (access) => { try { const result = parseAccessOrigin(access) if (!result.success) { appConnectionManager.reset() reject(new Error(result.error || 'accessOrigin Error.')) return } const headerInfo = parseHeader(result.data) await setHttpHeaders(headerInfo) appConnectionManager.setConnected(headerInfo) await this._fetchUserInfoAfterConnection() resolve(true) } catch (error) { appConnectionManager.reset() reject(error) } }) }) appConnectionManager.setConnecting(connectionPromise) return await connectionPromise } catch (error) { console.error('APP connection failed:', error) appConnectionManager.reset() return false } } async _fetchUserInfoAfterConnection() { try { const response = await getMemberProfile() if (response && response.status && response.body) { setUserInfo(response.body, null) } } catch (error) { console.warn('Failed to fetch user info after connection:', error) } } reset() { appConnectionManager.reset() } } class IdentityChecker { constructor() { this.isChecking = false this.checkPromise = null } async checkUserIdentity(userId) { if (!userId) { permissionManager.setUserIdentity(null) return USER_ROLES.GUEST } if (this.isChecking && this.checkPromise) { return await this.checkPromise } this.isChecking = true this.checkPromise = this._performIdentityCheck(userId) try { return await this.checkPromise } finally { this.isChecking = false this.checkPromise = null } } async _performIdentityCheck(userId) { try { console.debug('Checking user identity for:', userId) const response = await getUserIdentity() if (response && response.status && response.body) { permissionManager.setUserIdentity(response.body) return permissionManager.getPrimaryRole() } permissionManager.setUserIdentity(null) return USER_ROLES.GUEST } catch (error) { console.error('Failed to fetch user identity:', error) permissionManager.setUserIdentity(null) return USER_ROLES.GUEST } } reset() { this.isChecking = false this.checkPromise = null } } class PageRedirector { async redirectToCorrectPage(router, targetPath, currentPath) { const hasPermission = permissionManager.hasPagePermission(targetPath) if (hasPermission) { return false } const defaultPage = permissionManager.getDefaultPage() if (defaultPage !== currentPath) { await router.replace(defaultPage) return true } return false } async handleUnauthorizedAccess(router) { await router.replace(permissionManager.getDefaultPage()) } } class RouteGuard { constructor() { this.appConnectionChecker = new AppConnectionChecker() this.identityChecker = new IdentityChecker() this.pageRedirector = new PageRedirector() this.isGuardActive = false } install(router) { router.beforeEach(async (to, from, next) => { await this.handleRouteChange(to, from, next) }) this.isGuardActive = true console.debug('Route guard installed') } async handleRouteChange(to, from, next) { console.group('Route Guard Check') console.debug('Route change:', from.path, '->', to.path) try { if (to.meta?.isPublic || this.isPublicPage(to.path)) { console.groupEnd() next() return } const isConnected = await this.appConnectionChecker.ensureConnection() if (!isConnected) { console.groupEnd() next({ path: PAGES.NOT_APP, query: { message: 'Please open this page inside the official app', }, }) return } if (!permissionManager.isLoaded()) { const userId = getUserId() if (!userId) { console.groupEnd() next(PAGES.APPLY) return } await this.identityChecker.checkUserIdentity(userId) } if (permissionManager.hasPagePermission(to.path)) { console.groupEnd() next() return } const defaultPage = permissionManager.getDefaultPage() console.groupEnd() next(defaultPage) } catch (error) { console.error('Route guard error:', error) console.groupEnd() const message = String(error?.message || '').toLowerCase() if (message.includes('connection') || message.includes('app required')) { next(PAGES.NOT_APP) return } next(PAGES.APPLY) } } isPublicPage(path) { return isPublicPath(path) } async checkAndRedirect(router, userId) { try { const primaryRole = await this.identityChecker.checkUserIdentity(userId) const currentPath = router.currentRoute.value.path if (!permissionManager.hasPagePermission(currentPath)) { await this.pageRedirector.redirectToCorrectPage(router, currentPath, currentPath) } return primaryRole } catch (error) { console.error('Manual check and redirect failed:', error) throw error } } reset() { this.appConnectionChecker.reset() this.identityChecker.reset() permissionManager.reset() console.debug('Route guard reset') } } export const routeGuard = new RouteGuard() export const installRouteGuard = (router) => routeGuard.install(router) export const checkAndRedirect = (router, userId) => routeGuard.checkAndRedirect(router, userId) export const resetRouteGuard = () => routeGuard.reset()