mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at static-click 148 lines 4.9 kB view raw
1import React from 'react' 2import * as Linking from 'expo-linking' 3 4import {logEvent} from '#/lib/statsig/statsig' 5import {isNative} from '#/platform/detection' 6import {useSession} from '#/state/session' 7import {useComposerControls} from '#/state/shell' 8import {useCloseAllActiveElements} from '#/state/util' 9import {useIntentDialogs} from '#/components/intents/IntentDialogs' 10import {Referrer} from '../../../modules/expo-bluesky-swiss-army' 11 12type IntentType = 'compose' | 'verify-email' 13 14const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/ 15 16export function useIntentHandler() { 17 const incomingUrl = Linking.useURL() 18 const composeIntent = useComposeIntent() 19 const verifyEmailIntent = useVerifyEmailIntent() 20 21 React.useEffect(() => { 22 const handleIncomingURL = (url: string) => { 23 const referrerInfo = Referrer.getReferrerInfo() 24 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { 25 logEvent('deepLink:referrerReceived', { 26 to: url, 27 referrer: referrerInfo?.referrer, 28 hostname: referrerInfo?.hostname, 29 }) 30 } 31 32 // We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three 33 // slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care 34 // of two cases when parsing the url. If we ensure there is a third slash, we can always ensure the first 35 // path parameter is in pathname rather than in hostname. 36 if (url.startsWith('bluesky://') && !url.startsWith('bluesky:///')) { 37 url = url.replace('bluesky://', 'bluesky:///') 38 } 39 40 const urlp = new URL(url) 41 const [_, intent, intentType] = urlp.pathname.split('/') 42 43 // On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the 44 // intent check. On web, we have to check the first part of the path since we have an actual hostname 45 const isIntent = intent === 'intent' 46 const params = urlp.searchParams 47 48 if (!isIntent) return 49 50 switch (intentType as IntentType) { 51 case 'compose': { 52 composeIntent({ 53 text: params.get('text'), 54 imageUrisStr: params.get('imageUris'), 55 videoUri: params.get('videoUri'), 56 }) 57 return 58 } 59 case 'verify-email': { 60 const code = params.get('code') 61 if (!code) return 62 verifyEmailIntent(code) 63 return 64 } 65 default: { 66 return 67 } 68 } 69 } 70 71 if (incomingUrl) handleIncomingURL(incomingUrl) 72 }, [incomingUrl, composeIntent, verifyEmailIntent]) 73} 74 75export function useComposeIntent() { 76 const closeAllActiveElements = useCloseAllActiveElements() 77 const {openComposer} = useComposerControls() 78 const {hasSession} = useSession() 79 80 return React.useCallback( 81 ({ 82 text, 83 imageUrisStr, 84 videoUri, 85 }: { 86 text: string | null 87 imageUrisStr: string | null 88 videoUri: string | null 89 }) => { 90 if (!hasSession) return 91 92 closeAllActiveElements() 93 94 // Whenever a video URI is present, we don't support adding images right now. 95 if (videoUri) { 96 const [uri, width, height] = videoUri.split('|') 97 openComposer({ 98 text: text ?? undefined, 99 videoUri: {uri, width: Number(width), height: Number(height)}, 100 }) 101 return 102 } 103 104 const imageUris = imageUrisStr 105 ?.split(',') 106 .filter(part => { 107 // For some security, we're going to filter out any image uri that is external. We don't want someone to 108 // be able to provide some link like "bluesky://intent/compose?imageUris=https://IHaveYourIpNow.com/image.jpeg 109 // and we load that image 110 if (part.includes('https://') || part.includes('http://')) { 111 return false 112 } 113 // We also should just filter out cases that don't have all the info we need 114 return VALID_IMAGE_REGEX.test(part) 115 }) 116 .map(part => { 117 const [uri, width, height] = part.split('|') 118 return {uri, width: Number(width), height: Number(height)} 119 }) 120 121 setTimeout(() => { 122 openComposer({ 123 text: text ?? undefined, 124 imageUris: isNative ? imageUris : undefined, 125 }) 126 }, 500) 127 }, 128 [hasSession, closeAllActiveElements, openComposer], 129 ) 130} 131 132function useVerifyEmailIntent() { 133 const closeAllActiveElements = useCloseAllActiveElements() 134 const {verifyEmailDialogControl: control, setVerifyEmailState: setState} = 135 useIntentDialogs() 136 return React.useCallback( 137 (code: string) => { 138 closeAllActiveElements() 139 setState({ 140 code, 141 }) 142 setTimeout(() => { 143 control.open() 144 }, 1000) 145 }, 146 [closeAllActiveElements, control, setState], 147 ) 148}