[APP-2160] Centralize known publisher lookup with strict host matching

Move the publisher list and host detection into a single publishers.ts
module so both PublicationMetaRow and the Subscribe button consult the
same source. Match exact hosts and proper subdomains only, rejecting
lookalikes like evilleaflet.pub.
This commit is contained in:
Eric Bailey
2026-05-22 15:04:36 -05:00
parent deb31b2eb9
commit 827bb42d60
4 changed files with 114 additions and 44 deletions
@@ -1,4 +1,4 @@
import {Fragment, type ReactNode, useMemo} from 'react'
import {Fragment, type ReactNode} from 'react'
import {View} from 'react-native'
import {type AppBskyEmbedExternal} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
@@ -8,6 +8,7 @@ import {toNiceDomain} from '#/lib/strings/url-helpers'
import {useProfileQuery} from '#/state/queries/profile'
import {atoms as a, useTheme} from '#/alf'
import {InlineLinkText} from '#/components/Link'
import {matchStandardSitePublisher} from '#/components/Post/Embed/StandardSiteEmbed/publishers'
import {Text} from '#/components/Typography'
export function PublicationMetaRow({
@@ -25,18 +26,7 @@ export function PublicationMetaRow({
const {t: l} = useLingui()
const profileQuery = useProfileQuery({did: author.did ?? undefined})
const handle = author.did ? profileQuery.data?.handle : undefined
const highlightedPublisher = useMemo(() => {
try {
const u = new URL(view.source?.uri || '')
return (
u.host.endsWith('leaflet.pub') ||
u.host.endsWith('pckt.blog') ||
u.host.endsWith('offprint.app')
)
} catch (e) {
return false
}
}, [view])
const highlightedPublisher = !!matchStandardSitePublisher(view)
const metaTextStyle = [
a.text_xs,
@@ -14,14 +14,12 @@ import {ButtonIcon, ButtonText} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Clock_Stroke2_Corner0_Rounded as Clock} from '#/components/icons/Clock'
import {Leaflet} from '#/components/icons/community/Leaflet'
import {Offprint} from '#/components/icons/community/Offprint'
import {Pckt} from '#/components/icons/community/Pckt'
import {StandardSite} from '#/components/icons/community/StandardSite'
import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {Link} from '#/components/Link'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {PublicationMetaRow} from '#/components/Post/Embed/StandardSiteEmbed/PublicationMetaRow'
import {matchStandardSitePublisher} from '#/components/Post/Embed/StandardSiteEmbed/publishers'
import {StandardSiteThemeProvider} from '#/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider'
import {isStandardSitePublicationEmbed} from '#/components/Post/Embed/StandardSiteEmbed/utils'
import {Text} from '#/components/Typography'
@@ -37,33 +35,6 @@ const PUBLICATION_AVATAR_STYLE = {
borderRadius: a.rounded_sm.borderRadius,
}
export function useStandardSitePublisherConfig(
view: AppBskyEmbedExternal.ViewExternal,
) {
try {
const u = new URL(view.source?.uri || '')
if (u.host.endsWith('leaflet.pub')) {
return {
name: 'Leaflet',
Icon: Leaflet,
}
} else if (u.host.endsWith('pckt.blog')) {
return {
name: 'pckt',
Icon: Pckt,
}
} else if (u.host.endsWith('offprint.app')) {
return {
name: 'Offprint',
Icon: Offprint,
}
}
return null
} catch (e) {
return null
}
}
export const StandardSiteEmbed = ({
view,
onOpen,
@@ -431,7 +402,7 @@ export function SubscribeButton({
style?: StyleProp<ViewStyle>
}) {
const {t: l} = useLingui()
const highlightedPublisher = useStandardSitePublisherConfig(view)
const highlightedPublisher = matchStandardSitePublisher(view)
const cta = highlightedPublisher
? l`Subscribe on ${highlightedPublisher.name}`
: l`View publication`
@@ -0,0 +1,39 @@
import {type AppBskyEmbedExternal} from '@atproto/api'
import {Leaflet} from '#/components/icons/community/Leaflet'
import {Offprint} from '#/components/icons/community/Offprint'
import {Pckt} from '#/components/icons/community/Pckt'
export type StandardSitePublisher = {
host: string
name: string
Icon: typeof Leaflet
}
const STANDARD_SITE_PUBLISHERS: StandardSitePublisher[] = [
{host: 'leaflet.pub', name: 'Leaflet', Icon: Leaflet},
{host: 'pckt.blog', name: 'pckt', Icon: Pckt},
{host: 'offprint.app', name: 'Offprint', Icon: Offprint},
]
export function getStandardSitePublisherHost(
view: AppBskyEmbedExternal.ViewExternal,
): string | null {
try {
return new URL(view.source?.uri || '').host
} catch {
return null
}
}
export function hostMatches(host: string, target: string): boolean {
return host === target || host.endsWith('.' + target)
}
export function matchStandardSitePublisher(
view: AppBskyEmbedExternal.ViewExternal,
): StandardSitePublisher | null {
const host = getStandardSitePublisherHost(view)
if (!host) return null
return STANDARD_SITE_PUBLISHERS.find(p => hostMatches(host, p.host)) ?? null
}
@@ -0,0 +1,70 @@
import {type AppBskyEmbedExternal} from '@atproto/api'
import {isStandardSiteEmbed, isStandardSitePublicationEmbed} from './utils'
function makeView(
partial: Record<string, unknown>,
): AppBskyEmbedExternal.ViewExternal {
return {
uri: 'https://example.com/post',
title: 'title',
description: 'description',
...partial,
}
}
describe('isStandardSiteEmbed', () => {
it('returns true when any associated ref is in the site.standard.* namespace', () => {
const view = makeView({
associatedRefs: [{uri: 'at://did:plc:abc/site.standard.publication/foo'}],
})
expect(isStandardSiteEmbed(view)).toBe(true)
})
it('returns false when no associated refs are in the site.standard.* namespace', () => {
const view = makeView({
associatedRefs: [{uri: 'at://did:plc:abc/app.bsky.feed.post/foo'}],
})
expect(isStandardSiteEmbed(view)).toBe(false)
})
it('returns falsy when associatedRefs is missing', () => {
expect(isStandardSiteEmbed(makeView({}))).toBeFalsy()
})
})
describe('isStandardSitePublicationEmbed', () => {
it('returns true with at least one publication ref and no document refs', () => {
const view = makeView({
associatedRefs: [{uri: 'at://did:plc:abc/site.standard.publication/foo'}],
})
expect(isStandardSitePublicationEmbed(view)).toBe(true)
})
it('returns false when any ref is a document', () => {
const view = makeView({
associatedRefs: [
{uri: 'at://did:plc:abc/site.standard.publication/foo'},
{uri: 'at://did:plc:abc/site.standard.document/bar'},
],
})
expect(isStandardSitePublicationEmbed(view)).toBe(false)
})
it('returns false when there are no publication refs', () => {
const view = makeView({
associatedRefs: [{uri: 'at://did:plc:abc/site.standard.other/foo'}],
})
expect(isStandardSitePublicationEmbed(view)).toBe(false)
})
it('returns falsy for an empty associatedRefs array', () => {
expect(isStandardSitePublicationEmbed(makeView({associatedRefs: []}))).toBe(
false,
)
})
it('returns falsy when associatedRefs is missing', () => {
expect(isStandardSitePublicationEmbed(makeView({}))).toBeFalsy()
})
})