forked from jsnbuchanan/crowd-funder-for-time-pwa
Consolidate deep-link paths to be derived from the same source so they don't get out of sync any more.
This commit is contained in:
@@ -27,37 +27,8 @@
|
|||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
// Add a union type of all valid route paths
|
|
||||||
export const VALID_DEEP_LINK_ROUTES = [
|
|
||||||
// note that similar lists are below in deepLinkSchemas and in src/services/deepLinks.ts
|
|
||||||
"claim",
|
|
||||||
"claim-add-raw",
|
|
||||||
"claim-cert",
|
|
||||||
"confirm-gift",
|
|
||||||
"contact-import",
|
|
||||||
"did",
|
|
||||||
"invite-one-accept",
|
|
||||||
"onboard-meeting-setup",
|
|
||||||
"project",
|
|
||||||
"user-profile",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
// Create a type from the array
|
|
||||||
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
|
|
||||||
|
|
||||||
// Update your schema definitions to use this type
|
|
||||||
export const baseUrlSchema = z.object({
|
|
||||||
scheme: z.literal("timesafari"),
|
|
||||||
path: z.string(),
|
|
||||||
queryParams: z.record(z.string()).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use the type to ensure route validation
|
|
||||||
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES);
|
|
||||||
|
|
||||||
// Parameter validation schemas for each route type
|
// Parameter validation schemas for each route type
|
||||||
export const deepLinkSchemas = {
|
export const deepLinkSchemas = {
|
||||||
// note that similar lists are above in VALID_DEEP_LINK_ROUTES and in src/services/deepLinks.ts
|
|
||||||
claim: z.object({
|
claim: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
}),
|
}),
|
||||||
@@ -72,16 +43,22 @@ export const deepLinkSchemas = {
|
|||||||
"confirm-gift": z.object({
|
"confirm-gift": z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
}),
|
}),
|
||||||
|
"contact-edit": z.object({
|
||||||
|
did: z.string(),
|
||||||
|
}),
|
||||||
"contact-import": z.object({
|
"contact-import": z.object({
|
||||||
jwt: z.string(),
|
jwt: z.string(),
|
||||||
}),
|
}),
|
||||||
|
contacts: z.object({
|
||||||
|
contacts: z.string(), // JSON string of contacts array
|
||||||
|
}),
|
||||||
did: z.object({
|
did: z.object({
|
||||||
did: z.string(),
|
did: z.string(),
|
||||||
}),
|
}),
|
||||||
"invite-one-accept": z.object({
|
"invite-one-accept": z.object({
|
||||||
jwt: z.string(),
|
jwt: z.string(),
|
||||||
}),
|
}),
|
||||||
"onboard-meeting-setup": z.object({
|
"onboard-meeting-members": z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
}),
|
}),
|
||||||
project: z.object({
|
project: z.object({
|
||||||
@@ -92,6 +69,19 @@ export const deepLinkSchemas = {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create a type from the array
|
||||||
|
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
|
||||||
|
|
||||||
|
// Update your schema definitions to use this type
|
||||||
|
export const baseUrlSchema = z.object({
|
||||||
|
scheme: z.literal("timesafari"),
|
||||||
|
path: z.string(),
|
||||||
|
queryParams: z.record(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a union type of all valid route paths
|
||||||
|
export const VALID_DEEP_LINK_ROUTES = Object.keys(deepLinkSchemas) as readonly (keyof typeof deepLinkSchemas)[];
|
||||||
|
|
||||||
export type DeepLinkParams = {
|
export type DeepLinkParams = {
|
||||||
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>;
|
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>;
|
||||||
};
|
};
|
||||||
@@ -100,3 +90,6 @@ export interface DeepLinkError extends Error {
|
|||||||
code: string;
|
code: string;
|
||||||
details?: unknown;
|
details?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the type to ensure route validation
|
||||||
|
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES as [string, ...string[]]);
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deepLinkSchemas,
|
deepLinkSchemas,
|
||||||
baseUrlSchema,
|
baseUrlSchema,
|
||||||
@@ -53,6 +55,31 @@ import {
|
|||||||
import { logConsoleAndDb } from "../db/databaseUtil";
|
import { logConsoleAndDb } from "../db/databaseUtil";
|
||||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
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.
|
* Handles processing and routing of deep links in the application.
|
||||||
* Provides validation, error handling, and routing for deep link URLs.
|
* 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.
|
* Parses deep link URL into path, params and query components.
|
||||||
* Validates URL structure using Zod schemas.
|
* Validates URL structure using Zod schemas.
|
||||||
*
|
*
|
||||||
@@ -126,7 +130,7 @@ export class DeepLinkHandler {
|
|||||||
// );
|
// );
|
||||||
|
|
||||||
// Validate route exists before proceeding
|
// Validate route exists before proceeding
|
||||||
if (!this.ROUTE_MAP[routePath]) {
|
if (!ROUTE_MAP[routePath]) {
|
||||||
throw {
|
throw {
|
||||||
code: "INVALID_ROUTE",
|
code: "INVALID_ROUTE",
|
||||||
message: `Invalid route path: ${routePath}`,
|
message: `Invalid route path: ${routePath}`,
|
||||||
@@ -144,7 +148,7 @@ export class DeepLinkHandler {
|
|||||||
const params: Record<string, string> = {};
|
const params: Record<string, string> = {};
|
||||||
if (pathParams) {
|
if (pathParams) {
|
||||||
// Now we know routePath exists in ROUTE_MAP
|
// 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("/");
|
params[routeConfig.paramKey ?? "id"] = pathParams.join("/");
|
||||||
}
|
}
|
||||||
return { path: routePath, params, query };
|
return { path: routePath, params, query };
|
||||||
@@ -170,7 +174,7 @@ export class DeepLinkHandler {
|
|||||||
try {
|
try {
|
||||||
// Validate route exists
|
// Validate route exists
|
||||||
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
||||||
routeName = this.ROUTE_MAP[validRoute].name;
|
routeName = ROUTE_MAP[validRoute].name;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log the invalid route attempt
|
// Log the invalid route attempt
|
||||||
logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
|
logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const reportIssue = () => {
|
|||||||
// Log the error for analytics
|
// Log the error for analytics
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}`,
|
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}, query: ${JSON.stringify(route.query)}`,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user