diff --git a/backend/src/__tests__/galleryOgService.shareImage.test.js b/backend/src/__tests__/galleryOgService.shareImage.test.js index fd5f8b2e..879db273 100644 --- a/backend/src/__tests__/galleryOgService.shareImage.test.js +++ b/backend/src/__tests__/galleryOgService.shareImage.test.js @@ -343,6 +343,8 @@ describe('isSocialCrawler — extended bot coverage (#521)', () => { // 3rd-party preview services used by business-messaging stacks 'LinkPreview/1.0', 'Slack-ImgProxy/1.0', + // Viber link-preview fetcher (#699 follow-up) + 'Mozilla/5.0 (compatible; Viber)', ]; for (const ua of knownBots) { expect(isSocialCrawler(ua)).toBe(true); diff --git a/backend/src/services/galleryOgService.js b/backend/src/services/galleryOgService.js index a1ed8ef9..038c42f7 100644 --- a/backend/src/services/galleryOgService.js +++ b/backend/src/services/galleryOgService.js @@ -34,7 +34,11 @@ const SOCIAL_CRAWLER_PATTERNS = [ // messaging stacks (Twilio, LinkPreview.net, etc.). Match the // canonical lowercase substring; the /i flag handles case. /LinkPreview/i, - /Slack-ImgProxy/i + /Slack-ImgProxy/i, + // Viber's link-preview fetcher — was never detected, so shared links + // showed no rich preview in Viber (#699 follow-up). Keep in sync with the + // UA list in frontend/nginx.conf. + /Viber/i ]; function isSocialCrawler(userAgent) { diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 45175a03..c0f70c57 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -237,12 +237,22 @@ server { # Facebook, Slack, Twitter, etc.) don't run JS, so the SPA's client-side # meta tags never reach them. Route those UAs to backend's /og handler # via internal rewrite; humans fall through to the SPA via try_files. - location ~ ^/gallery/(?[A-Za-z0-9_-]+)(?:/[^/]+)?/?$ { + # {0,2} extra path segments so the deeper gallery share shapes match too: + # /gallery/ (public / share-token link) + # /gallery//client-access (client-access; token is in ?query) + # /gallery//show/ (slideshow; token is a PATH segment) + # The slideshow shape has TWO extra segments (show + token) — the old + # single-segment `(?:/[^/]+)?` never matched it, so slideshow links fell + # through to the SPA and got only the generic site-wide OG (#699 follow-up). + # NB: the regex is QUOTED because the {0,2} quantifier's braces would + # otherwise be parsed as nginx config block delimiters. + location ~ "^/gallery/(?[A-Za-z0-9_-]+)(?:/[^/]+){0,2}/?$" { # Keep this list in sync with SOCIAL_CRAWLER_PATTERNS in # backend/src/services/galleryOgService.js. WhatsAppBot / wa-bot # and LinkPreview / Slack-ImgProxy added in #521 to catch # business-API preview fetchers that aren't the main WhatsApp app. - if ($http_user_agent ~* "(facebookexternalhit|facebot|Twitterbot|WhatsApp|WhatsAppBot|wa-bot|Slackbot|Slack-ImgProxy|TelegramBot|SkypeUriPreview|Discordbot|LinkedInBot|Pinterest|vkShare|redditbot|Embedly|iframely|Snapchat|Applebot|Mastodon|Bluesky|OpenGraph|LinkPreview)") { + # Viber added in #699 follow-up (its preview fetcher was never detected). + if ($http_user_agent ~* "(facebookexternalhit|facebot|Twitterbot|WhatsApp|WhatsAppBot|wa-bot|Slackbot|Slack-ImgProxy|TelegramBot|SkypeUriPreview|Discordbot|LinkedInBot|Pinterest|vkShare|redditbot|Embedly|iframely|Snapchat|Applebot|Mastodon|Bluesky|OpenGraph|LinkPreview|Viber)") { rewrite ^ /og/gallery/$gallery_slug last; } try_files $uri $uri/ /index.html; @@ -260,6 +270,22 @@ server { proxy_set_header X-Forwarded-Proto $real_proto; } + # Branded URL shortener (#699). The backend `/s/` route both + # 302-redirects humans to the target gallery AND server-renders OG for + # social crawlers. Without this proxy, `/s/...` fell through to the SPA + # (which has no /s/ route) — so branded short links were dead for humans + # and crawlers alike. `^~` beats the regex SPA fallback below. The backend + # does its own UA detection, so no crawler `if` is needed here. + location ^~ /s/ { + set $backend_upstream backend; + proxy_pass http://$backend_upstream:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $real_proto; + } + # SPA fallback location / { try_files $uri $uri/ /index.html;