mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at verify-code 3.7 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 {Referrer} from '../../../modules/expo-bluesky-swiss-army' 10 11type IntentType = 'compose' 12 13const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/ 14 15export function useIntentHandler() { 16 const incomingUrl = Linking.useURL() 17 const composeIntent = useComposeIntent() 18 19 React.useEffect(() => { 20 const handleIncomingURL = (url: string) => { 21 const referrerInfo = Referrer.getReferrerInfo() 22 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { 23 logEvent('deepLink:referrerReceived', { 24 to: url, 25 referrer: referrerInfo?.referrer, 26 hostname: referrerInfo?.hostname, 27 }) 28 } 29 30 // We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three 31 // slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care 32 // of two cases when parsing the url. If we ensure there is a third slash, we can always ensure the first 33 // path parameter is in pathname rather than in hostname. 34 if (url.startsWith('bluesky://') && !url.startsWith('bluesky:///')) { 35 url = url.replace('bluesky://', 'bluesky:///') 36 } 37 38 const urlp = new URL(url) 39 const [_, intent, intentType] = urlp.pathname.split('/') 40 41 // On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the 42 // intent check. On web, we have to check the first part of the path since we have an actual hostname 43 const isIntent = intent === 'intent' 44 const params = urlp.searchParams 45 46 if (!isIntent) return 47 48 switch (intentType as IntentType) { 49 case 'compose': { 50 composeIntent({ 51 text: params.get('text'), 52 imageUrisStr: params.get('imageUris'), 53 }) 54 } 55 } 56 } 57 58 if (incomingUrl) handleIncomingURL(incomingUrl) 59 }, [incomingUrl, composeIntent]) 60} 61 62function useComposeIntent() { 63 const closeAllActiveElements = useCloseAllActiveElements() 64 const {openComposer} = useComposerControls() 65 const {hasSession} = useSession() 66 67 return React.useCallback( 68 ({ 69 text, 70 imageUrisStr, 71 }: { 72 text: string | null 73 imageUrisStr: string | null // unused for right now, will be used later with intents 74 }) => { 75 if (!hasSession) return 76 77 closeAllActiveElements() 78 79 const imageUris = imageUrisStr 80 ?.split(',') 81 .filter(part => { 82 // For some security, we're going to filter out any image uri that is external. We don't want someone to 83 // be able to provide some link like "bluesky://intent/compose?imageUris=https://IHaveYourIpNow.com/image.jpeg 84 // and we load that image 85 if (part.includes('https://') || part.includes('http://')) { 86 return false 87 } 88 // We also should just filter out cases that don't have all the info we need 89 return VALID_IMAGE_REGEX.test(part) 90 }) 91 .map(part => { 92 const [uri, width, height] = part.split('|') 93 return {uri, width: Number(width), height: Number(height)} 94 }) 95 96 setTimeout(() => { 97 openComposer({ 98 text: text ?? undefined, 99 imageUris: isNative ? imageUris : undefined, 100 }) 101 }, 500) 102 }, 103 [hasSession, closeAllActiveElements, openComposer], 104 ) 105}