diff --git a/toju-app/src/app/domains/chat/README.md b/toju-app/src/app/domains/chat/README.md index c8f7d9c..e8d7c3e 100644 --- a/toju-app/src/app/domains/chat/README.md +++ b/toju-app/src/app/domains/chat/README.md @@ -109,7 +109,7 @@ sequenceDiagram ## GIF integration -`KlipyService` checks availability on the active server, then proxies search requests through the server API. Images are rendered via an image proxy endpoint to avoid mixed-content issues. +`KlipyService` checks availability on the active server, then proxies search requests through the server API. Rendered remote images now attempt a direct load first and only fall back to the image proxy after the browser reports a load failure, which is the practical approximation of a CORS or mixed-content fallback path in the renderer. ```mermaid graph LR diff --git a/toju-app/src/app/domains/chat/application/klipy.service.ts b/toju-app/src/app/domains/chat/application/klipy.service.ts index 4dfcfc5..e789d53 100644 --- a/toju-app/src/app/domains/chat/application/klipy.service.ts +++ b/toju-app/src/app/domains/chat/application/klipy.service.ts @@ -135,6 +135,10 @@ export class KlipyService { } buildRenderableImageUrl(url: string): string { + return this.normalizeMediaUrl(url); + } + + buildImageProxyUrl(url: string): string { const trimmed = this.normalizeMediaUrl(url); if (!trimmed) diff --git a/toju-app/src/app/domains/chat/feature/chat-image-proxy-fallback.directive.ts b/toju-app/src/app/domains/chat/feature/chat-image-proxy-fallback.directive.ts new file mode 100644 index 0000000..d73a818 --- /dev/null +++ b/toju-app/src/app/domains/chat/feature/chat-image-proxy-fallback.directive.ts @@ -0,0 +1,50 @@ +import { + Directive, + HostBinding, + HostListener, + effect, + inject, + input, + signal +} from '@angular/core'; +import { KlipyService } from '../application/klipy.service'; + +@Directive({ + selector: 'img[appChatImageProxyFallback]', + standalone: true +}) +export class ChatImageProxyFallbackDirective { + readonly sourceUrl = input('', { alias: 'appChatImageProxyFallback' }); + + private readonly klipy = inject(KlipyService); + private readonly renderedSource = signal(''); + private hasAppliedProxyFallback = false; + + constructor() { + effect(() => { + this.hasAppliedProxyFallback = false; + this.renderedSource.set(this.klipy.buildRenderableImageUrl(this.sourceUrl())); + }); + } + + @HostBinding('src') + get src(): string { + return this.renderedSource(); + } + + @HostListener('error') + handleError(): void { + if (this.hasAppliedProxyFallback) { + return; + } + + const proxyUrl = this.klipy.buildImageProxyUrl(this.sourceUrl()); + + if (!proxyUrl || proxyUrl === this.renderedSource()) { + return; + } + + this.hasAppliedProxyFallback = true; + this.renderedSource.set(proxyUrl); + } +} diff --git a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-composer/chat-message-composer.component.html b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-composer/chat-message-composer.component.html index e298628..ce800e2 100644 --- a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-composer/chat-message-composer.component.html +++ b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-composer/chat-message-composer.component.html @@ -206,7 +206,7 @@