Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,17 @@
> **File:** `src/EchoHub.Server/Services/LinkEmbedService.cs`
> **Kind:** class

*Figure: How LinkEmbedService works.*

```mermaid
%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%%
flowchart TB
LinkEmbedService["TryGetEmbedsAsync: ExtractUrls content; if no URLs -> return null. For each URL: call FetchEmbedForUrlAsync -> validate absolute URI, allow http or https, skip private hosts; create CancellationTokenSource using HubConstants, send GET with HttpClient 'OgFetch' and HttpCompletionOption.ResponseHeadersRead; if non-success status -> skip; ensure Content-Type starts with text/html; read limited HTML; parse OG tags; determine title with og:title fallback to <title>; if no title -> skip; else build EmbedDto and add to results. Catch exceptions and LogDebug. Return embeds list or null"]
HubConstants["HubConstants: EmbedFetchTimeoutSeconds, EmbedMaxHtmlBytes, EmbedMaxDescription"]
EmbedDto["EmbedDto: represents successful OG embed data"]

LinkEmbedService -->|"reads timeouts and limits"| HubConstants
LinkEmbedService -->|"creates and adds successful EmbedDto"| EmbedDto
LinkEmbedService -->|"foreach URL (loop)"| LinkEmbedService
```

```csharp
public partial class LinkEmbedService
```


Detects and fetches Open Graph-style embed metadata for any URLs found in a piece of message `content`. Use `LinkEmbedService` (via its `TryGetEmbedsAsync` method) when you want a best-effort, non-throwing attempt to produce [`EmbedDto`](../../EchoHub.Core/DTOs/ChatDtos.cs.md) objects for links inside user messages — for example, to show link previews — and you want network, size and privacy protections applied automatically.
Detects URLs inside a message and attempts to build lightweight Open Graph-style preview data for each. Call `TryGetEmbedsAsync` when you need server-side preview cards for message text (for example, chat or activity feeds); the service will return a list of `EmbedDto` instances for URLs it successfully fetched and parsed, or `null` if no usable embeds were found. The method is resilient: it never throws to callers (failures are logged) and applies several safeguards (URI validation, scheme check, private-host blocking, content-type and size limits, timeout).

## Remarks
`LinkEmbedService` centralizes link-preview logic so callers do not have to implement URL extraction, host-safety checks, HTTP fetching, HTML-size limits, or Open Graph parsing themselves. The public `TryGetEmbedsAsync` method returns `null` when no useful embed data is available (either because no URLs were found or all fetch attempts failed) and never throws; individual fetch failures are caught and logged at debug level. Internally it calls the private `FetchEmbedForUrlAsync` for each URL which enforces absolute `http`/`https` URIs, rejects private hosts via `IsPrivateHost`, uses an `IHttpClientFactory`-created client named `"OgFetch"`, applies a `CancellationTokenSource` timeout (`HubConstants.EmbedFetchTimeoutSeconds`), requires a `text/html` response, bounds the HTML read size (`HubConstants.EmbedMaxHtmlBytes`), extracts Open Graph tags (falling back to the `<title>` tag), decodes HTML entities with `WebUtility.HtmlDecode`, and truncates long descriptions to `HubConstants.EmbedMaxDescriptionLength`.
`LinkEmbedService` is a focused utility for fetching and extracting minimal preview metadata from remote pages. It integrates with `IHttpClientFactory` (expects a named client `"OgFetch"`) and logs issues through the injected `ILogger<LinkEmbedService>`. The implementation defends against common server-side preview hazards: it only allows absolute `http`/`https` URIs, rejects private/internal hosts via `IsPrivateHost`, enforces a read timeout using `HubConstants.EmbedFetchTimeoutSeconds`, and limits the amount of HTML read with `HubConstants.EmbedMaxHtmlBytes`. Parsing is performed by extracting Open Graph tags via `ParseOgTags`, falling back to the `<title>` tag (via `TitleTagRegex`), and optionally parsing a `theme-color` meta tag (via `ParseThemeColor` / `ThemeColorRegex`). Text fields are HTML-decoded and long descriptions are truncated to `HubConstants.EmbedMaxDescriptionLength`.

## Notes
- The service expects an `IHttpClientFactory` client named `"OgFetch"` to be configured; network policy (proxies, handlers) should be applied on that named client rather than relying on this class to set HTTP options.
- Fetching is constrained by time and size: a cancellation timeout (`HubConstants.EmbedFetchTimeoutSeconds`) and a maximum number of HTML bytes (`HubConstants.EmbedMaxHtmlBytes`) are enforced; pages that exceed these limits may yield no embed.
- Only absolute `http`/`https` URLs are considered and private/internal hosts are explicitly ignored by `IsPrivateHost`; the method will return `null` instead of an [`EmbedDto`](../../EchoHub.Core/DTOs/ChatDtos.cs.md) for such URLs.
- Failures during individual URL fetches are swallowed (logged at debug) so `TryGetEmbedsAsync` remains non-throwing for callers — check logs when embeds are unexpectedly missing.
- The service expects a named `IHttpClientFactory` client called `"OgFetch"`; if that client is not registered or misconfigured the fetches will fail and be logged at debug level.
- Fetches are performed sequentially for each URL in `TryGetEmbedsAsync`, so large numbers of links or slow hosts may increase total latency; each fetch is nevertheless bounded by `HubConstants.EmbedFetchTimeoutSeconds`.
- HTML extraction relies on regex-based parsing (`TitleTagRegex`, `ThemeColorRegex`) and the `ParseOgTags` helper; this is intentionally pragmatic but may miss nonstandard or deeply nested metadata. The service also drops non-HTML responses, invalid URIs, non-HTTP(S) schemes, private hosts, and pages that lack any usable `title` or Open Graph title — in all those cases it returns `null` for that URL and proceeds without throwing.
Loading