prewarm URLs in onPressIn

This commit is contained in:
Samuel Newman
2025-11-23 12:47:13 +02:00
parent 8424083de9
commit d138484187
9 changed files with 76 additions and 2 deletions
@@ -0,0 +1,6 @@
{
"platforms": ["ios"],
"ios": {
"modules": ["UrlPrewarmModule"]
}
}
+1
View File
@@ -0,0 +1 @@
export * from './src/UrlPrewarmModule'
@@ -0,0 +1,25 @@
Pod::Spec.new do |s|
s.name = 'UrlPrewarm'
s.version = '1.0.0'
s.summary = 'A sample project summary'
s.description = 'A sample project description'
s.author = ''
s.homepage = 'https://docs.expo.dev/modules/'
s.platforms = {
:ios => '15.1',
:tvos => '15.1'
}
s.source = { git: '' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
}
s.frameworks = 'SafariServices'
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
end
@@ -0,0 +1,13 @@
import ExpoModulesCore
import SafariServices
public class UrlPrewarmModule: Module {
public func definition() -> ModuleDefinition {
Name("UrlPrewarm")
AsyncFunction("prewarmUrlsAsync") { (urls: [String]) in
let validUrls = urls.compactMap { URL(string: $0) }
SFSafariViewController.prewarmConnections(to: validUrls)
}
}
}
@@ -0,0 +1,7 @@
import {requireNativeModule} from 'expo'
const NativeModule = requireNativeModule('UrlPrewarm')
export function prewarmUrlsAsync(urls: string[]): Promise<void> {
return NativeModule.prewarmUrlsAsync(urls)
}
@@ -0,0 +1,5 @@
export function prewarmUrlsAsync(_urls: string[]): Promise<void> {
throw new Error(
'[Not available] prewarmUrlsAsync is not available on this platform',
)
}
@@ -7,6 +7,7 @@ import {useLingui} from '@lingui/react'
import {parseAltFromGIFDescription} from '#/lib/gif-alt-text' import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {prewarmUrl} from '#/lib/hooks/useOpenLink'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
import {toNiceDomain} from '#/lib/strings/url-helpers' import {toNiceDomain} from '#/lib/strings/url-helpers'
@@ -79,6 +80,7 @@ export const ExternalEmbed = ({
label={link.title || _(msg`Open link to ${niceUrl}`)} label={link.title || _(msg`Open link to ${niceUrl}`)}
to={link.uri} to={link.uri}
shouldProxy={true} shouldProxy={true}
onPressIn={() => prewarmUrl(link.uri)}
onPress={onPress} onPress={onPress}
onLongPress={onShareExternal}> onLongPress={onShareExternal}>
{({hovered}) => ( {({hovered}) => (
+3 -1
View File
@@ -2,6 +2,7 @@ import React from 'react'
import {type StyleProp, type TextStyle} from 'react-native' import {type StyleProp, type TextStyle} from 'react-native'
import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api'
import {prewarmUrl} from '#/lib/hooks/useOpenLink'
import {toShortUrl} from '#/lib/strings/url-helpers' import {toShortUrl} from '#/lib/strings/url-helpers'
import {atoms as a, flatten, type TextStyleProp} from '#/alf' import {atoms as a, flatten, type TextStyleProp} from '#/alf'
import {isOnlyEmoji} from '#/alf/typography' import {isOnlyEmoji} from '#/alf/typography'
@@ -108,7 +109,7 @@ export function RichText({
style={interactiveStyles} style={interactiveStyles}
// @ts-ignore TODO // @ts-ignore TODO
dataSet={WORD_WRAP} dataSet={WORD_WRAP}
shouldProxy={shouldProxyLinks} shouldProxy={false}
onPress={onLinkPress}> onPress={onLinkPress}>
{segment.text} {segment.text}
</InlineLinkText> </InlineLinkText>
@@ -128,6 +129,7 @@ export function RichText({
dataSet={WORD_WRAP} dataSet={WORD_WRAP}
shareOnLongPress shareOnLongPress
shouldProxy={shouldProxyLinks} shouldProxy={shouldProxyLinks}
onPressIn={() => prewarmUrl(link.uri, shouldProxyLinks)}
onPress={onLinkPress} onPress={onLinkPress}
emoji> emoji>
{toShortUrl(segment.text)} {toShortUrl(segment.text)}
+14 -1
View File
@@ -12,12 +12,13 @@ import {
toNiceDomain, toNiceDomain,
} from '#/lib/strings/url-helpers' } from '#/lib/strings/url-helpers'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative} from '#/platform/detection' import {isIOS, isNative} from '#/platform/detection'
import {useInAppBrowser} from '#/state/preferences/in-app-browser' import {useInAppBrowser} from '#/state/preferences/in-app-browser'
import {useTheme} from '#/alf' import {useTheme} from '#/alf'
import {useDialogContext} from '#/components/Dialog' import {useDialogContext} from '#/components/Dialog'
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
import * as UrlPrewarm from '../../../modules/url-prewarm'
export function useOpenLink() { export function useOpenLink() {
const enabled = useInAppBrowser() const enabled = useInAppBrowser()
@@ -81,3 +82,15 @@ export function useOpenLink() {
return openLink return openLink
} }
export async function prewarmUrl(url: string, shouldProxy: boolean = true) {
if (!isIOS) return
if (!isBskyAppUrl(url)) {
const urls = [url]
if (shouldProxy) {
urls.push(createProxiedUrl(url))
}
await UrlPrewarm.prewarmUrlsAsync(urls)
}
}