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