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.
 
 
 
 
 
 

230 lines
6.9 KiB

/**
* @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://<route>[/<param>][?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<string, string> = {}
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<void> {
try {
logConsoleAndDb('[DeepLink] Processing URL: ' + url, false)
const { path, params, query } = this.parseDeepLink(url)
// Ensure params is always a Record<string,string> 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<string, string>,
query: Record<string, string>
): Promise<void> {
const routeMap: Record<string, string> = {
'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
}
}
}
}