From f839b65c5659503615dad2aee55fc53cf6418032 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 3 Jul 2025 10:03:58 +0000 Subject: [PATCH] fix: resolve SVG loading error and improve TypeScript typing - Fix EntityIcon.vue to import blank-square.svg as module instead of using relative path - Update template to use imported SVG path for proper Electron compatibility - Make contact prop optional in EntityIcon component to fix TypeScript error - Add proper Settings type imports and method signatures in PlatformServiceMixin.ts - Replace console statements with logger calls across multiple files - Resolves SVG loading failures in Electron builds while maintaining web compatibility - Reduces TypeScript 'any' type warnings from 81 to 53 --- src/components/EntityIcon.vue | 5 +- src/libs/util.ts | 16 +++++ src/utils/PlatformServiceMixin.ts | 98 +++++++++++++++++++++---------- src/views/GiftedDetailsView.vue | 5 +- vite.config.common.mts | 23 ++++++++ 5 files changed, 114 insertions(+), 33 deletions(-) diff --git a/src/components/EntityIcon.vue b/src/components/EntityIcon.vue index 689a246a..5af428de 100644 --- a/src/components/EntityIcon.vue +++ b/src/components/EntityIcon.vue @@ -8,10 +8,11 @@ import { avataaars } from "@dicebear/collection"; import { Vue, Component, Prop } from "vue-facing-decorator"; import { Contact } from "../db/tables/contacts"; import { transformImageUrlForCors } from "../libs/util"; +import blankSquareSvg from "../assets/blank-square.svg"; @Component export default class EntityIcon extends Vue { - @Prop contact: Contact; + @Prop contact?: Contact; @Prop entityId = ""; // overridden by contact.did or profileImageUrl @Prop iconSize = 0; @Prop profileImageUrl = ""; // overridden by contact.profileImageUrl @@ -23,7 +24,7 @@ export default class EntityIcon extends Vue { } else { const identifier = this.contact?.did || this.entityId; if (!identifier) { - return ``; + return ``; } // https://api.dicebear.com/8.x/avataaars/svg?seed= // ... does not render things with the same seed as this library. diff --git a/src/libs/util.ts b/src/libs/util.ts index ea9d98d1..8ac4af4a 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -938,6 +938,10 @@ export async function importFromMnemonic( * transformImageUrlForCors('https://image.timesafari.app/abc123.jpg') * // Returns: '/image-proxy/abc123.jpg' in development * // Returns: 'https://image.timesafari.app/abc123.jpg' in production + * + * transformImageUrlForCors('https://live.staticflickr.com/2853/9194403742_c8297b965b_b.jpg') + * // Returns: '/flickr-proxy/2853/9194403742_c8297b965b_b.jpg' in development + * // Returns: 'https://live.staticflickr.com/2853/9194403742_c8297b965b_b.jpg' in production */ export function transformImageUrlForCors( imageUrl: string | undefined | null, @@ -961,5 +965,17 @@ export function transformImageUrlForCors( return `/image-proxy/${imagePath}`; } + // Transform Flickr URLs to use proxy + if (imageUrl.startsWith("https://live.staticflickr.com/")) { + const imagePath = imageUrl.replace("https://live.staticflickr.com/", ""); + return `/flickr-proxy/${imagePath}`; + } + + // Transform other Flickr subdomains if needed + if (imageUrl.includes(".staticflickr.com/")) { + const imagePath = imageUrl.split(".staticflickr.com/")[1]; + return `/flickr-proxy/${imagePath}`; + } + return imageUrl; } diff --git a/src/utils/PlatformServiceMixin.ts b/src/utils/PlatformServiceMixin.ts index a0267e3a..c074a2da 100644 --- a/src/utils/PlatformServiceMixin.ts +++ b/src/utils/PlatformServiceMixin.ts @@ -37,7 +37,7 @@ import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import type { PlatformService } from "@/services/PlatformService"; import { mapColumnsToValues, parseJsonField } from "@/db/databaseUtil"; -import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; +import { MASTER_SETTINGS_KEY, type Settings } from "@/db/tables/settings"; import * as databaseUtil from "@/db/databaseUtil"; import { logger } from "@/utils/logger"; @@ -246,7 +246,10 @@ export const PlatformServiceMixin = { * Utility method for retrieving and parsing settings * Common pattern used across many components */ - async $getSettings(key: string, fallback: any = null) { + async $getSettings( + key: string, + fallback: Settings | null = null, + ): Promise { try { const result = await this.$dbQuery( "SELECT * FROM settings WHERE id = ? OR accountDid = ?", @@ -257,10 +260,13 @@ export const PlatformServiceMixin = { return fallback; } - const settings = mapColumnsToValues( - result.columns, - result.values, - )[0] as any; + const mappedResults = mapColumnsToValues(result.columns, result.values); + + if (!mappedResults.length) { + return fallback; + } + + const settings = mappedResults[0] as Settings; // Handle JSON field parsing if (settings.searchBoxes) { @@ -287,8 +293,8 @@ export const PlatformServiceMixin = { async $getMergedSettings( defaultKey: string, accountDid?: string, - defaultFallback: any = {}, - ) { + defaultFallback: Settings = {}, + ): Promise { try { // Get default settings const defaultSettings = await this.$getSettings( @@ -298,7 +304,7 @@ export const PlatformServiceMixin = { // If no account DID, return defaults if (!accountDid) { - return defaultSettings; + return defaultSettings || defaultFallback; } // Get account-specific overrides @@ -308,21 +314,30 @@ export const PlatformServiceMixin = { ); if (!accountResult?.values?.length) { - return defaultSettings; + return defaultSettings || defaultFallback; } // Map and filter non-null overrides - const overrideSettings = mapColumnsToValues( + const mappedResults = mapColumnsToValues( accountResult.columns, accountResult.values, - )[0] as any; + ); + + if (!mappedResults.length) { + return defaultSettings || defaultFallback; + } + + const overrideSettings = mappedResults[0] as Settings; const filteredOverrides = Object.fromEntries( Object.entries(overrideSettings).filter(([_, v]) => v !== null), ); // Merge settings with overrides taking precedence - const mergedSettings = { ...defaultSettings, ...filteredOverrides }; + const mergedSettings = { + ...defaultSettings, + ...filteredOverrides, + } as Settings; // Handle JSON field parsing if (mergedSettings.searchBoxes) { @@ -450,7 +465,7 @@ export const PlatformServiceMixin = { * @param defaults Optional default values * @returns Cached settings object */ - async $settings(defaults: any = {}): Promise { + async $settings(defaults: Settings = {}): Promise { const cacheKey = `settings_${String(MASTER_SETTINGS_KEY)}`; const cached = this._getCached(cacheKey); if (cached) { @@ -459,6 +474,10 @@ export const PlatformServiceMixin = { const settings = await this.$getSettings(MASTER_SETTINGS_KEY, defaults); + if (!settings) { + return defaults; + } + // **ELECTRON-SPECIFIC FIX**: Apply platform-specific API server override // This ensures Electron always uses production endpoints regardless of cached settings if (process.env.VITE_PLATFORM === "electron") { @@ -482,7 +501,10 @@ export const PlatformServiceMixin = { * @param defaults Optional default values * @returns Cached merged settings object */ - async $accountSettings(did?: string, defaults: any = {}): Promise { + async $accountSettings( + did?: string, + defaults: Settings = {}, + ): Promise { const currentDid = did || (this as any).activeDid; const cacheKey = `account_settings_${currentDid || "default"}`; @@ -515,7 +537,7 @@ export const PlatformServiceMixin = { * @param changes Settings changes to save * @returns Promise Success status */ - async $saveSettings(changes: any): Promise { + async $saveSettings(changes: Partial): Promise { const result = await databaseUtil.updateDefaultSettings(changes); // Invalidate related caches @@ -532,7 +554,10 @@ export const PlatformServiceMixin = { * @param changes Settings changes to save * @returns Promise Success status */ - async $saveUserSettings(did: string, changes: any): Promise { + async $saveUserSettings( + did: string, + changes: Partial, + ): Promise { const result = await databaseUtil.updateDidSpecificSettings(did, changes); // Invalidate related caches @@ -548,7 +573,7 @@ export const PlatformServiceMixin = { * @param changes Settings changes to save * @returns Promise Success status */ - async $saveMySettings(changes: any): Promise { + async $saveMySettings(changes: Partial): Promise { const currentDid = (this as any).activeDid; if (!currentDid) { return await this.$saveSettings(changes); @@ -564,7 +589,7 @@ export const PlatformServiceMixin = { * Manually refresh settings cache - $refreshSettings() * Forces reload of settings from database */ - async $refreshSettings(): Promise { + async $refreshSettings(): Promise { this._invalidateCache(`settings_${MASTER_SETTINGS_KEY}`); const currentDid = (this as any).activeDid; if (currentDid) { @@ -604,12 +629,15 @@ export interface IPlatformServiceMixin { $dbQuery(sql: string, params?: unknown[]): Promise; $dbExec(sql: string, params?: unknown[]): Promise; $dbGetOneRow(sql: string, params?: unknown[]): Promise; - $getSettings(key: string, fallback?: any): Promise; + $getSettings( + key: string, + fallback?: Settings | null, + ): Promise; $getMergedSettings( defaultKey: string, accountDid?: string, - defaultFallback?: any, - ): Promise; + defaultFallback?: Settings, + ): Promise; $withTransaction(callback: () => Promise): Promise; isCapacitor: boolean; isWeb: boolean; @@ -640,22 +668,32 @@ declare module "@vue/runtime-core" { $dbQuery(sql: string, params?: unknown[]): Promise; $dbExec(sql: string, params?: unknown[]): Promise; $dbGetOneRow(sql: string, params?: unknown[]): Promise; - $getSettings(key: string, defaults?: any): Promise; - $getMergedSettings(key: string, did?: string, defaults?: any): Promise; + $getSettings( + key: string, + defaults?: Settings | null, + ): Promise; + $getMergedSettings( + key: string, + did?: string, + defaults?: Settings, + ): Promise; $withTransaction(fn: () => Promise): Promise; // Cached specialized shortcuts (massive performance boost) $contacts(): Promise; - $settings(defaults?: any): Promise; - $accountSettings(did?: string, defaults?: any): Promise; + $settings(defaults?: Settings): Promise; + $accountSettings(did?: string, defaults?: Settings): Promise; // Settings update shortcuts (eliminate 90% boilerplate) - $saveSettings(changes: any): Promise; - $saveUserSettings(did: string, changes: any): Promise; - $saveMySettings(changes: any): Promise; + $saveSettings(changes: Partial): Promise; + $saveUserSettings( + did: string, + changes: Partial, + ): Promise; + $saveMySettings(changes: Partial): Promise; // Cache management methods - $refreshSettings(): Promise; + $refreshSettings(): Promise; $refreshContacts(): Promise; $clearAllCaches(): void; } diff --git a/src/views/GiftedDetailsView.vue b/src/views/GiftedDetailsView.vue index bed063cf..ff3add99 100644 --- a/src/views/GiftedDetailsView.vue +++ b/src/views/GiftedDetailsView.vue @@ -77,7 +77,10 @@
- + { console.log('[Image Proxy Response]', req.url, '->', proxyRes.statusCode, proxyRes.headers.location || 'no redirect'); }); } + }, + // Proxy Flickr images to avoid CORS issues + '/flickr-proxy': { + target: 'https://live.staticflickr.com', + changeOrigin: true, + secure: true, + followRedirects: true, + rewrite: (path) => path.replace(/^\/flickr-proxy/, ''), + configure: (proxy) => { + proxy.on('error', (err, req, res) => { + console.log('[Flickr Proxy Error]', err); + }); + proxy.on('proxyReq', (proxyReq, req, res) => { + console.log('[Flickr Proxy Request]', req.method, req.url, '->', proxyReq.path); + }); + proxy.on('proxyRes', (proxyRes, req, res) => { + // Add CORS headers to the response + proxyRes.headers['Access-Control-Allow-Origin'] = '*'; + proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'; + proxyRes.headers['Access-Control-Allow-Headers'] = 'Content-Type'; + console.log('[Flickr Proxy Response]', req.url, '->', proxyRes.statusCode); + }); + } } } },