/** * @file Deep Link Handler Service * @author Matthew Raymer * * This service handles the processing and routing of deep links in the TimeSafari app. * It provides a type-safe interface between the raw deep links and the application router. * * Architecture: * 1. DeepLinkHandler class encapsulates all deep link processing logic * 2. Uses Zod schemas from types/deepLinks for parameter validation * 3. Provides consistent error handling and logging * 4. Maps validated parameters to Vue router calls * * Error Handling Strategy: * - All errors are wrapped in DeepLinkError interface * - Errors include error codes for systematic handling * - Detailed error information is logged for debugging * - Errors are propagated to the global error handler * * Validation Strategy: * - URL structure validation * - Route-specific parameter validation using Zod schemas * - Query parameter validation and sanitization * - Type-safe parameter passing to router * * Deep Link Format: * timesafari://[/][?queryParam1=value1&queryParam2=value2] * * Supported Routes: * - user-profile: View user profile * - project-details: View project details * - onboard-meeting-setup: Setup onboarding meeting * - invite-one-accept: Accept invitation * - contact-import: Import contacts * - confirm-gift: Confirm gift * - claim: View claim * - claim-cert: View claim certificate * - claim-add-raw: Add raw claim * - contact-edit: Edit contact * - contacts: View contacts * - did: View DID * * @example * const handler = new DeepLinkHandler(router); * await handler.handleDeepLink("timesafari://claim/123?view=details"); */ import { Router } from 'vue-router' import { deepLinkSchemas, baseUrlSchema, routeSchema, DeepLinkRoute } from '../types/deepLinks' import { logConsoleAndDb } from '../db' import type { DeepLinkError } from '../interfaces/deepLinks' /** * Handles processing and routing of deep links in the application. * Provides validation, error handling, and routing for deep link URLs. */ export class DeepLinkHandler { private router: Router /** * Creates a new DeepLinkHandler instance. * @param router - Vue Router instance for navigation */ constructor(router: Router) { this.router = router } /** * Parses deep link URL into path, params and query components. * Validates URL structure using Zod schemas. * * @param url - The deep link URL to parse (format: scheme://path[?query]) * @throws {DeepLinkError} If URL format is invalid * @returns Parsed URL components (path, params, query) */ private parseDeepLink(url: string) { const parts = url.split('://') if (parts.length !== 2) { throw { code: 'INVALID_URL', message: 'Invalid URL format' } } // Validate base URL structure baseUrlSchema.parse({ scheme: parts[0], path: parts[1], queryParams: {} // Will be populated below }) const [path, queryString] = parts[1].split('?') const [routePath, param] = path.split('/') const query: Record = {} if (queryString) { new URLSearchParams(queryString).forEach((value, key) => { query[key] = value }) } return { path: routePath, params: param ? { id: param } : {}, query } } /** * Processes incoming deep links and routes them appropriately. * Handles validation, error handling, and routing to the correct view. * * @param url - The deep link URL to process * @throws {DeepLinkError} If URL processing fails */ async handleDeepLink(url: string): Promise { try { logConsoleAndDb('[DeepLink] Processing URL: ' + url, false) const { path, params, query } = this.parseDeepLink(url) // Ensure params is always a Record by converting undefined to empty string const sanitizedParams = Object.fromEntries( Object.entries(params).map(([key, value]) => [key, value ?? '']) ) await this.validateAndRoute(path, sanitizedParams, query) } catch (error) { const deepLinkError = error as DeepLinkError logConsoleAndDb( `[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`, true ) throw { code: deepLinkError.code || 'UNKNOWN_ERROR', message: deepLinkError.message, details: deepLinkError.details } } } /** * Routes the deep link to appropriate view with validated parameters. * Validates route and parameters using Zod schemas before routing. * * @param path - The route path from the deep link * @param params - URL parameters * @param query - Query string parameters * @throws {DeepLinkError} If validation fails or route is invalid */ private async validateAndRoute( path: string, params: Record, query: Record ): Promise { const routeMap: Record = { 'user-profile': 'user-profile', 'project-details': 'project-details', 'onboard-meeting-setup': 'onboard-meeting-setup', 'invite-one-accept': 'invite-one-accept', 'contact-import': 'contact-import', 'confirm-gift': 'confirm-gift', claim: 'claim', 'claim-cert': 'claim-cert', 'claim-add-raw': 'claim-add-raw', 'contact-edit': 'contact-edit', contacts: 'contacts', did: 'did' } // First try to validate the route path let routeName: string try { // Validate route exists const validRoute = routeSchema.parse(path) as DeepLinkRoute routeName = routeMap[validRoute] } catch (error) { // Log the invalid route attempt logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true) // Redirect to error page with information about the invalid link await this.router.replace({ name: 'deep-link-error', query: { originalPath: path, errorCode: 'INVALID_ROUTE', message: `The link you followed (${path}) is not supported` } }) throw { code: 'INVALID_ROUTE', message: `Unsupported route: ${path}` } } // Continue with parameter validation as before... const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas] try { const validatedParams = await schema.parseAsync({ ...params, ...query }) await this.router.replace({ name: routeName, params: validatedParams, query }) } catch (error) { // For parameter validation errors, provide specific error feedback await this.router.replace({ name: 'deep-link-error', query: { originalPath: path, errorCode: 'INVALID_PARAMETERS', message: `The link parameters are invalid: ${(error as Error).message}` } }) throw { code: 'INVALID_PARAMETERS', message: (error as Error).message, details: error } } } }