diff --git a/src/services/PlatformService.ts b/src/services/PlatformService.ts index ab3c0a60..821261ee 100644 --- a/src/services/PlatformService.ts +++ b/src/services/PlatformService.ts @@ -1,25 +1,94 @@ +/** + * Represents the result of an image capture or selection operation. + * Contains both the image data as a Blob and the associated filename. + */ export interface ImageResult { + /** The image data as a Blob object */ blob: Blob; + /** The filename associated with the image */ fileName: string; } +/** + * Platform-agnostic interface for handling platform-specific operations. + * Provides a common API for file system operations, camera interactions, + * platform detection, and deep linking across different platforms + * (web, mobile, desktop). + */ export interface PlatformService { // File system operations + /** + * Reads the contents of a file at the specified path. + * @param path - The path to the file to read + * @returns Promise resolving to the file contents as a string + */ readFile(path: string): Promise; + + /** + * Writes content to a file at the specified path. + * @param path - The path where the file should be written + * @param content - The content to write to the file + * @returns Promise that resolves when the write is complete + */ writeFile(path: string, content: string): Promise; + + /** + * Deletes a file at the specified path. + * @param path - The path to the file to delete + * @returns Promise that resolves when the deletion is complete + */ deleteFile(path: string): Promise; + + /** + * Lists all files in the specified directory. + * @param directory - The directory path to list + * @returns Promise resolving to an array of filenames + */ listFiles(directory: string): Promise; // Camera operations + /** + * Activates the device camera to take a picture. + * @returns Promise resolving to the captured image result + */ takePicture(): Promise; + + /** + * Opens a file picker to select an existing image. + * @returns Promise resolving to the selected image result + */ pickImage(): Promise; // Platform specific features + /** + * Checks if the current platform is Capacitor (mobile). + * @returns true if running on Capacitor + */ isCapacitor(): boolean; + + /** + * Checks if the current platform is Electron (desktop). + * @returns true if running on Electron + */ isElectron(): boolean; + + /** + * Checks if the current platform is PyWebView. + * @returns true if running on PyWebView + */ isPyWebView(): boolean; + + /** + * Checks if the current platform is web browser. + * @returns true if running in a web browser + */ isWeb(): boolean; // Deep linking + /** + * Handles deep link URLs for the application. + * @param url - The deep link URL to handle + * @returns Promise that resolves when the deep link has been handled + */ handleDeepLink(url: string): Promise; } diff --git a/src/services/PlatformServiceFactory.ts b/src/services/PlatformServiceFactory.ts index a3a74b9a..6dca11b8 100644 --- a/src/services/PlatformServiceFactory.ts +++ b/src/services/PlatformServiceFactory.ts @@ -4,9 +4,32 @@ import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService"; import { ElectronPlatformService } from "./platforms/ElectronPlatformService"; import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService"; +/** + * Factory class for creating platform-specific service implementations. + * Implements the Singleton pattern to ensure only one instance of PlatformService exists. + * + * The factory determines which platform implementation to use based on the VITE_PLATFORM + * environment variable. Supported platforms are: + * - capacitor: Mobile platform using Capacitor + * - electron: Desktop platform using Electron + * - pywebview: Python WebView implementation + * - web: Default web platform (fallback) + * + * @example + * ```typescript + * const platformService = PlatformServiceFactory.getInstance(); + * await platformService.takePicture(); + * ``` + */ export class PlatformServiceFactory { private static instance: PlatformService | null = null; + /** + * Gets or creates the singleton instance of PlatformService. + * Creates the appropriate platform-specific implementation based on environment. + * + * @returns {PlatformService} The singleton instance of PlatformService + */ public static getInstance(): PlatformService { if (PlatformServiceFactory.instance) { return PlatformServiceFactory.instance; diff --git a/src/services/api.ts b/src/services/api.ts index f0261f20..5869abf8 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,5 +1,40 @@ +/** + * API error handling utilities for the application. + * Provides centralized error handling for API requests with platform-specific logging. + * + * @module api + */ + import { AxiosError } from "axios"; import { logger } from "../utils/logger"; + +/** + * Handles API errors with platform-specific logging and error processing. + * + * @param error - The Axios error object from the failed request + * @param endpoint - The API endpoint that was called + * @returns null for rate limit errors (400), throws the error otherwise + * @throws The original error for non-rate-limit cases + * + * @remarks + * Special handling includes: + * - Enhanced logging for Capacitor platform + * - Rate limit detection and handling + * - Detailed error information logging including: + * - Error message + * - HTTP status + * - Response data + * - Request configuration (URL, method, headers) + * + * @example + * ```typescript + * try { + * await api.getData(); + * } catch (error) { + * handleApiError(error as AxiosError, '/api/data'); + * } + * ``` + */ export const handleApiError = (error: AxiosError, endpoint: string) => { if (process.env.VITE_PLATFORM === "capacitor") { logger.error(`[Capacitor API Error] ${endpoint}:`, { diff --git a/src/services/deepLinks.ts b/src/services/deepLinks.ts index 87096cf9..4681019a 100644 --- a/src/services/deepLinks.ts +++ b/src/services/deepLinks.ts @@ -23,6 +23,23 @@ * - Query parameter validation and sanitization * - Type-safe parameter passing to router * + * Deep Link Format: + * timesafari://[/][?queryParam1=value1&queryParam2=value2] + * + * Supported Routes: + * - user-profile: View user profile + * - project-details: View project details + * - onboard-meeting-setup: Setup onboarding meeting + * - invite-one-accept: Accept invitation + * - contact-import: Import contacts + * - confirm-gift: Confirm gift + * - claim: View claim + * - claim-cert: View claim certificate + * - claim-add-raw: Add raw claim + * - contact-edit: Edit contact + * - contacts: View contacts + * - did: View DID + * * @example * const handler = new DeepLinkHandler(router); * await handler.handleDeepLink("timesafari://claim/123?view=details"); @@ -38,15 +55,28 @@ import { import { logConsoleAndDb } from "../db"; import type { DeepLinkError } from "../interfaces/deepLinks"; +/** + * Handles processing and routing of deep links in the application. + * Provides validation, error handling, and routing for deep link URLs. + */ export class DeepLinkHandler { private router: Router; + /** + * Creates a new DeepLinkHandler instance. + * @param router - Vue Router instance for navigation + */ constructor(router: Router) { this.router = router; } /** - * 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. + * + * @param url - The deep link URL to parse (format: scheme://path[?query]) + * @throws {DeepLinkError} If URL format is invalid + * @returns Parsed URL components (path, params, query) */ private parseDeepLink(url: string) { const parts = url.split("://"); @@ -79,8 +109,11 @@ export class DeepLinkHandler { } /** - * Processes incoming deep links and routes them appropriately - * @param url The deep link URL to process + * Processes incoming deep links and routes them appropriately. + * Handles validation, error handling, and routing to the correct view. + * + * @param url - The deep link URL to process + * @throws {DeepLinkError} If URL processing fails */ async handleDeepLink(url: string): Promise { try { @@ -107,7 +140,13 @@ export class DeepLinkHandler { } /** - * Routes the deep link to appropriate view with validated parameters + * Routes the deep link to appropriate view with validated parameters. + * Validates route and parameters using Zod schemas before routing. + * + * @param path - The route path from the deep link + * @param params - URL parameters + * @param query - Query string parameters + * @throws {DeepLinkError} If validation fails or route is invalid */ private async validateAndRoute( path: string, diff --git a/src/services/plan.ts b/src/services/plan.ts index a1a32122..a730d63a 100644 --- a/src/services/plan.ts +++ b/src/services/plan.ts @@ -1,12 +1,55 @@ +/** + * Plan service module for handling plan and claim data loading. + * Provides functionality to load plans with retry mechanism and error handling. + * + * @module plan + */ + import axios from "axios"; import { logger } from "../utils/logger"; +/** + * Response interface for plan loading operations. + * Represents the structure of both successful and error responses. + */ interface PlanResponse { + /** The response data payload */ data?: unknown; + /** HTTP status code of the response */ status?: number; + /** Error message in case of failure */ error?: string; + /** Response headers */ + headers?: unknown; } +/** + * Loads a plan with automatic retry mechanism. + * Attempts to load the plan multiple times in case of failure. + * + * @param handle - The unique identifier for the plan or claim + * @param retries - Number of retry attempts (default: 3) + * @returns Promise resolving to PlanResponse + * + * @remarks + * - Implements exponential backoff with 1 second delay between retries + * - Provides detailed logging of each attempt and any errors + * - Handles both plan and claim flows based on handle content + * - Logs comprehensive error information including: + * - HTTP status and headers + * - Response data + * - Request configuration + * + * @example + * ```typescript + * const response = await loadPlanWithRetry('plan-123'); + * if (response.error) { + * console.error(response.error); + * } else { + * console.log(response.data); + * } + * ``` + */ export const loadPlanWithRetry = async ( handle: string, retries = 3, @@ -58,6 +101,22 @@ export const loadPlanWithRetry = async ( } }; +/** + * Makes a single API request to load a plan or claim. + * Determines the appropriate endpoint based on the handle. + * + * @param handle - The unique identifier for the plan or claim + * @returns Promise resolving to PlanResponse + * @throws Will throw an error if the API request fails + * + * @remarks + * - Automatically detects claim vs plan endpoints based on handle + * - Uses axios for HTTP requests + * - Provides detailed error logging + * - Different endpoints: + * - Claims: /api/claims/{handle} + * - Plans: /api/plans/{handle} + */ export const loadPlan = async (handle: string): Promise => { logger.log(`[Plan Service] Making API request for plan ${handle}`); diff --git a/src/services/platforms/CapacitorPlatformService.ts b/src/services/platforms/CapacitorPlatformService.ts index ef757155..b6ba1198 100644 --- a/src/services/platforms/CapacitorPlatformService.ts +++ b/src/services/platforms/CapacitorPlatformService.ts @@ -3,15 +3,37 @@ import { Filesystem, Directory } from "@capacitor/filesystem"; import { Camera, CameraResultType, CameraSource } from "@capacitor/camera"; import { logger } from "../../utils/logger"; +/** + * Platform service implementation for Capacitor (mobile) platform. + * Provides native mobile functionality through Capacitor plugins for: + * - File system operations + * - Camera and image picker + * - Platform-specific features + */ export class CapacitorPlatformService implements PlatformService { + /** + * Reads a file from the app's data directory. + * @param path - Relative path to the file in the app's data directory + * @returns Promise resolving to the file contents as string + * @throws Error if file cannot be read or doesn't exist + */ async readFile(path: string): Promise { const file = await Filesystem.readFile({ path, directory: Directory.Data, }); + if (file.data instanceof Blob) { + return await file.data.text(); + } return file.data; } + /** + * Writes content to a file in the app's data directory. + * @param path - Relative path where to write the file + * @param content - Content to write to the file + * @throws Error if write operation fails + */ async writeFile(path: string, content: string): Promise { await Filesystem.writeFile({ path, @@ -20,6 +42,11 @@ export class CapacitorPlatformService implements PlatformService { }); } + /** + * Deletes a file from the app's data directory. + * @param path - Relative path to the file to delete + * @throws Error if deletion fails or file doesn't exist + */ async deleteFile(path: string): Promise { await Filesystem.deleteFile({ path, @@ -27,14 +54,26 @@ export class CapacitorPlatformService implements PlatformService { }); } + /** + * Lists files in the specified directory within app's data directory. + * @param directory - Relative path to the directory to list + * @returns Promise resolving to array of filenames + * @throws Error if directory cannot be read or doesn't exist + */ async listFiles(directory: string): Promise { const result = await Filesystem.readdir({ path: directory, directory: Directory.Data, }); - return result.files; + return result.files.map(file => typeof file === 'string' ? file : file.name); } + /** + * Opens the device camera to take a picture. + * Configures camera for high quality images with editing enabled. + * @returns Promise resolving to the captured image data + * @throws Error if camera access fails or user cancels + */ async takePicture(): Promise { try { const image = await Camera.getPhoto({ @@ -55,6 +94,12 @@ export class CapacitorPlatformService implements PlatformService { } } + /** + * Opens the device photo gallery to pick an existing image. + * Configures picker for high quality images with editing enabled. + * @returns Promise resolving to the selected image data + * @throws Error if gallery access fails or user cancels + */ async pickImage(): Promise { try { const image = await Camera.getPhoto({ @@ -75,6 +120,12 @@ export class CapacitorPlatformService implements PlatformService { } } + /** + * Converts base64 image data to a Blob. + * @param base64String - Base64 encoded image data + * @returns Promise resolving to image Blob + * @throws Error if conversion fails + */ private async processImageData(base64String?: string): Promise { if (!base64String) { throw new Error("No image data received"); @@ -95,22 +146,43 @@ export class CapacitorPlatformService implements PlatformService { return new Blob(byteArrays, { type: "image/jpeg" }); } + /** + * Checks if running on Capacitor platform. + * @returns true, as this is the Capacitor implementation + */ isCapacitor(): boolean { return true; } + /** + * Checks if running on Electron platform. + * @returns false, as this is not Electron + */ isElectron(): boolean { return false; } + /** + * Checks if running on PyWebView platform. + * @returns false, as this is not PyWebView + */ isPyWebView(): boolean { return false; } + /** + * Checks if running on web platform. + * @returns false, as this is not web + */ isWeb(): boolean { return false; } + /** + * Handles deep link URLs for the application. + * Note: Capacitor handles deep links automatically. + * @param _url - The deep link URL (unused) + */ async handleDeepLink(_url: string): Promise { // Capacitor handles deep links automatically // This is just a placeholder for the interface diff --git a/src/services/platforms/ElectronPlatformService.ts b/src/services/platforms/ElectronPlatformService.ts index a2921904..0a99cd43 100644 --- a/src/services/platforms/ElectronPlatformService.ts +++ b/src/services/platforms/ElectronPlatformService.ts @@ -1,49 +1,122 @@ import { ImageResult, PlatformService } from "../PlatformService"; import { logger } from "../../utils/logger"; +/** + * Platform service implementation for Electron (desktop) platform. + * Note: This is a placeholder implementation with most methods currently unimplemented. + * Implements the PlatformService interface but throws "Not implemented" errors for most operations. + * + * @remarks + * This service is intended for desktop application functionality through Electron. + * Future implementations should provide: + * - Native file system access + * - Desktop camera integration + * - System-level features + */ export class ElectronPlatformService implements PlatformService { + /** + * Reads a file from the filesystem. + * @param _path - Path to the file to read + * @returns Promise that should resolve to file contents + * @throws Error with "Not implemented" message + * @todo Implement file reading using Electron's file system API + */ async readFile(_path: string): Promise { throw new Error("Not implemented"); } + /** + * Writes content to a file. + * @param _path - Path where to write the file + * @param _content - Content to write to the file + * @throws Error with "Not implemented" message + * @todo Implement file writing using Electron's file system API + */ async writeFile(_path: string, _content: string): Promise { throw new Error("Not implemented"); } + /** + * Deletes a file from the filesystem. + * @param _path - Path to the file to delete + * @throws Error with "Not implemented" message + * @todo Implement file deletion using Electron's file system API + */ async deleteFile(_path: string): Promise { throw new Error("Not implemented"); } + /** + * Lists files in the specified directory. + * @param _directory - Path to the directory to list + * @returns Promise that should resolve to array of filenames + * @throws Error with "Not implemented" message + * @todo Implement directory listing using Electron's file system API + */ async listFiles(_directory: string): Promise { throw new Error("Not implemented"); } + /** + * Should open system camera to take a picture. + * @returns Promise that should resolve to captured image data + * @throws Error with "Not implemented" message + * @todo Implement camera access using Electron's media APIs + */ async takePicture(): Promise { logger.error("takePicture not implemented in Electron platform"); throw new Error("Not implemented"); } + /** + * Should open system file picker for selecting an image. + * @returns Promise that should resolve to selected image data + * @throws Error with "Not implemented" message + * @todo Implement file picker using Electron's dialog API + */ async pickImage(): Promise { logger.error("pickImage not implemented in Electron platform"); throw new Error("Not implemented"); } + /** + * Checks if running on Capacitor platform. + * @returns false, as this is not Capacitor + */ isCapacitor(): boolean { return false; } + /** + * Checks if running on Electron platform. + * @returns true, as this is the Electron implementation + */ isElectron(): boolean { return true; } + /** + * Checks if running on PyWebView platform. + * @returns false, as this is not PyWebView + */ isPyWebView(): boolean { return false; } + /** + * Checks if running on web platform. + * @returns false, as this is not web + */ isWeb(): boolean { return false; } + /** + * Should handle deep link URLs for the desktop application. + * @param _url - The deep link URL to handle + * @throws Error with "Not implemented" message + * @todo Implement deep link handling using Electron's protocol handler + */ async handleDeepLink(_url: string): Promise { logger.error("handleDeepLink not implemented in Electron platform"); throw new Error("Not implemented"); diff --git a/src/services/platforms/PyWebViewPlatformService.ts b/src/services/platforms/PyWebViewPlatformService.ts index 3e4d88f1..0e118297 100644 --- a/src/services/platforms/PyWebViewPlatformService.ts +++ b/src/services/platforms/PyWebViewPlatformService.ts @@ -1,49 +1,123 @@ import { ImageResult, PlatformService } from "../PlatformService"; import { logger } from "../../utils/logger"; +/** + * Platform service implementation for PyWebView platform. + * Note: This is a placeholder implementation with most methods currently unimplemented. + * Implements the PlatformService interface but throws "Not implemented" errors for most operations. + * + * @remarks + * This service is intended for Python-based desktop applications using pywebview. + * Future implementations should provide: + * - Integration with Python backend file operations + * - System camera access through Python + * - Native system dialogs via pywebview + * - Python-JavaScript bridge functionality + */ export class PyWebViewPlatformService implements PlatformService { + /** + * Reads a file using the Python backend. + * @param _path - Path to the file to read + * @returns Promise that should resolve to file contents + * @throws Error with "Not implemented" message + * @todo Implement file reading through pywebview's Python-JavaScript bridge + */ async readFile(_path: string): Promise { throw new Error("Not implemented"); } + /** + * Writes content to a file using the Python backend. + * @param _path - Path where to write the file + * @param _content - Content to write to the file + * @throws Error with "Not implemented" message + * @todo Implement file writing through pywebview's Python-JavaScript bridge + */ async writeFile(_path: string, _content: string): Promise { throw new Error("Not implemented"); } + /** + * Deletes a file using the Python backend. + * @param _path - Path to the file to delete + * @throws Error with "Not implemented" message + * @todo Implement file deletion through pywebview's Python-JavaScript bridge + */ async deleteFile(_path: string): Promise { throw new Error("Not implemented"); } + /** + * Lists files in the specified directory using the Python backend. + * @param _directory - Path to the directory to list + * @returns Promise that should resolve to array of filenames + * @throws Error with "Not implemented" message + * @todo Implement directory listing through pywebview's Python-JavaScript bridge + */ async listFiles(_directory: string): Promise { throw new Error("Not implemented"); } + /** + * Should open system camera through Python backend. + * @returns Promise that should resolve to captured image data + * @throws Error with "Not implemented" message + * @todo Implement camera access using Python's camera libraries + */ async takePicture(): Promise { logger.error("takePicture not implemented in PyWebView platform"); throw new Error("Not implemented"); } + /** + * Should open system file picker through pywebview. + * @returns Promise that should resolve to selected image data + * @throws Error with "Not implemented" message + * @todo Implement file picker using pywebview's file dialog API + */ async pickImage(): Promise { logger.error("pickImage not implemented in PyWebView platform"); throw new Error("Not implemented"); } + /** + * Checks if running on Capacitor platform. + * @returns false, as this is not Capacitor + */ isCapacitor(): boolean { return false; } + /** + * Checks if running on Electron platform. + * @returns false, as this is not Electron + */ isElectron(): boolean { return false; } + /** + * Checks if running on PyWebView platform. + * @returns true, as this is the PyWebView implementation + */ isPyWebView(): boolean { return true; } + /** + * Checks if running on web platform. + * @returns false, as this is not web + */ isWeb(): boolean { return false; } + /** + * Should handle deep link URLs through the Python backend. + * @param _url - The deep link URL to handle + * @throws Error with "Not implemented" message + * @todo Implement deep link handling using Python's URL handling capabilities + */ async handleDeepLink(_url: string): Promise { logger.error("handleDeepLink not implemented in PyWebView platform"); throw new Error("Not implemented"); diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts index 715d6d2d..9a65278a 100644 --- a/src/services/platforms/WebPlatformService.ts +++ b/src/services/platforms/WebPlatformService.ts @@ -1,23 +1,69 @@ import { ImageResult, PlatformService } from "../PlatformService"; import { logger } from "../../utils/logger"; +/** + * Platform service implementation for web browser platform. + * Implements the PlatformService interface with web-specific functionality. + * + * @remarks + * This service provides web-based implementations for: + * - Image capture using the browser's file input + * - Image selection from local filesystem + * - Image processing and conversion + * + * Note: File system operations are not available in the web platform + * due to browser security restrictions. These methods throw appropriate errors. + */ export class WebPlatformService implements PlatformService { + /** + * Not supported in web platform. + * @param _path - Unused path parameter + * @throws Error indicating file system access is not available + */ async readFile(_path: string): Promise { throw new Error("File system access not available in web platform"); } + /** + * Not supported in web platform. + * @param _path - Unused path parameter + * @param _content - Unused content parameter + * @throws Error indicating file system access is not available + */ async writeFile(_path: string, _content: string): Promise { throw new Error("File system access not available in web platform"); } + /** + * Not supported in web platform. + * @param _path - Unused path parameter + * @throws Error indicating file system access is not available + */ async deleteFile(_path: string): Promise { throw new Error("File system access not available in web platform"); } + /** + * Not supported in web platform. + * @param _directory - Unused directory parameter + * @throws Error indicating file system access is not available + */ async listFiles(_directory: string): Promise { throw new Error("File system access not available in web platform"); } + /** + * Opens a file input dialog configured for camera capture. + * Creates a temporary file input element to access the device camera. + * + * @returns Promise resolving to the captured image data + * @throws Error if image capture fails or no image is selected + * + * @remarks + * Uses the 'capture' attribute to prefer the device camera. + * Falls back to file selection if camera is not available. + * Processes the captured image to ensure consistent format. + */ async takePicture(): Promise { return new Promise((resolve, reject) => { const input = document.createElement("input"); @@ -47,6 +93,17 @@ export class WebPlatformService implements PlatformService { }); } + /** + * Opens a file input dialog for selecting an image file. + * Creates a temporary file input element to access local files. + * + * @returns Promise resolving to the selected image data + * @throws Error if image processing fails or no image is selected + * + * @remarks + * Allows selection of any image file type. + * Processes the selected image to ensure consistent format. + */ async pickImage(): Promise { return new Promise((resolve, reject) => { const input = document.createElement("input"); @@ -75,6 +132,18 @@ export class WebPlatformService implements PlatformService { }); } + /** + * Processes an image file to ensure consistent format. + * Converts the file to a data URL and then to a Blob. + * + * @param file - The image File object to process + * @returns Promise resolving to processed image Blob + * @throws Error if file reading or conversion fails + * + * @remarks + * This method ensures consistent image format across different + * input sources by converting through data URL to Blob. + */ private async processImageFile(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -97,22 +166,45 @@ export class WebPlatformService implements PlatformService { }); } + /** + * Checks if running on Capacitor platform. + * @returns false, as this is not Capacitor + */ isCapacitor(): boolean { return false; } + /** + * Checks if running on Electron platform. + * @returns false, as this is not Electron + */ isElectron(): boolean { return false; } + /** + * Checks if running on PyWebView platform. + * @returns false, as this is not PyWebView + */ isPyWebView(): boolean { return false; } + /** + * Checks if running on web platform. + * @returns true, as this is the web implementation + */ isWeb(): boolean { return true; } + /** + * Handles deep link URLs in the web platform. + * Deep links are handled through URL parameters in the web environment. + * + * @param _url - The deep link URL to handle (unused in web implementation) + * @returns Promise that resolves immediately as web handles URLs naturally + */ async handleDeepLink(_url: string): Promise { // Web platform can handle deep links through URL parameters return Promise.resolve();