Update config and gates
This commit is contained in:
@@ -12,7 +12,6 @@ import {Admonition} from '#/components/Admonition'
|
|||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
import * as Select from '#/components/Select'
|
import * as Select from '#/components/Select'
|
||||||
@@ -140,26 +139,13 @@ function DialogInner({profile}: {profile: bsky.profile.AnyProfileView}) {
|
|||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
</View>
|
</View>
|
||||||
{(liveLinkError || linkMetaError) && (
|
{(liveLinkError || linkMetaError) && (
|
||||||
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
|
<Admonition type="error">
|
||||||
<WarningIcon
|
|
||||||
style={[{color: t.palette.negative_500}]}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
a.text_sm,
|
|
||||||
a.leading_snug,
|
|
||||||
a.flex_1,
|
|
||||||
a.font_semi_bold,
|
|
||||||
{color: t.palette.negative_500},
|
|
||||||
]}>
|
|
||||||
{liveLinkError ? (
|
{liveLinkError ? (
|
||||||
<Trans>This is not a valid link</Trans>
|
<Trans>This is not a valid link</Trans>
|
||||||
) : (
|
) : (
|
||||||
cleanError(linkMetaError)
|
cleanError(linkMetaError)
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Admonition>
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LinkPreview linkMeta={linkMeta} loading={linkMetaLoading} />
|
<LinkPreview linkMeta={linkMeta} loading={linkMetaLoading} />
|
||||||
|
|||||||
@@ -19,9 +19,23 @@ import {useAgent, useSession} from '#/state/session'
|
|||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {useDialogContext} from '#/components/Dialog'
|
import {useDialogContext} from '#/components/Dialog'
|
||||||
|
|
||||||
|
const serviceUrlToNameMap: Record<string, string> = {
|
||||||
|
'twitch.tv': 'Twitch',
|
||||||
|
'www.twitch.tv': 'Twitch',
|
||||||
|
'youtube.com': 'YouTube',
|
||||||
|
'www.youtube.com': 'YouTube',
|
||||||
|
'youtu.be': 'YouTube',
|
||||||
|
'nba.com': 'NBA',
|
||||||
|
'www.nba.com': 'NBA',
|
||||||
|
'nba.smart.link': 'nba.smart.link',
|
||||||
|
'espn.com': 'ESPN',
|
||||||
|
'www.espn.com': 'ESPN',
|
||||||
|
'stream.place': 'Streamplace',
|
||||||
|
'skylight.social': 'Skylight',
|
||||||
|
}
|
||||||
|
|
||||||
export function useLiveLinkMetaQuery(url: string | null) {
|
export function useLiveLinkMetaQuery(url: string | null) {
|
||||||
const liveNowConfig = useLiveNowConfig()
|
const liveNowConfig = useLiveNowConfig()
|
||||||
const {currentAccount} = useSession()
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
@@ -30,13 +44,18 @@ export function useLiveLinkMetaQuery(url: string | null) {
|
|||||||
queryKey: ['link-meta', url],
|
queryKey: ['link-meta', url],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!url) return undefined
|
if (!url) return undefined
|
||||||
const config = liveNowConfig.find(cfg => cfg.did === currentAccount?.did)
|
|
||||||
|
|
||||||
if (!config) throw new Error(_(msg`You are not allowed to go live`))
|
|
||||||
|
|
||||||
const urlp = new URL(url)
|
const urlp = new URL(url)
|
||||||
if (!config.domains.includes(urlp.hostname)) {
|
if (!liveNowConfig.allowedDomains.includes(urlp.hostname)) {
|
||||||
throw new Error(_(msg`${urlp.hostname} is not a valid URL`))
|
const services = Array.from(
|
||||||
|
new Set(
|
||||||
|
liveNowConfig.allowedDomains.map(d => serviceUrlToNameMap[d] || d),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
_(
|
||||||
|
msg`This service is not supported while the Live feature is in beta. Allowed services: ${services.join(', ')}.`,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getLinkMeta(agent, url)
|
return await getLinkMeta(agent, url)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import {isAfter, parseISO} from 'date-fns'
|
import {isAfter, parseISO} from 'date-fns'
|
||||||
|
|
||||||
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {useLiveNowConfig} from '#/state/service-config'
|
import {type LiveNowConfig, useLiveNowConfig} from '#/state/service-config'
|
||||||
import {useTickEveryMinute} from '#/state/shell'
|
import {useTickEveryMinute} from '#/state/shell'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
|||||||
tick! // revalidate every minute
|
tick! // revalidate every minute
|
||||||
|
|
||||||
if (shadowed && 'status' in shadowed && shadowed.status) {
|
if (shadowed && 'status' in shadowed && shadowed.status) {
|
||||||
const isValid = validateStatus(shadowed.did, shadowed.status, config)
|
const isValid = validateStatus(shadowed.status, config)
|
||||||
const isDisabled = shadowed.status.isDisabled || false
|
const isDisabled = shadowed.status.isDisabled || false
|
||||||
const isActive = isStatusStillActive(shadowed.status.expiresAt)
|
const isActive = isStatusStillActive(shadowed.status.expiresAt)
|
||||||
if (isValid && !isDisabled && isActive) {
|
if (isValid && !isDisabled && isActive) {
|
||||||
@@ -65,19 +65,14 @@ export function isStatusStillActive(timeStr: string | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateStatus(
|
export function validateStatus(
|
||||||
did: string,
|
|
||||||
status: AppBskyActorDefs.StatusView,
|
status: AppBskyActorDefs.StatusView,
|
||||||
config: {did: string; domains: string[]}[],
|
config: LiveNowConfig,
|
||||||
) {
|
) {
|
||||||
if (status.status !== 'app.bsky.actor.status#live') return false
|
if (status.status !== 'app.bsky.actor.status#live') return false
|
||||||
const sources = config.find(cfg => cfg.did === did)
|
|
||||||
if (!sources) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (AppBskyEmbedExternal.isView(status.embed)) {
|
if (AppBskyEmbedExternal.isView(status.embed)) {
|
||||||
const url = new URL(status.embed.external.uri)
|
const url = new URL(status.embed.external.uri)
|
||||||
return sources.domains.includes(url.hostname)
|
return config.allowedDomains.includes(url.hostname)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type Gate =
|
|||||||
| 'disable_settings_find_contacts'
|
| 'disable_settings_find_contacts'
|
||||||
| 'explore_show_suggested_feeds'
|
| 'explore_show_suggested_feeds'
|
||||||
| 'feed_reply_button_open_thread'
|
| 'feed_reply_button_open_thread'
|
||||||
|
| 'live_now_beta'
|
||||||
| 'old_postonboarding'
|
| 'old_postonboarding'
|
||||||
| 'onboarding_add_video_feed'
|
| 'onboarding_add_video_feed'
|
||||||
| 'onboarding_suggested_starterpacks'
|
| 'onboarding_suggested_starterpacks'
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import {createContext, useContext, useMemo} from 'react'
|
import {createContext, useContext, useMemo} from 'react'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {useLanguagePrefs} from '#/state/preferences/languages'
|
import {useLanguagePrefs} from '#/state/preferences/languages'
|
||||||
import {useServiceConfigQuery} from '#/state/queries/service-config'
|
import {useServiceConfigQuery} from '#/state/queries/service-config'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {IS_DEV} from '#/env'
|
||||||
import {device} from '#/storage'
|
import {device} from '#/storage'
|
||||||
|
|
||||||
type TrendingContext = {
|
type TrendingContext = {
|
||||||
@@ -18,7 +21,7 @@ const TrendingContext = createContext<TrendingContext>({
|
|||||||
})
|
})
|
||||||
TrendingContext.displayName = 'TrendingContext'
|
TrendingContext.displayName = 'TrendingContext'
|
||||||
|
|
||||||
const LiveNowContext = createContext<LiveNowContext | null>(null)
|
const LiveNowContext = createContext<LiveNowContext>([])
|
||||||
LiveNowContext.displayName = 'LiveNowContext'
|
LiveNowContext.displayName = 'LiveNowContext'
|
||||||
|
|
||||||
const CheckEmailConfirmedContext = createContext<boolean | null>(null)
|
const CheckEmailConfirmedContext = createContext<boolean | null>(null)
|
||||||
@@ -82,19 +85,34 @@ export function useTrendingConfig() {
|
|||||||
return useContext(TrendingContext)
|
return useContext(TrendingContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLiveNowConfig() {
|
const DEFAULT_LIVE_ALLOWED_DOMAINS = ['twitch.tv', 'www.twitch.tv']
|
||||||
|
export type LiveNowConfig = {
|
||||||
|
allowedDomains: string[]
|
||||||
|
}
|
||||||
|
export function useLiveNowConfig(): LiveNowConfig {
|
||||||
const ctx = useContext(LiveNowContext)
|
const ctx = useContext(LiveNowContext)
|
||||||
if (!ctx) {
|
const canGoLive = useCanGoLive()
|
||||||
throw new Error(
|
const {currentAccount} = useSession()
|
||||||
'useLiveNowConfig must be used within a ServiceConfigManager',
|
if (!canGoLive) return {allowedDomains: []}
|
||||||
)
|
if (!currentAccount?.did) return {allowedDomains: []}
|
||||||
|
const vip = ctx.find(live => live.did === currentAccount.did)
|
||||||
|
if (vip) {
|
||||||
|
return {
|
||||||
|
allowedDomains: vip.domains.concat(DEFAULT_LIVE_ALLOWED_DOMAINS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
allowedDomains: DEFAULT_LIVE_ALLOWED_DOMAINS,
|
||||||
}
|
}
|
||||||
return ctx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCanGoLive(did?: string) {
|
export function useCanGoLive() {
|
||||||
const config = useLiveNowConfig()
|
const gate = useGate()
|
||||||
return !!config.find(cfg => cfg.did === did)
|
const {hasSession} = useSession()
|
||||||
|
if (!hasSession) return false
|
||||||
|
const isInBeta = IS_DEV ? true : gate('live_now_beta')
|
||||||
|
if (!isInBeta) return false
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCheckEmailConfirmed() {
|
export function useCheckEmailConfirmed() {
|
||||||
|
|||||||
Reference in New Issue
Block a user