@ -44,6 +44,8 @@
* /
import { Router } from "vue-router" ;
import { z } from "zod" ;
import {
deepLinkSchemas ,
baseUrlSchema ,
@ -53,6 +55,31 @@ import {
import { logConsoleAndDb } from "../db/databaseUtil" ;
import type { DeepLinkError } from "../interfaces/deepLinks" ;
// Helper function to extract the first key from a Zod object schema
function getFirstKeyFromZodObject ( schema : z.ZodObject < any > ) : string | undefined {
const shape = schema . shape ;
const keys = Object . keys ( shape ) ;
return keys . length > 0 ? keys [ 0 ] : undefined ;
}
/ * *
* Maps deep link routes to their corresponding Vue router names and optional parameter keys .
*
* It 's an object where keys are the deep link routes and values are objects with ' name ' and ' paramKey ' .
*
* The paramKey is used to extract the parameter from the route path ,
* because "router.replace" expects the right parameter name for the route .
* /
export const ROUTE_MAP : Record < string , { name : string ; paramKey ? : string } > =
Object . entries ( deepLinkSchemas ) . reduce ( ( acc , [ routeName , schema ] ) = > {
const paramKey = getFirstKeyFromZodObject ( schema as z . ZodObject < any > ) ;
acc [ routeName ] = {
name : routeName ,
paramKey
} ;
return acc ;
} , { } as Record < string , { name : string ; paramKey ? : string } > ) ;
/ * *
* Handles processing and routing of deep links in the application .
* Provides validation , error handling , and routing for deep link URLs .
@ -69,30 +96,7 @@ export class DeepLinkHandler {
}
/ * *
* Maps deep link routes to their corresponding Vue router names and optional parameter keys .
*
* The paramKey is used to extract the parameter from the route path ,
* because "router.replace" expects the right parameter name for the route .
* The default is "id" .
* /
private readonly ROUTE_MAP : Record <
string ,
{ name : string ; paramKey? : string }
> = {
// note that similar lists are in src/interfaces/deepLinks.ts
claim : { name : "claim" } ,
"claim-add-raw" : { name : "claim-add-raw" } ,
"claim-cert" : { name : "claim-cert" } ,
"confirm-gift" : { name : "confirm-gift" } ,
"contact-import" : { name : "contact-import" , paramKey : "jwt" } ,
did : { name : "did" , paramKey : "did" } ,
"invite-one-accept" : { name : "invite-one-accept" , paramKey : "jwt" } ,
"onboard-meeting-members" : { name : "onboard-meeting-members" } ,
project : { name : "project" } ,
"user-profile" : { name : "user-profile" } ,
} ;
/ * *
* Parses deep link URL into path , params and query components .
* Validates URL structure using Zod schemas .
*
@ -115,18 +119,9 @@ export class DeepLinkHandler {
const [ path , queryString ] = parts [ 1 ] . split ( "?" ) ;
const [ routePath , . . . pathParams ] = path . split ( "/" ) ;
// logger.info(
// "[DeepLink] Debug:",
// "Route Path:",
// routePath,
// "Path Params:",
// pathParams,
// "Query String:",
// queryString,
// );
// Validate route exists before proceeding
if ( ! this . ROUTE_MAP [ routePath ] ) {
if ( ! ROUTE_MAP [ routePath ] ) {
throw {
code : "INVALID_ROUTE" ,
message : ` Invalid route path: ${ routePath } ` ,
@ -144,9 +139,14 @@ export class DeepLinkHandler {
const params : Record < string , string > = { } ;
if ( pathParams ) {
// Now we know routePath exists in ROUTE_MAP
const routeConfig = this . ROUTE_MAP [ routePath ] ;
const routeConfig = ROUTE_MAP [ routePath ] ;
params [ routeConfig . paramKey ? ? "id" ] = pathParams . join ( "/" ) ;
}
// logConsoleAndDb(
// `[DeepLink] Debug: Route Path: ${routePath} Path Params: ${JSON.stringify(params)} Query String: ${JSON.stringify(query)}`,
// false,
// );
return { path : routePath , params , query } ;
}
@ -170,7 +170,7 @@ export class DeepLinkHandler {
try {
// Validate route exists
const validRoute = routeSchema . parse ( path ) as DeepLinkRoute ;
routeName = this . ROUTE_MAP [ validRoute ] . name ;
routeName = ROUTE_MAP [ validRoute ] . name ;
} catch ( error ) {
// Log the invalid route attempt
logConsoleAndDb ( ` [DeepLink] Invalid route path: ${ path } ` , true ) ;
@ -178,52 +178,65 @@ export class DeepLinkHandler {
// Redirect to error page with information about the invalid link
await this . router . replace ( {
name : "deep-link-error" ,
params ,
query : {
originalPath : path ,
errorCode : "INVALID_ROUTE" ,
message : ` The link you followed ( ${ path } ) is not supported ` ,
errorMessage : ` The link you followed ( ${ path } ) is not supported ` ,
. . . query ,
} ,
} ) ;
throw {
code : "INVALID_ROUTE" ,
message : ` Unsupported route: ${ path } ` ,
} ;
// This previously threw an error but we're redirecting so there's no need.
return ;
}
// Continue with parameter validation as before...
const schema = deepLinkSchemas [ path as keyof typeof deepLinkSchemas ] ;
let validatedParams , validatedQuery ;
try {
const validatedParams = await schema . parseAsync ( {
. . . params ,
. . . query ,
validatedParams = await schema . parseAsync ( params ) ;
validatedQuery = await schema . parseAsync ( query ) ;
} catch ( error ) {
// For parameter validation errors, provide specific error feedback
logConsoleAndDb ( ` [DeepLink] Invalid parameters for route name ${ routeName } for path: ${ path } : ${ JSON . stringify ( error ) } ... with params: ${ JSON . stringify ( params ) } ... and query: ${ JSON . stringify ( query ) } ` , true ) ;
await this . router . replace ( {
name : "deep-link-error" ,
params ,
query : {
originalPath : path ,
errorCode : "INVALID_PARAMETERS" ,
errorMessage : ` The link parameters are invalid: ${ ( error as Error ) . message } ` ,
. . . query ,
} ,
} ) ;
// This previously threw an error but we're redirecting so there's no need.
return ;
}
try {
await this . router . replace ( {
name : routeName ,
params : validatedParams ,
query ,
query : validatedQuery ,
} ) ;
} catch ( error ) {
logConsoleAndDb ( ` [DeepLink] Error routing to route name ${ routeName } for path: ${ path } : ${ JSON . stringify ( error ) } ... with validated params: ${ JSON . stringify ( validatedParams ) } ... and validated query: ${ JSON . stringify ( validatedQuery ) } ` , true ) ;
// For parameter validation errors, provide specific error feedback
await this . router . replace ( {
name : "deep-link-error" ,
params : validatedParams ,
query : {
originalPath : path ,
errorCode : "INVALID_PARAMETERS" ,
message : ` The link parameters are invalid: ${ ( error as Error ) . message } ` ,
errorCode : "ROUTING_ERROR" ,
errorMessage : ` Error routing to ${ routeName } : ${ ( JSON . stringify ( error ) ) } ` ,
. . . validatedQuery ,
} ,
} ) ;
throw {
code : "INVALID_PARAMETERS" ,
message : ( error as Error ) . message ,
details : error ,
params : params ,
query : query ,
} ;
}
}
/ * *
@ -235,7 +248,6 @@ export class DeepLinkHandler {
* /
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 (
@ -245,7 +257,7 @@ export class DeepLinkHandler {
} catch ( error ) {
const deepLinkError = error as DeepLinkError ;
logConsoleAndDb (
` [DeepLink] Error ( ${ deepLinkError . code } ): ${ deepLinkError . message } ` ,
` [DeepLink] Error ( ${ deepLinkError . code } ): ${ deepLinkError . details } ` ,
true ,
) ;