mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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}