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);
+ });
+ }
}
}
},