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.
148 lines
4.3 KiB
148 lines
4.3 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
|
|
*
|
|
* @example
|
|
* const handler = new DeepLinkHandler(router);
|
|
* await handler.handleDeepLink("timesafari://claim/123?view=details");
|
|
*/
|
|
|
|
import { Router } from "vue-router";
|
|
import { deepLinkSchemas, baseUrlSchema } from "../types/deepLinks";
|
|
import { logConsoleAndDb } from "../db";
|
|
import type { DeepLinkError } from "../interfaces/deepLinks";
|
|
|
|
export class DeepLinkHandler {
|
|
private router: Router;
|
|
|
|
constructor(router: Router) {
|
|
this.router = router;
|
|
}
|
|
|
|
/**
|
|
* Parses deep link URL into path, params and query components
|
|
*/
|
|
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
|
|
* @param url The deep link URL to process
|
|
*/
|
|
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
|
|
*/
|
|
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": "project",
|
|
"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"
|
|
};
|
|
|
|
const routeName = routeMap[path];
|
|
if (!routeName) {
|
|
throw {
|
|
code: "INVALID_ROUTE",
|
|
message: `Unsupported route: ${path}`,
|
|
};
|
|
}
|
|
|
|
// Validate parameters based on route type
|
|
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
|
|
const validatedParams = await schema.parseAsync({
|
|
...params,
|
|
...query,
|
|
});
|
|
|
|
await this.router.replace({
|
|
name: routeName,
|
|
params: validatedParams,
|
|
query,
|
|
});
|
|
}
|
|
}
|
|
|