From 4369ba955a31cecd313ac93f918958a995a8cd10 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Wed, 16 Sep 2026 13:22:00 +0200 Subject: [PATCH] feat(avatar): support guestFallback and version query params - provide a full match the backend's getAvatar() method: - `guestFallback` param (checked stable32 and newer) - `v` param (added in nextcloud/server#64295, not backported atm) - copy and adjust test coverage from nextcloud/vue version for missing gap Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Maksim Sukharev --- lib/avatar.ts | 28 ++++++++++++++++++++++++++-- tests/avatar.spec.ts | 18 ++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/avatar.ts b/lib/avatar.ts index d710fe96..e4a2577a 100644 --- a/lib/avatar.ts +++ b/lib/avatar.ts @@ -14,7 +14,7 @@ export interface AvatarUrlOptions { isDarkTheme?: boolean /** - * If the given user is is a guest user. + * If the given user is a guest user. * This is needed as guest users use a different API endpoint. * * @default false @@ -28,6 +28,19 @@ export interface AvatarUrlOptions { * @default 64 */ size?: 64 | 512 + + /** + * Fall back to a generated guest avatar if no avatar is found for the user. + * Has no effect when `isGuestUser` is `true`. + * + * @default false + */ + guestFallback?: boolean + + /** + * Cache-busting value appended as a `v` query parameter. + */ + version?: string | number } /** @@ -51,8 +64,19 @@ export function generateAvatarUrl(user: string, options?: AvatarUrlOptions): str ? '/dark' : '' - return generateUrl(`/avatar${guestUrl}/{user}/{size}${themeUrl}`, { + const url = generateUrl(`/avatar${guestUrl}/{user}/{size}${themeUrl}`, { user, size, }) + + const query = new URLSearchParams() + if (options?.guestFallback) { + query.set('guestFallback', 'true') + } + if (options?.version !== undefined) { + query.set('v', String(options.version)) + } + + const queryString = query.toString() + return queryString ? `${url}?${queryString}` : url } diff --git a/tests/avatar.spec.ts b/tests/avatar.spec.ts index 8e298123..d9f0dbc0 100644 --- a/tests/avatar.spec.ts +++ b/tests/avatar.spec.ts @@ -37,4 +37,22 @@ describe('generateAvatarUrl', () => { expect(generateAvatarUrl('alice', { isGuestUser: true, isDarkTheme: true })).toBe('//index.php/avatar/guest/alice/64/dark') expect(generateAvatarUrl('john', { isGuestUser: true, isDarkTheme: true, size: 512 })).toBe('//index.php/avatar/guest/john/512/dark') }) + + it('should append guestFallback query parameter if set', () => { + expect(generateAvatarUrl('alice', { guestFallback: true })).toBe('//index.php/avatar/alice/64?guestFallback=true') + expect(generateAvatarUrl('alice', { guestFallback: false })).toBe('//index.php/avatar/alice/64') + }) + + it('should append v query parameter if version is set', () => { + expect(generateAvatarUrl('alice', { version: 3 })).toBe('//index.php/avatar/alice/64?v=3') + expect(generateAvatarUrl('alice', { version: 'abc' })).toBe('//index.php/avatar/alice/64?v=abc') + }) + + it('should append both query parameters when combined', () => { + expect(generateAvatarUrl('alice', { guestFallback: true, version: 3 })).toBe('//index.php/avatar/alice/64?guestFallback=true&v=3') + }) + + it('should still append guestFallback for guest avatars even though the backend ignores it', () => { + expect(generateAvatarUrl('alice', { isGuestUser: true, guestFallback: true })).toBe('//index.php/avatar/guest/alice/64?guestFallback=true') + }) })