From 8a5e1ac689997374205727df1c5fa45e145769b3 Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Sun, 17 May 2026 18:11:24 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EA=B3=B5=EC=9C=A0=20URL=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EC=B6=94=EA=B0=80=20=EC=A7=84=EC=9E=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.json | 14 + app/(auth)/login.tsx | 20 +- app/(tabs)/(home)/add-link.tsx | 43 +- app/(tabs)/_layout.tsx | 2 + app/_layout.tsx | 31 +- components/share-intent-router.tsx | 59 +++ package-lock.json | 548 +++++++++++++++++++++++++- package.json | 3 + patches/expo-share-intent+5.1.1.patch | 19 + patches/xcode+3.0.1.patch | 14 + plugins/with-android-single-task.js | 9 + utils/shared-url.ts | 75 ++++ 12 files changed, 790 insertions(+), 47 deletions(-) create mode 100644 components/share-intent-router.tsx create mode 100644 patches/expo-share-intent+5.1.1.patch create mode 100644 patches/xcode+3.0.1.patch create mode 100644 utils/shared-url.ts diff --git a/app.json b/app.json index 9497703..e44c72d 100644 --- a/app.json +++ b/app.json @@ -29,6 +29,20 @@ }, "plugins": [ "expo-router", + [ + "expo-share-intent", + { + "iosActivationRules": { + "NSExtensionActivationSupportsText": true, + "NSExtensionActivationSupportsWebURLWithMaxCount": 1, + "NSExtensionActivationSupportsWebPageWithMaxCount": 1 + }, + "iosShareExtensionName": "LinCleanShareExtension", + "androidIntentFilters": [ + "text/*" + ] + } + ], "./plugins/with-android-single-task", [ "expo-splash-screen", diff --git a/app/(auth)/login.tsx b/app/(auth)/login.tsx index 30929f8..41e2e2f 100644 --- a/app/(auth)/login.tsx +++ b/app/(auth)/login.tsx @@ -2,6 +2,7 @@ import { useAuth, useSSO } from '@clerk/expo'; import * as AuthSession from 'expo-auth-session'; import { Image } from 'expo-image'; import { router } from 'expo-router'; +import { useShareIntentContext } from 'expo-share-intent'; import * as WebBrowser from 'expo-web-browser'; import { useState } from 'react'; import { Alert, StyleSheet, Text, View } from 'react-native'; @@ -10,6 +11,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { SocialLoginButton } from '@/components/ui/social-login-button'; import { Colors, Typography } from '@/constants/theme'; import { syncAuthenticatedMember } from '@/services/auth-api'; +import { getSharedUrlFromIntent } from '@/utils/shared-url'; const IMG_WORDMARK = require('@/assets/images/login_wordmark.png'); const CLERK_REDIRECT_URL = AuthSession.makeRedirectUri({ @@ -23,6 +25,7 @@ export default function LoginScreen() { const insets = useSafeAreaInsets(); const { getToken, signOut } = useAuth(); const { startSSOFlow } = useSSO(); + const { hasShareIntent, resetShareIntent, shareIntent } = useShareIntentContext(); const [isSigningIn, setIsSigningIn] = useState(false); const handleGoogleLogin = async () => { @@ -46,7 +49,22 @@ export default function LoginScreen() { await setActive({ session: createdSessionId }); sessionActivated = true; await syncAuthenticatedMember(getToken); - router.replace('/(tabs)/(home)'); + + const sharedUrl = hasShareIntent ? getSharedUrlFromIntent(shareIntent) : null; + + if (sharedUrl) { + router.replace({ + pathname: '/(tabs)/(home)/add-link', + params: { sharedUrl }, + }); + resetShareIntent(true); + } else { + if (hasShareIntent) { + resetShareIntent(true); + } + + router.replace('/(tabs)/(home)'); + } } catch (error) { console.error(error); diff --git a/app/(tabs)/(home)/add-link.tsx b/app/(tabs)/(home)/add-link.tsx index 6eb4ee0..c083e72 100644 --- a/app/(tabs)/(home)/add-link.tsx +++ b/app/(tabs)/(home)/add-link.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { ActivityIndicator, KeyboardAvoidingView, @@ -10,29 +10,38 @@ import { TouchableOpacity, View, } from 'react-native'; -import { Stack, router } from 'expo-router'; +import { Stack, router, useLocalSearchParams } from 'expo-router'; import { ScanButton } from '@/components/ui/scan-button'; import { Colors, Typography } from '@/constants/theme'; +import { normalizeHttpUrlInput } from '@/utils/shared-url'; -function isValidUrlFormat(value: string): boolean { - const trimmed = value.trim(); - if (!/^https?:\/\//i.test(trimmed)) return false; - try { - const parsed = new URL(trimmed); - const parts = parsed.hostname.split('.'); - const tld = parts[parts.length - 1]; - return parts.length >= 2 && tld.length >= 2; - } catch { - return false; +function getSharedUrlParam(value: string | string[] | undefined): string { + if (typeof value !== 'string') { + return ''; } + + return normalizeHttpUrlInput(value) ?? ''; } export default function AddLinkScreen() { - const [url, setUrl] = useState(''); + const { sharedUrl } = useLocalSearchParams<{ sharedUrl?: string }>(); + const initialSharedUrl = getSharedUrlParam(sharedUrl); + const [url, setUrl] = useState(initialSharedUrl); const [error, setError] = useState(''); const [isChecking, setIsChecking] = useState(false); + useEffect(() => { + const nextSharedUrl = getSharedUrlParam(sharedUrl); + + if (!nextSharedUrl) { + return; + } + + setUrl(nextSharedUrl); + setError(''); + }, [sharedUrl]); + const handleScan = async () => { const trimmed = url.trim(); @@ -42,7 +51,9 @@ export default function AddLinkScreen() { } // 1단계: new URL()로 형식 검증 - if (!isValidUrlFormat(trimmed)) { + const normalizedUrl = normalizeHttpUrlInput(trimmed); + + if (!normalizedUrl) { setError('올바르지 않은 URL 입력입니다.'); return; } @@ -50,13 +61,13 @@ export default function AddLinkScreen() { // 2단계: Linking.canOpenURL()로 실제 열기 가능 여부 확인 setIsChecking(true); try { - const canOpen = await Linking.canOpenURL(trimmed); + const canOpen = await Linking.canOpenURL(normalizedUrl); if (!canOpen) { setError('올바르지 않은 URL 입력입니다.'); return; } setError(''); - router.push({ pathname: '/(tabs)/(home)/scanning', params: { url: trimmed } }); + router.push({ pathname: '/(tabs)/(home)/scanning', params: { url: normalizedUrl } }); } catch { setError('URL 확인 중 오류가 발생했습니다.'); } finally { diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index e999349..2005dfa 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -3,6 +3,7 @@ import type { BottomTabBarProps } from '@react-navigation/bottom-tabs'; import { Redirect, router, Tabs } from 'expo-router'; import { useEffect, useRef, useState } from 'react'; +import { ShareIntentRouter } from '@/components/share-intent-router'; import { BottomTabBar, type TabVariant } from '@/components/ui/bottom-tab-bar'; import { SavedLinksProvider } from '@/context/saved-links-context'; import { syncAuthenticatedMember } from '@/services/auth-api'; @@ -120,6 +121,7 @@ export default function TabLayout() { return ( + } screenOptions={{ headerShown: false }} diff --git a/app/_layout.tsx b/app/_layout.tsx index cc99559..5b97697 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -2,6 +2,7 @@ import { ClerkProvider } from '@clerk/expo'; import { tokenCache } from '@clerk/expo/token-cache'; import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; import { Stack } from 'expo-router'; +import { ShareIntentProvider } from 'expo-share-intent'; import { StatusBar } from 'expo-status-bar'; import 'react-native-reanimated'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; @@ -24,19 +25,21 @@ export default function RootLayout() { const colorScheme = useColorScheme(); return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ); } diff --git a/components/share-intent-router.tsx b/components/share-intent-router.tsx new file mode 100644 index 0000000..a4fb1de --- /dev/null +++ b/components/share-intent-router.tsx @@ -0,0 +1,59 @@ +import { useAuth } from '@clerk/expo'; +import { router, useSegments } from 'expo-router'; +import { useShareIntentContext } from 'expo-share-intent'; +import { useEffect, useRef } from 'react'; + +import { getSharedUrlFromIntent } from '@/utils/shared-url'; + +export function ShareIntentRouter() { + const { isLoaded, isSignedIn } = useAuth(); + const segments = useSegments(); + const { error, hasShareIntent, isReady, resetShareIntent, shareIntent } = + useShareIntentContext(); + const handledUrlRef = useRef(null); + + useEffect(() => { + if (error) { + console.warn('Failed to read share intent.', error); + } + }, [error]); + + useEffect(() => { + if (!hasShareIntent) { + handledUrlRef.current = null; + return; + } + + if (!isLoaded || !isReady) { + return; + } + + if (!isSignedIn) { + return; + } + + if (segments[0] !== '(tabs)') { + return; + } + + const sharedUrl = getSharedUrlFromIntent(shareIntent); + + if (!sharedUrl) { + resetShareIntent(true); + return; + } + + if (handledUrlRef.current === sharedUrl) { + return; + } + + handledUrlRef.current = sharedUrl; + router.replace({ + pathname: '/(tabs)/(home)/add-link', + params: { sharedUrl }, + }); + resetShareIntent(true); + }, [hasShareIntent, isLoaded, isReady, isSignedIn, resetShareIntent, segments, shareIntent]); + + return null; +} diff --git a/package-lock.json b/package-lock.json index 57e4abd..04a36b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,12 +26,14 @@ "expo-linking": "~8.0.11", "expo-router": "~6.0.23", "expo-secure-store": "~15.0.8", + "expo-share-intent": "^5.1.1", "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", "expo-symbols": "~1.0.8", "expo-system-ui": "~6.0.9", "expo-web-browser": "~15.0.10", "lottie-react-native": "~7.3.1", + "patch-package": "^8.0.1", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.5", @@ -2478,6 +2480,102 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -2835,6 +2933,16 @@ "node": ">=12.4.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -4683,6 +4791,12 @@ "node": ">=10.0.0" } }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" + }, "node_modules/@zxcvbn-ts/core": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@zxcvbn-ts/core/-/core-3.0.4.tgz", @@ -5540,7 +5654,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5559,7 +5672,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5573,7 +5685,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6127,7 +6238,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -6261,7 +6371,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -6272,6 +6381,12 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6396,7 +6511,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6443,7 +6557,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -7550,6 +7663,168 @@ "node": ">=20.16.0" } }, + "node_modules/expo-share-intent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/expo-share-intent/-/expo-share-intent-5.1.1.tgz", + "integrity": "sha512-0sEf34+4w/ySQd7xZmnog/oOm1q+PUBHFoGU97mxTXIGijY4LNmSX806efrDkYMwCxgRA1iHxG/zBib4zBPmYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/achorein" + }, + "https://www.buymeacoffee.com/achorein" + ], + "license": "MIT", + "dependencies": { + "@expo/config-plugins": "~10.1.1", + "expo-constants": "~18.0.10", + "expo-linking": "~8.0.9" + }, + "peerDependencies": { + "expo": "^54", + "expo-constants": ">=18.0.8", + "expo-linking": ">=8.0.8", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-share-intent/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/expo-share-intent/node_modules/@expo/config-plugins": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.1.2.tgz", + "integrity": "sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==", + "license": "MIT", + "dependencies": { + "@expo/config-types": "^53.0.5", + "@expo/json-file": "~9.1.5", + "@expo/plist": "^0.3.5", + "@expo/sdk-runtime-versions": "^1.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.5", + "getenv": "^2.0.0", + "glob": "^10.4.2", + "resolve-from": "^5.0.0", + "semver": "^7.5.4", + "slash": "^3.0.0", + "slugify": "^1.6.6", + "xcode": "^3.0.1", + "xml2js": "0.6.0" + } + }, + "node_modules/expo-share-intent/node_modules/@expo/config-types": { + "version": "53.0.5", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.5.tgz", + "integrity": "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==", + "license": "MIT" + }, + "node_modules/expo-share-intent/node_modules/@expo/json-file": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.1.5.tgz", + "integrity": "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.3" + } + }, + "node_modules/expo-share-intent/node_modules/@expo/plist": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.3.5.tgz", + "integrity": "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.2.3", + "xmlbuilder": "^15.1.1" + } + }, + "node_modules/expo-share-intent/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/expo-share-intent/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/expo-share-intent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/expo-share-intent/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/expo-share-intent/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/expo-share-intent/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/expo-splash-screen": { "version": "31.0.13", "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-31.0.13.tgz", @@ -7992,6 +8267,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -8041,6 +8325,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/freeport-async": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz", @@ -8059,6 +8371,20 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8151,7 +8477,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -8194,7 +8519,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -8350,7 +8674,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8391,7 +8714,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -8420,7 +8742,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9164,7 +9485,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -9231,6 +9551,21 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -9475,6 +9810,25 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -9494,6 +9848,27 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -9520,6 +9895,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -10059,7 +10443,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10717,7 +11100,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11097,6 +11479,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11131,6 +11519,71 @@ "node": ">= 0.8" } }, + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/patch-package/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12554,7 +13007,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -12944,6 +13396,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -13054,6 +13521,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -13329,6 +13809,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -13642,6 +14131,15 @@ "node": ">=4" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -14360,6 +14858,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index c0302c6..21d7b4b 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "android": "expo run:android", "ios": "expo run:ios", "web": "expo start --web", + "postinstall": "patch-package", "lint": "expo lint" }, "dependencies": { @@ -29,12 +30,14 @@ "expo-linking": "~8.0.11", "expo-router": "~6.0.23", "expo-secure-store": "~15.0.8", + "expo-share-intent": "^5.1.1", "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", "expo-symbols": "~1.0.8", "expo-system-ui": "~6.0.9", "expo-web-browser": "~15.0.10", "lottie-react-native": "~7.3.1", + "patch-package": "^8.0.1", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.5", diff --git a/patches/expo-share-intent+5.1.1.patch b/patches/expo-share-intent+5.1.1.patch new file mode 100644 index 0000000..4135d6a --- /dev/null +++ b/patches/expo-share-intent+5.1.1.patch @@ -0,0 +1,19 @@ +diff --git a/node_modules/expo-share-intent/build/useShareIntent.js b/node_modules/expo-share-intent/build/useShareIntent.js +index 1ab12c4..d15ad05 100644 +--- a/node_modules/expo-share-intent/build/useShareIntent.js ++++ b/node_modules/expo-share-intent/build/useShareIntent.js +@@ -50,12 +50,12 @@ export default function useShareIntent(options = SHAREINTENT_OPTIONS_DEFAULT) { + } + }; + useEffect(() => { +- if (options.disabled) ++ if (options.disabled || !isReady) + return; + options.debug && + console.debug("useShareIntent[mount]", getScheme(options), options); + refreshShareIntent(); +- }, [url, options.disabled]); ++ }, [url, options.disabled, isReady]); + /** + * Handle application state (active, background, inactive) + */ diff --git a/patches/xcode+3.0.1.patch b/patches/xcode+3.0.1.patch new file mode 100644 index 0000000..c69fe1b --- /dev/null +++ b/patches/xcode+3.0.1.patch @@ -0,0 +1,14 @@ +diff --git a/node_modules/xcode/lib/pbxProject.js b/node_modules/xcode/lib/pbxProject.js +index 068548a..b478056 100644 +--- a/node_modules/xcode/lib/pbxProject.js ++++ b/node_modules/xcode/lib/pbxProject.js +@@ -1678,8 +1678,8 @@ function correctForFrameworksPath(file, project) { + + function correctForPath(file, project, group) { + var r_group_dir = new RegExp('^' + group + '[\\\\/]'); + +- if (project.pbxGroupByName(group).path) ++ if (project.pbxGroupByName(group) && project.pbxGroupByName(group).path) + file.path = file.path.replace(r_group_dir, ''); + + return file; diff --git a/plugins/with-android-single-task.js b/plugins/with-android-single-task.js index 8f91095..3c986fc 100644 --- a/plugins/with-android-single-task.js +++ b/plugins/with-android-single-task.js @@ -2,10 +2,19 @@ const { AndroidConfig, withAndroidManifest } = require('@expo/config-plugins'); function withAndroidSingleTask(config) { return withAndroidManifest(config, (manifestConfig) => { + const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow( + manifestConfig.modResults + ); const mainActivity = AndroidConfig.Manifest.getMainActivityOrThrow(manifestConfig.modResults); + const isProductionBuild = process.env.EAS_BUILD_PROFILE === 'production'; + const usesHttpApi = process.env.EXPO_PUBLIC_API_BASE_URL?.startsWith('http://'); mainActivity.$['android:launchMode'] = 'singleTask'; + if (!isProductionBuild && usesHttpApi) { + mainApplication.$['android:usesCleartextTraffic'] = 'true'; + } + return manifestConfig; }); } diff --git a/utils/shared-url.ts b/utils/shared-url.ts new file mode 100644 index 0000000..8afa00c --- /dev/null +++ b/utils/shared-url.ts @@ -0,0 +1,75 @@ +import type { ShareIntent } from 'expo-share-intent'; + +const SHARED_URL_PATTERN = /https?:\/\/[^\s<>"']+/gi; +const TRAILING_PUNCTUATION_PATTERN = /[)\].,!?;:]+$/; + +function trimUrlCandidate(value: string): string { + return value.trim().replace(TRAILING_PUNCTUATION_PATTERN, ''); +} + +function hasValidHostname(hostname: string): boolean { + const parts = hostname.split('.'); + const tld = parts[parts.length - 1]; + + return parts.length >= 2 && tld.length >= 2; +} + +export function normalizeHttpUrlInput(value: string): string | null { + const candidate = trimUrlCandidate(value); + + if (!/^https?:\/\//i.test(candidate)) { + return null; + } + + try { + const parsed = new URL(candidate); + + if (!['http:', 'https:'].includes(parsed.protocol)) { + return null; + } + + if (!hasValidHostname(parsed.hostname)) { + return null; + } + + return parsed.toString(); + } catch { + return null; + } +} + +export function extractHttpUrlFromText(value: string): string | null { + const matches = value.match(SHARED_URL_PATTERN); + + if (!matches) { + return null; + } + + for (const match of matches) { + const normalized = normalizeHttpUrlInput(match); + + if (normalized) { + return normalized; + } + } + + return null; +} + +export function getSharedUrlFromIntent(shareIntent: ShareIntent): string | null { + const candidates = [shareIntent.webUrl, shareIntent.text, shareIntent.meta?.title]; + + for (const candidate of candidates) { + if (!candidate) { + continue; + } + + const normalized = extractHttpUrlFromText(candidate); + + if (normalized) { + return normalized; + } + } + + return null; +} From 726ba16698f838f15d9309bfa3fad1a6907a78eb Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Mon, 18 May 2026 22:33:30 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=EA=B3=B5=EC=9C=A0=20=EC=9D=B8?= =?UTF-8?q?=ED=85=90=ED=8A=B8=20=EB=A6=AC=EC=85=8B=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(auth)/login.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/(auth)/login.tsx b/app/(auth)/login.tsx index 41e2e2f..7245f1d 100644 --- a/app/(auth)/login.tsx +++ b/app/(auth)/login.tsx @@ -52,17 +52,16 @@ export default function LoginScreen() { const sharedUrl = hasShareIntent ? getSharedUrlFromIntent(shareIntent) : null; + if (hasShareIntent) { + resetShareIntent(true); + } + if (sharedUrl) { router.replace({ pathname: '/(tabs)/(home)/add-link', params: { sharedUrl }, }); - resetShareIntent(true); } else { - if (hasShareIntent) { - resetShareIntent(true); - } - router.replace('/(tabs)/(home)'); } } catch (error) {