Browse Source

Fix InviteOneAcceptView migration fence; remove USE_DEXIE_DB dependency

- Remove USE_DEXIE_DB import from app.ts constants
- Update InviteOneAcceptView to use PlatformServiceMixin pattern
- Remove legacy Dexie database access code
- Move WORKER_ONLY_DATABASE_IMPLEMENTATION.md to doc/ directory
- Remerge changes in router/index.ts

Fixes Electron build failure caused by missing USE_DEXIE_DB export.
web-serve-fix
Matthew Raymer 2 weeks ago
parent
commit
e85def4d60
  1. 57
      src/interfaces/deepLinks.ts
  2. 11
      src/main.capacitor.ts
  3. 13
      src/router/index.ts
  4. 130
      src/services/deepLinks.ts
  5. 105
      src/views/DeepLinkErrorView.vue
  6. 120
      src/views/InviteOneAcceptView.vue

57
src/interfaces/deepLinks.ts

@ -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,24 @@ 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({
contactJwt: z.string().optional(),
inviteJwt: z.string().optional(),
}),
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(), // optional because A) it could be a query param, and B) the page displays an input if things go wrong
jwt: z.string().optional(),
}), }),
"onboard-meeting-setup": z.object({ "onboard-meeting-members": z.object({
id: z.string(), id: z.string(),
}), }),
project: z.object({ project: z.object({
@ -92,6 +71,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 +92,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[]]);

11
src/main.capacitor.ts

@ -72,12 +72,11 @@ const handleDeepLink = async (data: { url: string }) => {
await deepLinkHandler.handleDeepLink(data.url); await deepLinkHandler.handleDeepLink(data.url);
} catch (error) { } catch (error) {
logger.error("[DeepLink] Error handling deep link: ", error); logger.error("[DeepLink] Error handling deep link: ", error);
handleApiError( let message: string = error instanceof Error ? error.message : safeStringify(error);
{ if (data.url) {
message: error instanceof Error ? error.message : safeStringify(error), message += `\nURL: ${data.url}`;
} as AxiosError, }
"deep-link", handleApiError({ message } as AxiosError, "deep-link");
);
} }
}; };

13
src/router/index.ts

