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}