diff --git a/scripts/test-android.js b/scripts/test-android.js index ce117b6..cb7f1f5 100644 --- a/scripts/test-android.js +++ b/scripts/test-android.js @@ -122,8 +122,15 @@ const executeDeeplink = async (url, description, log) => { execSync(`adb shell am start -W -a android.intent.action.VIEW -d "${url}" -c android.intent.category.BROWSABLE`); log(`✅ Successfully executed: ${description}`); - // Wait between deeplink tests - await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5s + // Wait for app to load content + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Press a key (Back button) to ensure app is in consistent state + log(`📱 Sending keystroke (BACK) to device...`); + execSync('adb shell input keyevent KEYCODE_BACK'); + + // Wait a bit longer after keystroke before next test + await new Promise(resolve => setTimeout(resolve, 2000)); } catch (error) { log(`❌ Failed to execute deeplink: ${description}`); log(`Error: ${error.message}`); diff --git a/src/libs/util.ts b/src/libs/util.ts index a249dca..1d2fa03 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -99,7 +99,6 @@ export function numberOrZero(str: string): number { return isNumeric(str) ? +str : 0; } - /** * from https://tools.ietf.org/html/rfc3986#section-3 * also useful is https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Definition diff --git a/src/router/index.ts b/src/router/index.ts index e17801f..b28aa8e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -281,6 +281,15 @@ const routes: Array = [ name: "user-profile", component: () => import("../views/UserProfileView.vue"), }, + { + path: "/deep-link-error", + name: "deep-link-error", + component: () => import("../views/DeepLinkErrorView.vue"), + meta: { + title: "Invalid Deep Link", + requiresAuth: false, + }, + }, ]; const isElectron = window.location.protocol === "file:"; diff --git a/src/services/deepLinks.ts b/src/services/deepLinks.ts index 8d75d0e..5a253f8 100644 --- a/src/services/deepLinks.ts +++ b/src/services/deepLinks.ts @@ -29,7 +29,11 @@ */ import { Router } from "vue-router"; -import { deepLinkSchemas, baseUrlSchema } from "../types/deepLinks"; +import { + deepLinkSchemas, + baseUrlSchema, + routeSchema, +} from "../types/deepLinks"; import { logConsoleAndDb } from "../db"; import type { DeepLinkError } from "../interfaces/deepLinks"; @@ -111,7 +115,7 @@ export class DeepLinkHandler { ): Promise { const routeMap: Record = { "user-profile": "user-profile", - project: "project", + "project-details": "project-details", "onboard-meeting-setup": "onboard-meeting-setup", "invite-one-accept": "invite-one-accept", "contact-import": "contact-import", @@ -124,25 +128,63 @@ export class DeepLinkHandler { did: "did", }; - const routeName = routeMap[path]; - if (!routeName) { + // 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}`, }; } - // Validate parameters based on route type + // Continue with parameter validation as before... const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas]; - const validatedParams = await schema.parseAsync({ - ...params, - ...query, - }); - await this.router.replace({ - name: routeName, - params: validatedParams, - query, - }); + 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, + }; + } } } diff --git a/src/types/deepLinks.ts b/src/types/deepLinks.ts index 923027c..a8564d6 100644 --- a/src/types/deepLinks.ts +++ b/src/types/deepLinks.ts @@ -27,13 +27,35 @@ */ import { z } from "zod"; -// Base URL validation schema +// Add a union type of all valid route paths +export const VALID_DEEP_LINK_ROUTES = [ + "user-profile", + "project-details", + "onboard-meeting-setup", + "invite-one-accept", + "contact-import", + "confirm-gift", + "claim", + "claim-cert", + "claim-add-raw", + "contact-edit", + "contacts", + "did", +] 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 export const deepLinkSchemas = { "user-profile": z.object({ diff --git a/src/views/DeepLinkErrorView.vue b/src/views/DeepLinkErrorView.vue new file mode 100644 index 0000000..2fba6e4 --- /dev/null +++ b/src/views/DeepLinkErrorView.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index c38df85..95476af 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -372,7 +372,10 @@
- +
Totals @@ -451,14 +454,17 @@
{{ give.issuedAt?.substring(0, 10) }} -
+
{{ give.description }}