feat(deepLinks): implement comprehensive deep linking system

- Add type-safe deep link parameter validation using Zod
- Implement consistent error handling across all deep link routes
- Add support for query parameters in deep links
- Create comprehensive deep linking documentation
- Add logging for deep link operations

Security:
- Validate all deep link parameters before processing
- Sanitize and type-check query parameters
- Add error boundaries around deep link handling
- Implement route-specific parameter validation

Testing:
- Add parameter validation tests
- Add error handling tests
- Test query parameter support
This commit is contained in:
Matthew Raymer
2025-02-26 09:35:04 +00:00
parent 3b4f4dc125
commit 1a9c97fe88
6 changed files with 199 additions and 85 deletions

84
src/services/deepLinks.ts Normal file
View File

@@ -0,0 +1,84 @@
import { Router } from "vue-router";
import { deepLinkSchemas, DeepLinkParams } from "../types/deepLinks";
import { logConsoleAndDb } from "../db";
interface DeepLinkError extends Error {
code: string;
details?: unknown;
}
export class DeepLinkHandler {
private router: Router;
constructor(router: Router) {
this.router = router;
}
/**
* 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);
await this.validateAndRoute(path, params, 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> = {
claim: 'claim',
'claim-cert': 'claim-cert',
'claim-add-raw': 'claim-add-raw',
'contact-edit': 'contact-edit',
'contact-import': 'contact-import',
project: 'project',
'invite-one-accept': 'invite-one-accept',
'offer-details': 'offer-details',
'confirm-gift': 'confirm-gift'
};
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
});
}
}