@ -73,6 +73,11 @@ const routes: Array<RouteRecordRaw> = [
name: "contacts", name: "contacts",
component: () => import("../views/ContactsView.vue"), component: () => import("../views/ContactsView.vue"),
}, },
{
path: "/database-migration",
name: "database-migration",
component: () => import("../views/DatabaseMigration.vue"),
},
{ {
path: "/did/:did?", path: "/did/:did?",
name: "did", name: "did",
@ -139,8 +144,9 @@ const routes: Array<RouteRecordRaw> = [
component: () => import("../views/InviteOneView.vue"), component: () => import("../views/InviteOneView.vue"),
}, },
{ {
// optional because A) it could be a query param, and B) the page displays an input if things go wrong
path: "/invite-one-accept/:jwt?", path: "/invite-one-accept/:jwt?",
name: "InviteOneAcceptView", name: "invite-one-accept",
component: () => import("../views/InviteOneAcceptView.vue"), component: () => import("../views/InviteOneAcceptView.vue"),
}, },
{ {
@ -148,11 +154,6 @@ const routes: Array<RouteRecordRaw> = [
name: "logs", name: "logs",
component: () => import("../views/LogView.vue"), component: () => import("../views/LogView.vue"),
}, },
{
path: "/database-migration",
name: "database-migration",
component: () => import("../views/DatabaseMigration.vue"),
},
{ {
path: "/new-activity", path: "/new-activity",
name: "new-activity", name: "new-activity",

130
src/services/deepLinks.ts

@ -44,15 +44,41 @@
*/ */
import { Router } from "vue-router"; import { Router } from "vue-router";
import { z } from "zod";
import { import {
deepLinkSchemas, deepLinkSchemas,
baseUrlSchema, baseUrlSchema,
routeSchema, routeSchema,
DeepLinkRoute, DeepLinkRoute,
} from "../interfaces/deepLinks"; } from "../interfaces/deepLinks";
// Legacy databaseUtil import removed - using logger instead import { logConsoleAndDb } from "../db/databaseUtil";
import type { DeepLinkError } from "../interfaces/deepLinks"; import type { DeepLinkError } from "../interfaces/deepLinks";
import { logger } from "../utils/logger";
// 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.
@ -70,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.
* *
@ -116,18 +119,9 @@ export class DeepLinkHandler {
const [path, queryString] = parts[1].split("?"); const [path, queryString] = parts[1].split("?");
const [routePath, ...pathParams] = path.split("/"); const [routePath, ...pathParams] = path.split("/");
// logger.info(
// "[DeepLink] Debug:",
// "Route Path:",
// routePath,
// "Path Params:",
// pathParams,
// "Query String:",
// queryString,
// );
// 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}`,
@ -145,9 +139,14 @@ 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("/");
} }
// logConsoleAndDb(
// `[DeepLink] Debug: Route Path: ${routePath} Path Params: ${JSON.stringify(params)} Query String: ${JSON.stringify(query)}`,
// false,
// );
return { path: routePath, params, query }; return { path: routePath, params, query };
} }
@ -171,60 +170,73 @@ 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
logger.error(`[DeepLink] Invalid route path: ${path}`); logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
// Redirect to error page with information about the invalid link // Redirect to error page with information about the invalid link
await this.router.replace({ await this.router.replace({
name: "deep-link-error", name: "deep-link-error",
params,
query: { query: {
originalPath: path, originalPath: path,
errorCode: "INVALID_ROUTE", errorCode: "INVALID_ROUTE",
message: `The link you followed (${path}) is not supported`, errorMessage: `The link you followed (${path}) is not supported`,
...query,
}, },
}); });
throw { // This previously threw an error but we're redirecting so there's no need.
code: "INVALID_ROUTE", return;
message: `Unsupported route: ${path}`,
};
} }
// Continue with parameter validation as before... // Continue with parameter validation as before...
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas]; const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
let validatedParams, validatedQuery;
try { try {
const validatedParams = await schema.parseAsync({ validatedParams = await schema.parseAsync(params);
...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, ...query,
},
}); });
// This previously threw an error but we're redirecting so there's no need.
return;
}
try {
await this.router.replace({ await this.router.replace({
name: routeName, name: routeName,
params: validatedParams, params: validatedParams,
query, query: validatedQuery,
}); });
} catch (error) { } 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 // For parameter validation errors, provide specific error feedback
await this.router.replace({ await this.router.replace({
name: "deep-link-error", name: "deep-link-error",
params: validatedParams,
query: { query: {
originalPath: path, originalPath: path,
errorCode: "INVALID_PARAMETERS", errorCode: "ROUTING_ERROR",
message: `The link parameters are invalid: ${(error as Error).message}`, errorMessage: `Error routing to ${routeName}: ${(JSON.stringify(error))}`,
...validatedQuery,
}, },
}); });
throw {
code: "INVALID_PARAMETERS",
message: (error as Error).message,
details: error,
params: params,
query: query,
};
} }
} }
/** /**
@ -236,7 +248,6 @@ export class DeepLinkHandler {
*/ */
async handleDeepLink(url: string): Promise<void> { async handleDeepLink(url: string): Promise<void> {
try { try {
logger.info("[DeepLink] Processing URL: " + url);
const { path, params, query } = this.parseDeepLink(url); const { path, params, query } = this.parseDeepLink(url);
// Ensure params is always a Record<string,string> by converting undefined to empty string // Ensure params is always a Record<string,string> by converting undefined to empty string
const sanitizedParams = Object.fromEntries( const sanitizedParams = Object.fromEntries(
@ -245,8 +256,9 @@ export class DeepLinkHandler {
await this.validateAndRoute(path, sanitizedParams, query); await this.validateAndRoute(path, sanitizedParams, query);
} catch (error) { } catch (error) {
const deepLinkError = error as DeepLinkError; const deepLinkError = error as DeepLinkError;
logger.error( logConsoleAndDb(
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`, `[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
true,
); );
throw { throw {

105
src/views/DeepLinkErrorView.vue

@ -15,9 +15,9 @@
<code>timesafari://{{ formattedPath }}</code> <code>timesafari://{{ formattedPath }}</code>
<div class="debug-info"> <div class="debug-info">
<h4>Parameters:</h4> <h4>Parameters:</h4>
<pre>{{ JSON.stringify($route.params, null, 2) }}</pre> <pre>{{ JSON.stringify(route.params, null, 2) }}</pre>
<h4>Query:</h4> <h4>Query:</h4>
<pre>{{ JSON.stringify($route.query, null, 2) }}</pre> <pre>{{ JSON.stringify(route.query, null, 2) }}</pre>
</div> </div>
</div> </div>
</div> </div>
@ -31,104 +31,79 @@
<h2>Supported Deep Links</h2> <h2>Supported Deep Links</h2>
<ul> <ul>
<li v-for="(routeItem, index) in validRoutes" :key="index"> <li v-for="(routeItem, index) in validRoutes" :key="index">
<code>timesafari://{{ routeItem }}/:id</code> <code>timesafari://{{ routeItem }}/:{{ deepLinkSchemaKeys[routeItem] }}</code>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
// TODO: Testing Required - Vue 3 to Options API + PlatformServiceMixin Migration import { computed, onMounted } from "vue";
// Priority: Medium | Migrated: 2025-07-06 | Author: Matthew Raymer import { useRoute, useRouter } from "vue-router";
// import { VALID_DEEP_LINK_ROUTES, deepLinkSchemas } from "../interfaces/deepLinks";
// MIGRATION DETAILS: Converted from Vue 3 Composition API to Options API for PlatformServiceMixin import { logConsoleAndDb } from "../db/databaseUtil";
// - Replaced logConsoleAndDb() with this.$logAndConsole()
// - Converted computed properties to getters
// - Converted onMounted to mounted() lifecycle hook
//
// TESTING NEEDED:
// 1. Test invalid deep link triggering (easy to test)
// 2. Verify error logging works correctly
// 3. Test error page display and navigation
// 4. Test "Report Issue" functionality
//
// Test URL: timesafari://invalid/path?param=test
import { Component, Vue } from "vue-facing-decorator";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import { VALID_DEEP_LINK_ROUTES } from "../interfaces/deepLinks";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
@Component({ const route = useRoute();
mixins: [PlatformServiceMixin], const router = useRouter();
// an object with the route as the key and the first param name as the value
const deepLinkSchemaKeys = Object.fromEntries(
Object.entries(deepLinkSchemas).map(([route, schema]) => {
const param = Object.keys(schema.shape)[0];
return [route, param];
}) })
export default class DeepLinkErrorView extends Vue { );
/** Current route instance */
$route!: RouteLocationNormalizedLoaded;
/** Router instance for navigation */
$router!: Router;
validRoutes = VALID_DEEP_LINK_ROUTES;
// Extract error information from query params // Extract error information from query params
get errorCode(): string { const errorCode = computed(
return (this.$route.query.errorCode as string) || "UNKNOWN_ERROR"; () => (route.query.errorCode as string) || "UNKNOWN_ERROR",
}
get errorMessage(): string {
return (
(this.$route.query.message as string) ||
"The deep link you followed is invalid or not supported."
); );
} const errorMessage = computed(
() =>
get originalPath(): string { (route.query.errorMessage as string) ||
return this.$route.query.originalPath as string; "The deep link you followed is invalid or not supported.",
} );
const originalPath = computed(() => route.query.originalPath as string);
const validRoutes = VALID_DEEP_LINK_ROUTES;
// Format the path and include any parameters // Format the path and include any parameters
get formattedPath(): string { const formattedPath = computed(() => {
if (!this.originalPath) return ""; if (!originalPath.value) return "";
const path = this.originalPath.replace(/^\/+/, ""); const path = originalPath.value.replace(/^\/+/, "");
// Log for debugging // Log for debugging
logger.log( logger.log(
"[DeepLinkError] Original Path:", "[DeepLinkError] Original Path:",
this.originalPath, originalPath.value,
"Route Params:", "Route Params:",
this.$route.params, route.params,
"Route Query:", "Route Query:",
this.$route.query, route.query,
); );
return path; return path;
} });
// Navigation methods // Navigation methods
goHome(): void { const goHome = () => router.replace({ name: "home" });
this.$router.replace({ name: "home" }); const reportIssue = () => {
}
reportIssue(): void {
// Open a support form or email // Open a support form or email
window.open( window.open(
"mailto:support@timesafari.app?subject=Invalid Deep Link&body=" + "mailto:support@timesafari.app?subject=Invalid Deep Link&body=" +
encodeURIComponent( encodeURIComponent(
`I encountered an error with a deep link: timesafari://${this.originalPath}\nError: ${this.errorMessage}`, `I encountered an error with a deep link: timesafari://${originalPath.value}\nError: ${errorMessage.value}`,
), ),
); );
} };
// Log the error for analytics // Log the error for analytics
async mounted(): Promise<void> { onMounted(() => {
this.$logAndConsole( logConsoleAndDb(
`[DeepLink] Error page displayed for path: ${this.originalPath}, code: ${this.errorCode}, params: ${JSON.stringify(this.$route.params)}`, `[DeepLinkError] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}, query: ${JSON.stringify(route.query)}`,
true, true,
); );
} });
}
</script> </script>
<style scoped> <style scoped>

120
src/views/InviteOneAcceptView.vue

@ -24,12 +24,12 @@
placeholder="Paste invitation..." placeholder="Paste invitation..."
class="mt-4 border-2 border-gray-300 p-2 rounded" class="mt-4 border-2 border-gray-300 p-2 rounded"
cols="30" cols="30"
@input="handleInputChange" @input="() => checkInvite(inputJwt)"
/> />
<br /> <br />
<button <button
class="ml-2 p-2 bg-blue-500 text-white rounded" class="ml-2 p-2 bg-blue-500 text-white rounded"
@click="handleAcceptClick" @click="() => processInvite(inputJwt, true)"
> >
Accept Accept
</button> </button>
@ -43,26 +43,13 @@ import { Router, RouteLocationNormalized } from "vue-router";
import QuickNav from "../components/QuickNav.vue"; import QuickNav from "../components/QuickNav.vue";
import { APP_SERVER, NotificationIface } from "../constants/app"; import { APP_SERVER, NotificationIface } from "../constants/app";
import { logConsoleAndDb } from "../db/index"; import {
logConsoleAndDb,
} from "../db/index";
import * as databaseUtil from "../db/databaseUtil";
import { decodeEndorserJwt } from "../libs/crypto/vc"; import { decodeEndorserJwt } from "../libs/crypto/vc";
import { errorStringForLog } from "../libs/endorserServer"; import { errorStringForLog } from "../libs/endorserServer";
import { generateSaveAndActivateIdentity } from "../libs/util"; import { generateSaveAndActivateIdentity } from "../libs/util";
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
import { logger } from "../utils/logger";
import {
NOTIFY_INVITE_MISSING,
NOTIFY_INVITE_PROCESSING_ERROR,
NOTIFY_INVITE_TRUNCATED_DATA,
} from "../constants/notifications";
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
/**
* @file InviteOneAcceptView.vue
* @description Invitation acceptance flow for single-use invitations to join the platform.
* Processes JWTs from various sources (URL, text input) and redirects to contacts page
* for completion of the invitation process.
* @author Matthew Raymer
*/
/** /**
* Invite One Accept View Component * Invite One Accept View Component
@ -91,7 +78,6 @@ import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
*/ */
@Component({ @Component({
components: { QuickNav }, components: { QuickNav },
mixins: [PlatformServiceMixin],
}) })
export default class InviteOneAcceptView extends Vue { export default class InviteOneAcceptView extends Vue {
/** Notification function injected by Vue */ /** Notification function injected by Vue */
@ -101,9 +87,6 @@ export default class InviteOneAcceptView extends Vue {
/** Route instance for current route */ /** Route instance for current route */
$route!: RouteLocationNormalized; $route!: RouteLocationNormalized;
// Notification helper system
private notify = createNotifyHelpers(this.$notify);
/** Active user's DID */ /** Active user's DID */
activeDid = ""; activeDid = "";
/** API server endpoint */ /** API server endpoint */
@ -117,7 +100,7 @@ export default class InviteOneAcceptView extends Vue {
* Component lifecycle hook that initializes invite processing * Component lifecycle hook that initializes invite processing
* *
* Workflow: * Workflow:
* 1. Loads account settings using PlatformServiceMixin * 1. Opens database connection
* 2. Retrieves account settings * 2. Retrieves account settings
* 3. Ensures active DID exists or generates one * 3. Ensures active DID exists or generates one
* 4. Extracts JWT from URL path * 4. Extracts JWT from URL path
@ -129,45 +112,21 @@ export default class InviteOneAcceptView extends Vue {
async mounted() { async mounted() {
this.checkingInvite = true; this.checkingInvite = true;
try { // Load or generate identity
logger.debug( let settings = await databaseUtil.retrieveSettingsForActiveAccount();
"[InviteOneAcceptView] Component mounted - processing invitation",
);
// Load or generate identity using PlatformServiceMixin
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
logger.debug("[InviteOneAcceptView] Account settings loaded", {
hasActiveDid: !!this.activeDid,
hasApiServer: !!this.apiServer,
});
if (!this.activeDid) { if (!this.activeDid) {
logger.debug(
"[InviteOneAcceptView] No active DID found, generating new identity",
);
this.activeDid = await generateSaveAndActivateIdentity(); this.activeDid = await generateSaveAndActivateIdentity();
logger.debug("[InviteOneAcceptView] New identity generated", {
newActiveDid: !!this.activeDid,
});
} }
// Extract JWT from route path // Extract JWT from route path
const jwt = (this.$route.params.jwt as string) || ""; const jwt = (this.$route.params.jwt as string) || this.$route.query.jwt as string || "";
logger.debug("[InviteOneAcceptView] Processing invite from route", {
hasJwt: !!jwt,
jwtLength: jwt.length,
});
await this.processInvite(jwt, false); await this.processInvite(jwt, false);
} catch (error) {
logger.error("[InviteOneAcceptView] Error during mount:", error);
} finally {
this.checkingInvite = false; this.checkingInvite = false;
} }
}
/** /**
* Processes an invite JWT and/or text containing the invite * Processes an invite JWT and/or text containing the invite
@ -265,7 +224,15 @@ export default class InviteOneAcceptView extends Vue {
*/ */
private handleMissingJwt(notify: boolean) { private handleMissingJwt(notify: boolean) {
if (notify) { if (notify) {
this.notify.error(NOTIFY_INVITE_MISSING.message, TIMEOUTS.LONG); this.$notify(
{
group: "alert",
type: "danger",
title: "Missing Invite",
text: "There was no invite. Paste the entire text that has the data.",
},
5000,
);
} }
} }
@ -279,7 +246,15 @@ export default class InviteOneAcceptView extends Vue {
logConsoleAndDb(fullError, true); logConsoleAndDb(fullError, true);
if (notify) { if (notify) {
this.notify.error(NOTIFY_INVITE_PROCESSING_ERROR.message, TIMEOUTS.BRIEF); this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "There was an error processing that invite.",
},
3000,
);
} }
} }
@ -302,35 +277,16 @@ export default class InviteOneAcceptView extends Vue {
jwtInput.endsWith("invite-one-accept") || jwtInput.endsWith("invite-one-accept") ||
jwtInput.endsWith("invite-one-accept/") jwtInput.endsWith("invite-one-accept/")
) { ) {
this.notify.error(NOTIFY_INVITE_TRUNCATED_DATA.message, TIMEOUTS.LONG); this.$notify(
} {
} group: "alert",
type: "danger",
/** title: "Error",
* Template handler for input change events text: "That is only part of the invite data; it's missing some at the end. Try another way to get the full data.",
* },
* Called when user types in the invitation text input field. 5000,
* Validates the input for common error patterns. );
*
* @throws Will not throw but shows notifications
* @emits Notifications on validation errors
*/
handleInputChange() {
this.checkInvite(this.inputJwt);
} }
/**
* Template handler for Accept button click
*
* Processes the invitation with user notification enabled.
* This is the explicit user action to accept an invitation.
*
* @throws Will not throw but logs errors
* @emits Notifications on errors
* @emits Router navigation on success
*/
handleAcceptClick() {
this.processInvite(this.inputJwt, true);
} }
} }
</script> </script>

Loading…
Cancel
Save