Pass referrer on native (with an opt out) (#6648)
* Pass referer on native * Add ChainLink3 * Add an opt out for sending utm * Remove noreferrer on links We do have <meta name="referrer" content="origin-when-cross-origin"> in HTML, should be sufficient. * Narrow down the condition slightly --------- Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 610 B |
@@ -223,7 +223,7 @@ export function Link({
|
|||||||
{...web({
|
{...web({
|
||||||
hrefAttrs: {
|
hrefAttrs: {
|
||||||
target: download ? undefined : isExternal ? 'blank' : undefined,
|
target: download ? undefined : isExternal ? 'blank' : undefined,
|
||||||
rel: isExternal ? 'noopener noreferrer' : undefined,
|
rel: isExternal ? 'noopener' : undefined,
|
||||||
download,
|
download,
|
||||||
},
|
},
|
||||||
dataSet: {
|
dataSet: {
|
||||||
@@ -307,7 +307,7 @@ export function InlineLinkText({
|
|||||||
{...web({
|
{...web({
|
||||||
hrefAttrs: {
|
hrefAttrs: {
|
||||||
target: download ? undefined : isExternal ? 'blank' : undefined,
|
target: download ? undefined : isExternal ? 'blank' : undefined,
|
||||||
rel: isExternal ? 'noopener noreferrer' : undefined,
|
rel: isExternal ? 'noopener' : undefined,
|
||||||
download,
|
download,
|
||||||
},
|
},
|
||||||
dataSet: {
|
dataSet: {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const ChainLink3_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z',
|
||||||
|
})
|
||||||
@@ -4,12 +4,14 @@ import * as WebBrowser from 'expo-web-browser'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
createBskyAppAbsoluteUrl,
|
createBskyAppAbsoluteUrl,
|
||||||
|
isBskyAppUrl,
|
||||||
isBskyRSSUrl,
|
isBskyRSSUrl,
|
||||||
isRelativeUrl,
|
isRelativeUrl,
|
||||||
} from '#/lib/strings/url-helpers'
|
} from '#/lib/strings/url-helpers'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useInAppBrowser} from '#/state/preferences/in-app-browser'
|
import {useInAppBrowser} from '#/state/preferences/in-app-browser'
|
||||||
|
import {useOptOutOfUtm} from '#/state/preferences/opt-out-of-utm'
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ export function useOpenLink() {
|
|||||||
const enabled = useInAppBrowser()
|
const enabled = useInAppBrowser()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const sheetWrapper = useSheetWrapper()
|
const sheetWrapper = useSheetWrapper()
|
||||||
|
const optOutOfUtm = useOptOutOfUtm()
|
||||||
|
|
||||||
const openLink = useCallback(
|
const openLink = useCallback(
|
||||||
async (url: string, override?: boolean) => {
|
async (url: string, override?: boolean) => {
|
||||||
@@ -26,6 +29,9 @@ export function useOpenLink() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNative && !url.startsWith('mailto:')) {
|
if (isNative && !url.startsWith('mailto:')) {
|
||||||
|
if (!optOutOfUtm && !isBskyAppUrl(url) && url.startsWith('http')) {
|
||||||
|
url = addUtmSource(url)
|
||||||
|
}
|
||||||
if (override === undefined && enabled === undefined) {
|
if (override === undefined && enabled === undefined) {
|
||||||
openModal({
|
openModal({
|
||||||
name: 'in-app-browser-consent',
|
name: 'in-app-browser-consent',
|
||||||
@@ -47,8 +53,24 @@ export function useOpenLink() {
|
|||||||
}
|
}
|
||||||
Linking.openURL(url)
|
Linking.openURL(url)
|
||||||
},
|
},
|
||||||
[enabled, openModal, t, sheetWrapper],
|
[enabled, openModal, t, sheetWrapper, optOutOfUtm],
|
||||||
)
|
)
|
||||||
|
|
||||||
return openLink
|
return openLink
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addUtmSource(url: string): string {
|
||||||
|
let parsedUrl
|
||||||
|
try {
|
||||||
|
parsedUrl = new URL(url)
|
||||||
|
} catch (e) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
if (!parsedUrl.searchParams.has('utm_source')) {
|
||||||
|
parsedUrl.searchParams.set('utm_source', 'bluesky')
|
||||||
|
if (!parsedUrl.searchParams.has('utm_medium')) {
|
||||||
|
parsedUrl.searchParams.set('utm_medium', 'social')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsedUrl.toString()
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,9 +9,16 @@ import {
|
|||||||
useInAppBrowser,
|
useInAppBrowser,
|
||||||
useSetInAppBrowser,
|
useSetInAppBrowser,
|
||||||
} from '#/state/preferences/in-app-browser'
|
} from '#/state/preferences/in-app-browser'
|
||||||
|
import {
|
||||||
|
useOptOutOfUtm,
|
||||||
|
useSetOptOutOfUtm,
|
||||||
|
} from '#/state/preferences/opt-out-of-utm'
|
||||||
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
|
import {Admonition} from '#/components/Admonition'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble'
|
import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble'
|
||||||
|
import {ChainLink3_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
|
||||||
import {Hashtag_Stroke2_Corner0_Rounded as HashtagIcon} from '#/components/icons/Hashtag'
|
import {Hashtag_Stroke2_Corner0_Rounded as HashtagIcon} from '#/components/icons/Hashtag'
|
||||||
import {Home_Stroke2_Corner2_Rounded as HomeIcon} from '#/components/icons/Home'
|
import {Home_Stroke2_Corner2_Rounded as HomeIcon} from '#/components/icons/Home'
|
||||||
import {Macintosh_Stroke2_Corner2_Rounded as MacintoshIcon} from '#/components/icons/Macintosh'
|
import {Macintosh_Stroke2_Corner2_Rounded as MacintoshIcon} from '#/components/icons/Macintosh'
|
||||||
@@ -29,6 +36,8 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
|||||||
const setAutoplayDisabledPref = useSetAutoplayDisabled()
|
const setAutoplayDisabledPref = useSetAutoplayDisabled()
|
||||||
const inAppBrowserPref = useInAppBrowser()
|
const inAppBrowserPref = useInAppBrowser()
|
||||||
const setUseInAppBrowser = useSetInAppBrowser()
|
const setUseInAppBrowser = useSetInAppBrowser()
|
||||||
|
const optOutOfUtm = useOptOutOfUtm()
|
||||||
|
const setOptOutOfUtm = useSetOptOutOfUtm()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
@@ -68,6 +77,19 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
|||||||
</SettingsList.ItemText>
|
</SettingsList.ItemText>
|
||||||
</SettingsList.LinkItem>
|
</SettingsList.LinkItem>
|
||||||
<SettingsList.Divider />
|
<SettingsList.Divider />
|
||||||
|
<Toggle.Item
|
||||||
|
name="disable_autoplay"
|
||||||
|
label={_(msg`Autoplay videos and GIFs`)}
|
||||||
|
value={!autoplayDisabledPref}
|
||||||
|
onChange={value => setAutoplayDisabledPref(!value)}>
|
||||||
|
<SettingsList.Item>
|
||||||
|
<SettingsList.ItemIcon icon={PlayIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Autoplay videos and GIFs</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
<Toggle.Platform />
|
||||||
|
</SettingsList.Item>
|
||||||
|
</Toggle.Item>
|
||||||
{isNative && (
|
{isNative && (
|
||||||
<Toggle.Item
|
<Toggle.Item
|
||||||
name="use_in_app_browser"
|
name="use_in_app_browser"
|
||||||
@@ -83,19 +105,31 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
|||||||
</SettingsList.Item>
|
</SettingsList.Item>
|
||||||
</Toggle.Item>
|
</Toggle.Item>
|
||||||
)}
|
)}
|
||||||
<Toggle.Item
|
{isNative && <SettingsList.Divider />}
|
||||||
name="disable_autoplay"
|
{isNative && (
|
||||||
label={_(msg`Autoplay videos and GIFs`)}
|
<Toggle.Item
|
||||||
value={!autoplayDisabledPref}
|
name="allow_utm"
|
||||||
onChange={value => setAutoplayDisabledPref(!value)}>
|
label={_(msg`Specify Bluesky as a referer`)}
|
||||||
|
value={!(optOutOfUtm ?? false)}
|
||||||
|
onChange={value => setOptOutOfUtm(!value)}>
|
||||||
|
<SettingsList.Item>
|
||||||
|
<SettingsList.ItemIcon icon={ChainLinkIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Send Bluesky referrer</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
<Toggle.Platform />
|
||||||
|
</SettingsList.Item>
|
||||||
|
</Toggle.Item>
|
||||||
|
)}
|
||||||
|
{isNative && (
|
||||||
<SettingsList.Item>
|
<SettingsList.Item>
|
||||||
<SettingsList.ItemIcon icon={PlayIcon} />
|
<Admonition type="info" style={[a.flex_1]}>
|
||||||
<SettingsList.ItemText>
|
<Trans>
|
||||||
<Trans>Autoplay videos and GIFs</Trans>
|
Helps external sites estimate traffic from Bluesky.
|
||||||
</SettingsList.ItemText>
|
</Trans>
|
||||||
<Toggle.Platform />
|
</Admonition>
|
||||||
</SettingsList.Item>
|
</SettingsList.Item>
|
||||||
</Toggle.Item>
|
)}
|
||||||
</SettingsList.Container>
|
</SettingsList.Container>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ const schema = z.object({
|
|||||||
subtitlesEnabled: z.boolean().optional(),
|
subtitlesEnabled: z.boolean().optional(),
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
mutedThreads: z.array(z.string()),
|
mutedThreads: z.array(z.string()),
|
||||||
|
optOutOfUtm: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
export type Schema = z.infer<typeof schema>
|
export type Schema = z.infer<typeof schema>
|
||||||
|
|
||||||
@@ -169,6 +170,7 @@ export const defaults: Schema = {
|
|||||||
kawaii: false,
|
kawaii: false,
|
||||||
hasCheckedForStarterPack: false,
|
hasCheckedForStarterPack: false,
|
||||||
subtitlesEnabled: true,
|
subtitlesEnabled: true,
|
||||||
|
optOutOfUtm: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tryParse(rawData: string): Schema | undefined {
|
export function tryParse(rawData: string): Schema | undefined {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {Provider as InAppBrowserProvider} from './in-app-browser'
|
|||||||
import {Provider as KawaiiProvider} from './kawaii'
|
import {Provider as KawaiiProvider} from './kawaii'
|
||||||
import {Provider as LanguagesProvider} from './languages'
|
import {Provider as LanguagesProvider} from './languages'
|
||||||
import {Provider as LargeAltBadgeProvider} from './large-alt-badge'
|
import {Provider as LargeAltBadgeProvider} from './large-alt-badge'
|
||||||
|
import {Provider as OutOutOfUtmProvider} from './opt-out-of-utm'
|
||||||
import {Provider as SubtitlesProvider} from './subtitles'
|
import {Provider as SubtitlesProvider} from './subtitles'
|
||||||
import {Provider as UsedStarterPacksProvider} from './used-starter-packs'
|
import {Provider as UsedStarterPacksProvider} from './used-starter-packs'
|
||||||
|
|
||||||
@@ -39,7 +40,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
<AutoplayProvider>
|
<AutoplayProvider>
|
||||||
<UsedStarterPacksProvider>
|
<UsedStarterPacksProvider>
|
||||||
<SubtitlesProvider>
|
<SubtitlesProvider>
|
||||||
<KawaiiProvider>{children}</KawaiiProvider>
|
<OutOutOfUtmProvider>
|
||||||
|
<KawaiiProvider>{children}</KawaiiProvider>
|
||||||
|
</OutOutOfUtmProvider>
|
||||||
</SubtitlesProvider>
|
</SubtitlesProvider>
|
||||||
</UsedStarterPacksProvider>
|
</UsedStarterPacksProvider>
|
||||||
</AutoplayProvider>
|
</AutoplayProvider>
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import * as persisted from '#/state/persisted'
|
||||||
|
|
||||||
|
type StateContext = boolean
|
||||||
|
type SetContext = (v: boolean) => void
|
||||||
|
|
||||||
|
const stateContext = React.createContext<StateContext>(
|
||||||
|
Boolean(persisted.defaults.optOutOfUtm),
|
||||||
|
)
|
||||||
|
const setContext = React.createContext<SetContext>((_: boolean) => {})
|
||||||
|
|
||||||
|
export function Provider({children}: {children: React.ReactNode}) {
|
||||||
|
const [state, setState] = React.useState(
|
||||||
|
Boolean(persisted.get('optOutOfUtm')),
|
||||||
|
)
|
||||||
|
|
||||||
|
const setStateWrapped = React.useCallback(
|
||||||
|
(optOutOfUtm: persisted.Schema['optOutOfUtm']) => {
|
||||||
|
setState(Boolean(optOutOfUtm))
|
||||||
|
persisted.write('optOutOfUtm', optOutOfUtm)
|
||||||
|
},
|
||||||
|
[setState],
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return persisted.onUpdate('optOutOfUtm', nextOptOutOfUtm => {
|
||||||
|
setState(Boolean(nextOptOutOfUtm))
|
||||||
|
})
|
||||||
|
}, [setStateWrapped])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<stateContext.Provider value={state}>
|
||||||
|
<setContext.Provider value={setStateWrapped}>
|
||||||
|
{children}
|
||||||
|
</setContext.Provider>
|
||||||
|
</stateContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOptOutOfUtm = () => React.useContext(stateContext)
|
||||||
|
export const useSetOptOutOfUtm = () => React.useContext(setContext)
|
||||||
@@ -256,7 +256,7 @@ export const TextLink = memo(function TextLink({
|
|||||||
if (isExternal) {
|
if (isExternal) {
|
||||||
return {
|
return {
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
// rel: 'noopener noreferrer',
|
// rel: 'noopener',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
Reference in New Issue
Block a user