diff --git a/.gitignore b/.gitignore index f4b6515..bbe5af3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,11 @@ yarn-error.* .DS_Store *.pem +# builds +*.ipa +ios/ +android/ + # Apple Secret AuthKey_*.p8 diff --git a/app.json b/app.json index 928863b..d98ae4b 100644 --- a/app.json +++ b/app.json @@ -1,24 +1,41 @@ { "expo": { - "name": "Tech Tracker Expo", - "slug": "tech-tracker-expo", + "name": "Tech Tracker", + "slug": "tech-tracker", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/images/icon.png", "scheme": "com.techtracker", "userInterfaceStyle": "automatic", + "splash": { + "image": "./assets/images/splash.png", + "resizeMode": "contain", + "backgroundColor": "#2e2f3d" + }, "newArchEnabled": true, "ios": { "usesAppleSignIn": true, "supportsTablet": true, - "bundleIdentifier": "com.gibbyb.techtrackerexpo" + "bundleIdentifier": "com.gbrown.techtracker", + "config": { + "usesNonExemptEncryption": false + }, + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false, + "NSLocationWhenInUseUsageDescription": "This app uses your location in order to allow you to share your location in chat.", + "NSCameraUsageDescription": "This app uses your camera to take photos & send them in the chat." + } }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/images/adaptive-icon.png", - "backgroundColor": "#ffffff" + "backgroundColor": "#2e2f3d" }, - "package": "com.gibbyb.techtrackerexpo" + "permissions": [ + "android.permission.ACCESS_COARSE_LOCATION", + "android.permission.ACCESS_FINE_LOCATION" + ], + "package": "com.gbrown.techtracker" }, "web": { "bundler": "metro", @@ -36,8 +53,19 @@ "backgroundColor": "#ffffff" } ], - "expo-secure-store", - "expo-apple-authentication" + [ + "expo-secure-store", + { + "faceIDPermission": "Allow $(PRODUCT_NAME) to access your FaceID biometric data." + } + ], + "expo-apple-authentication", + [ + "expo-location", + { + "locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location." + } + ] ], "experiments": { "typedRoutes": true @@ -51,8 +79,6 @@ "updates": { "url": "https://u.expo.dev/7d872415-9160-4e06-ba95-4c3442e04b79" }, - "runtimeVersion": { - "policy": "appVersion" - } + "runtimeVersion": "1.0.0" } } diff --git a/app/_layout.tsx b/app/_layout.tsx index b231886..1f6ca4c 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -9,8 +9,8 @@ import { ThemedView } from '@/components/theme'; import { Session } from '@supabase/supabase-js'; import { useColorScheme } from '@/hooks/useColorScheme'; import { supabase } from '@/lib/supabase'; -import LoginPage from '@/components/auth/Login'; -import Account from '@/components/Account'; +import Auth from '@/components/auth/Auth'; +import PushNotificationManager from '@/services/PushNotificationManager'; // Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync(); @@ -44,19 +44,21 @@ const RootLayout = () => { } return ( - - {session && session.user ? ( - - - - - ) : ( - - - - )} - - + + + {session && session.user ? ( + + + + + ) : ( + + + + )} + + + ); }; diff --git a/components/auth/AppleSignIn.native.tsx b/components/auth/AppleSignIn.native.tsx new file mode 100644 index 0000000..ad22c7f --- /dev/null +++ b/components/auth/AppleSignIn.native.tsx @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from 'react'; +import { supabase } from '@/lib/supabase'; +import * as AppleAuthentication from 'expo-apple-authentication' +import { useColorScheme } from '@/hooks/useColorScheme'; +import { ThemedView } from '@/components/theme'; +import { StyleSheet, Platform } from 'react-native'; +import Constants from 'expo-constants'; +import * as Notifications from 'expo-notifications'; + +const AppleSignInButton = () => { + const scheme = useColorScheme() ?? 'dark'; + + const signInWithApple = async () => { + try { + const credential = await AppleAuthentication.signInAsync({ + requestedScopes: [ + AppleAuthentication.AppleAuthenticationScope.FULL_NAME, + AppleAuthentication.AppleAuthenticationScope.EMAIL, + ], + }); + //const projectId = Constants.expoConfig?.extra?.projectId; + //if (!projectId) throw new Error('No projectId found in expo.config.json'); + //const pushToken = await Notifications.getExpoPushTokenAsync({ + //projectId, + //}); + if (credential.identityToken) { + const full_name = ( + credential.fullName && + credential.fullName.givenName && + credential.fullName.familyName + ) + ? `${credential.fullName.givenName} ${credential.fullName.familyName}` + : null; + + const { + error, + data: { user, session }, + } = await supabase.auth.signInWithIdToken({ + provider: 'apple', + token: credential.identityToken, + }); + console.log(JSON.stringify({ error, user }, null, 2)) + if (!error) { + if (user) { + const data: any = { + full_name, + }; + const { error: updateError } = await supabase.auth.updateUser({ + data, + }); + if (updateError) { + console.error('Error updating user metadata:', updateError); + } + } + } + } else { + throw new Error('No identityToken.') + } + } catch (e: any) { + if (e.code === 'ERR_REQUEST_CANCELED') { + // handle that the user canceled the sign-in flow + console.log('User canceled sign-in flow'); + } else { + // handle other errors + console.log('Error signing in with Apple:', e); + } + } + } + + if (Platform.OS !== 'ios') return
; + else return ( + + + + + ); +} +export default AppleSignInButton; + +const styles = StyleSheet.create({ + verticallySpaced: { + paddingTop: 4, + paddingBottom: 4, + alignItems: 'center', + }, + mt20: { + marginTop: 20, + }, +}); diff --git a/components/auth/AppleSignIn.tsx b/components/auth/AppleSignIn.tsx deleted file mode 100644 index 386e36f..0000000 --- a/components/auth/AppleSignIn.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { StyleSheet, Alert, Platform } from 'react-native'; -import * as AppleAuthentication from 'expo-apple-authentication'; -import { supabase } from '@/lib/supabase'; -import { useColorScheme } from '@/hooks/useColorScheme'; - -type AppleSignInProps = { - onSignInStart?: () => void; - onSignInComplete?: () => void; - onSignInError?: (error: any) => void; -}; - -const AppleSignIn: React.FC = ({ - onSignInStart, - onSignInComplete, - onSignInError, -}) => { - const scheme = useColorScheme() ?? 'dark'; - const [isAppleAuthAvailable, setIsAppleAuthAvailable] = useState(false); - - useEffect(() => { - if (Platform.OS === 'ios') { - AppleAuthentication.isAvailableAsync().then(setIsAppleAuthAvailable); - } - }, []); - - const handleAppleSignIn = async () => { - try { - onSignInStart?.(); - - // Get credentials from Apple - const credential = await AppleAuthentication.signInAsync({ - requestedScopes: [ - AppleAuthentication.AppleAuthenticationScope.FULL_NAME, - AppleAuthentication.AppleAuthenticationScope.EMAIL, - ], - }); - - if (!credential.email) { - throw new Error('Email is required for Apple Sign In'); - } - - // Extract user information - const { email, fullName, user: appleUserId } = credential; - - // Create a name from the fullName object if available - let name = null; - if (fullName?.givenName || fullName?.familyName) { - name = `${fullName?.givenName || ''} ${fullName?.familyName || ''}`.trim(); - } - - // Create a deterministic password based on the Apple user ID - // This way the user can sign in again with the same password - const password = `Apple-${appleUserId.substring(0, 16)}`; - - // First try to sign in (in case the user already exists) - const { data: signInData, error: signInError } = await supabase.auth.signInWithPassword({ - email, - password, - }); - - if (!signInError && signInData?.user) { - // User exists and signed in successfully - onSignInComplete?.(); - return; - } - - // If sign-in failed, create a new user - const { data: signUpData, error: signUpError } = await supabase.auth.signUp({ - email, - password, - options: { - data: { - full_name: name, - }, - }, - }); - - if (signUpError) { - throw signUpError; - } - - // User created successfully - onSignInComplete?.(); - } catch (error) { - console.error('Apple sign in error:', error); - - if (error.code === 'ERR_REQUEST_CANCELED') { - console.log('Sign in was canceled'); - } else { - Alert.alert( - 'Sign in error', - 'An error occurred while signing in with Apple. Please try again.', - ); - onSignInError?.(error); - } - } - }; - - // Only render on iOS and if Apple Authentication is available - if (Platform.OS !== 'ios' || !isAppleAuthAvailable) { - return null; - } - - return ( - - ); -}; - -const styles = StyleSheet.create({ - button: { - width: 320, - height: 50, - marginVertical: 10, - }, -}); - -export default AppleSignIn; diff --git a/components/auth/Login.tsx b/components/auth/Auth.tsx similarity index 90% rename from components/auth/Login.tsx rename to components/auth/Auth.tsx index e3e3297..81e72d1 100644 --- a/components/auth/Login.tsx +++ b/components/auth/Auth.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Alert, StyleSheet, AppState, Image, Platform } from 'react-native'; import { supabase } from '@/lib/supabase'; import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme'; -import AppleSignIn from '@/components/auth/AppleSignIn'; +import AppleSignInButton from '@/components/auth/AppleSignIn.native'; // Tells Supabase Auth to continuously refresh the session automatically if // the app is in the foreground. When this is added, you will continue to receive @@ -18,7 +18,7 @@ if (Platform.OS !== 'web') { }); } -const LoginPage = () => { +const Auth = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); @@ -102,20 +102,12 @@ const LoginPage = () => { fontSize={24} /> - - {/* Apple Sign In - Only shows on iOS */} - - setLoading(true)} - onSignInComplete={() => setLoading(false)} - onSignInError={() => setLoading(false)} - /> - + ); }; -export default LoginPage; +export default Auth; const styles = StyleSheet.create({ container: { diff --git a/constants/Types.ts b/constants/Types.ts new file mode 100644 index 0000000..56b0bbe --- /dev/null +++ b/constants/Types.ts @@ -0,0 +1,6 @@ +export type NotificationMessage = { + sound?: string; + title: string; + body: string; + data?: any; +}; diff --git a/package-lock.json b/package-lock.json index 7f05e38..883f787 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "0BSD", "dependencies": { "@expo/metro-runtime": "~4.0.1", + "@expo/ngrok": "4.1.0", "@expo/vector-icons": "^14.0.2", "@react-native-async-storage/async-storage": "^1.23.1", "@react-navigation/bottom-tabs": "^7.2.0", @@ -19,12 +20,15 @@ "expo": "~52.0.28", "expo-apple-authentication": "~7.1.3", "expo-blur": "~14.0.3", - "expo-constants": "~17.0.5", - "expo-dev-client": "~5.0.10", + "expo-constants": "~17.0.7", + "expo-dev-client": "~5.0.12", + "expo-device": "~7.0.2", "expo-font": "~13.0.3", "expo-haptics": "~14.0.1", "expo-insights": "~0.8.2", "expo-linking": "~7.0.5", + "expo-location": "~18.0.7", + "expo-notifications": "~0.29.13", "expo-router": "~4.0.17", "expo-secure-store": "~14.0.1", "expo-splash-screen": "~0.29.21", @@ -2697,6 +2701,184 @@ "react-native": "*" } }, + "node_modules/@expo/ngrok": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@expo/ngrok/-/ngrok-4.1.0.tgz", + "integrity": "sha512-PrtWxBt/SnOF1jZkf7oWznhEPFrmYKKeJPLVRKnEBd/y4eUYfoiNIXOzflIzhdrMubjWVI+pFuPJ6nkjVL95/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@expo/ngrok-bin": "2.3.40", + "got": "^11.5.1", + "uuid": "^3.3.2", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/@expo/ngrok-bin": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin/-/ngrok-bin-2.3.40.tgz", + "integrity": "sha512-40GK1CY1QLPDHSQS7Fd36CJeZgMwtbeezkgp4tzfExVRVtWw30jOBCsM7TBB9IqEmmX7C/XwG47scMQHCnMw8A==", + "bin": { + "ngrok": "bin/ngrok.js" + }, + "optionalDependencies": { + "@expo/ngrok-bin-darwin-arm64": "2.3.40", + "@expo/ngrok-bin-darwin-x64": "2.3.40", + "@expo/ngrok-bin-freebsd-ia32": "2.3.40", + "@expo/ngrok-bin-freebsd-x64": "2.3.40", + "@expo/ngrok-bin-linux-arm": "2.3.40", + "@expo/ngrok-bin-linux-arm64": "2.3.40", + "@expo/ngrok-bin-linux-ia32": "2.3.40", + "@expo/ngrok-bin-linux-x64": "2.3.40", + "@expo/ngrok-bin-sunos-x64": "2.3.40", + "@expo/ngrok-bin-win32-ia32": "2.3.40", + "@expo/ngrok-bin-win32-x64": "2.3.40" + } + }, + "node_modules/@expo/ngrok-bin-darwin-arm64": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-darwin-arm64/-/ngrok-bin-darwin-arm64-2.3.40.tgz", + "integrity": "sha512-Zij81v/bIsVBvgXgYS71xbi/3lqKfVEfr7rId8BsHO3Ec1nQcp/I+729W3KX9PUHzWlXCLxOKZ3uF4jL/TcNbg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@expo/ngrok-bin-darwin-x64": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-darwin-x64/-/ngrok-bin-darwin-x64-2.3.40.tgz", + "integrity": "sha512-nqGLfxIjZBoT79VDk5mqaHQKCWkunSi486zGLeB8Ye8Qar1yo4STFwks+DqTbnGD5ItArQz2LzKRVE4YXuJFuw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@expo/ngrok-bin-freebsd-ia32": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-freebsd-ia32/-/ngrok-bin-freebsd-ia32-2.3.40.tgz", + "integrity": "sha512-Ji3jZaOuIZO+ege23kZZAAEPUYkF+6mCpghb16b28Is1QHOSl2L4foDnAcWyzSEiBihMicxWltaQyaaxA0fdgw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@expo/ngrok-bin-freebsd-x64": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-freebsd-x64/-/ngrok-bin-freebsd-x64-2.3.40.tgz", + "integrity": "sha512-mVnzKGQmOyXimZx6udoiyo3ZTYLZnPShlTySaDP0tqQ0vYz4ZscgvaYpMmDSPrsP/YG2owmKgzmOE2V+ycD8qA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@expo/ngrok-bin-linux-arm": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-arm/-/ngrok-bin-linux-arm-2.3.40.tgz", + "integrity": "sha512-Je1QBd7x0hbZa4T3gZbVgD0cSzstpJ7Mu0+dM2lOB+vm3bd603yHtD0RlLdqARJFhPTE1M2zLd68gCEeZ5fRgQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@expo/ngrok-bin-linux-arm64": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-arm64/-/ngrok-bin-linux-arm64-2.3.40.tgz", + "integrity": "sha512-S6kbnRqsVXHo/bWNxc0jfq33aQQRsGWjb6e7SvZ2DgXsPFLn27cfK0eHD96uCssARDVhzPsc+VU/B3d8C1DT5A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@expo/ngrok-bin-linux-ia32": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-ia32/-/ngrok-bin-linux-ia32-2.3.40.tgz", + "integrity": "sha512-gPY5zv5Fu+TkCm5iZolXQbu7e5hc7fTllIKn/zJQxxZs/WCvSxyB5ip6vQcHiavu/kjr0HtNciPX/guXvWENkg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@expo/ngrok-bin-linux-x64": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-x64/-/ngrok-bin-linux-x64-2.3.40.tgz", + "integrity": "sha512-yOuwpOmMe6RGnk9ninlM7Zg1EiF81ptFOcFmT61PDOA4gK8/ttZKTMkDQiq0DZdcXUyE0HCr83EglJZTnHIzPA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@expo/ngrok-bin-sunos-x64": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-sunos-x64/-/ngrok-bin-sunos-x64-2.3.40.tgz", + "integrity": "sha512-0itEIQ7KsxRbF9nJk6tt0Ey+9TDC5H7krOsy3t7DPx01EvtaiEdMyhmE1XWjBtwr8+BaY9CpEhUWkx4iCcE4cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ] + }, + "node_modules/@expo/ngrok-bin-win32-ia32": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-win32-ia32/-/ngrok-bin-win32-ia32-2.3.40.tgz", + "integrity": "sha512-RAunwOAskfU0R5mYlxxB+bihLJ4nLRx5/x+q5nIq1muYmaqLvGtkQQHZKzgHJANJ7ZIbzfJY57IN2UICpibgIQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@expo/ngrok-bin-win32-x64": { + "version": "2.3.40", + "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-win32-x64/-/ngrok-bin-win32-x64-2.3.40.tgz", + "integrity": "sha512-a8xtUxX/Ftp2ho+/+VR5GCg0ttP9MNzYj58TVjfiKMkl4mVrbFVIzEinRzmy7PhiOWxqGQSCOdzEfa6C2G4nEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@expo/ngrok/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/@expo/osascript": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.1.6.tgz", @@ -2920,6 +3102,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@ide/backoff": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz", + "integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==", + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -4357,6 +4545,18 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "license": "MIT" }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -4449,6 +4649,18 @@ "@supabase/storage-js": "2.7.1" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -4500,6 +4712,18 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -4553,6 +4777,12 @@ "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", "license": "MIT" }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4606,6 +4836,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.13.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz", @@ -4658,6 +4897,15 @@ "@types/react": "^18" } }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -4672,9 +4920,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", - "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -4967,9 +5215,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5204,6 +5452,19 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/ast-types": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", @@ -5478,6 +5739,12 @@ "@babel/core": "^7.0.0" } }, + "node_modules/badgin": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz", + "integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5722,6 +5989,48 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6070,6 +6379,18 @@ "node": ">=6" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6516,6 +6837,33 @@ "node": ">=0.10" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -6574,6 +6922,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -6600,6 +6957,23 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/del": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", @@ -7268,6 +7642,15 @@ "react-native": "*" } }, + "node_modules/expo-application": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-6.0.2.tgz", + "integrity": "sha512-qcj6kGq3mc7x5yIb5KxESurFTJCoEKwNEL34RdPEvTB/xhl7SeVZlu05sZBqxB1V4Ryzq/LsCb7NHNfBbb3L7A==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-asset": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.0.4.tgz", @@ -7362,6 +7745,44 @@ "expo": "*" } }, + "node_modules/expo-device": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-7.0.2.tgz", + "integrity": "sha512-0PkTixE4Qi8VQBjixnj4aw2f6vE4tUZH7GK8zHROGKlBypZKcWmsA+W/Vp3RC5AyREjX71pO/hjKTSo/vF0E2w==", + "license": "MIT", + "dependencies": { + "ua-parser-js": "^0.7.33" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-device/node_modules/ua-parser-js": { + "version": "0.7.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz", + "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/expo-eas-client": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.13.3.tgz", @@ -7445,6 +7866,15 @@ "react-native": "*" } }, + "node_modules/expo-location": { + "version": "18.0.7", + "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-18.0.7.tgz", + "integrity": "sha512-EPN5yTtDCH3EMiJpJFCWuD+vJkhVbl1j3tMYo3wkhGJN7xlvZf6naLXj8vYntdXSa5M40KjOgjaRiUUqf6+dXw==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-manifests": { "version": "0.15.7", "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.15.7.tgz", @@ -7522,6 +7952,26 @@ "invariant": "^2.2.4" } }, + "node_modules/expo-notifications": { + "version": "0.29.13", + "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.29.13.tgz", + "integrity": "sha512-GHye6XeI1uEeVttJO/hGwUyA5cgQsxR3mi5q37yOE7cZN3cMj36pIfEEmjXEr0nWIWSzoJ0w8c2QxNj5xfP1pA==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.6.4", + "@ide/backoff": "^1.0.0", + "abort-controller": "^3.0.0", + "assert": "^2.0.0", + "badgin": "^1.1.5", + "expo-application": "~6.0.2", + "expo-constants": "~17.0.5" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-router": { "version": "4.0.17", "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-4.0.17.tgz", @@ -7984,9 +8434,9 @@ "license": "MIT" }, "node_modules/flow-parser": { - "version": "0.262.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.262.0.tgz", - "integrity": "sha512-K3asSw4s2/sRoUC4xD2OfGi04gdYCCFRgkcwEXi5JyfFhS0HrFWLcDPp55ttv95OY5970WKl4T+7hWrnuOAUMQ==", + "version": "0.263.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.263.0.tgz", + "integrity": "sha512-F0Tr7SUvZ4BQYglFOkr8rCTO5FPjCwMhm/6i57h40F80Oz/hzzkqte4lGO0vGJ7THQonuXcTyYqCdKkAwt5d2w==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -8316,6 +8766,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -8450,6 +8925,12 @@ "dev": true, "license": "MIT" }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -8490,6 +8971,19 @@ "node": ">= 6" } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -8846,6 +9340,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10227,6 +10737,12 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -10322,6 +10838,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -10789,6 +11314,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -11368,6 +11902,15 @@ "node": ">=4" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -11653,6 +12196,18 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-package-arg": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", @@ -11735,6 +12290,51 @@ "node": ">=0.10.0" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -11911,6 +12511,15 @@ "node": ">=0.10.0" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -12518,6 +13127,18 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13199,6 +13820,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -13236,6 +13863,18 @@ "node": ">=10" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -14468,9 +15107,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.12", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.12.tgz", - "integrity": "sha512-jDLYqo7oF8tJIttjXO6jBY5Hk8p3A8W4ttih7cCEq64fQFWmgJ4VqAQjKr7WwIDlmXKEc6QeoRb5ecjZ+2afcg==", + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.13.tgz", + "integrity": "sha512-JG3pBixF6kx2o0Yfz2K6pqh72DpwTI08nooHd06tcj5WyIt5SsSiUYqRT+kemrGUNSuSzVhwfZ28aO8gogajNQ==", "dev": true, "license": "MIT", "peer": true, @@ -15486,6 +16125,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 829cffd..aedb769 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "test": "jest --watchAll", "lint": "expo lint", @@ -28,8 +28,8 @@ "expo": "~52.0.28", "expo-apple-authentication": "~7.1.3", "expo-blur": "~14.0.3", - "expo-constants": "~17.0.5", - "expo-dev-client": "~5.0.10", + "expo-constants": "~17.0.7", + "expo-dev-client": "~5.0.12", "expo-font": "~13.0.3", "expo-haptics": "~14.0.1", "expo-insights": "~0.8.2", @@ -52,7 +52,11 @@ "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", "react-native-web": "~0.19.13", - "react-native-webview": "13.12.5" + "react-native-webview": "13.12.5", + "@expo/ngrok": "4.1.0", + "expo-notifications": "~0.29.13", + "expo-device": "~7.0.2", + "expo-location": "~18.0.7" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/services/PushNotificationManager.tsx b/services/PushNotificationManager.tsx new file mode 100644 index 0000000..8b63869 --- /dev/null +++ b/services/PushNotificationManager.tsx @@ -0,0 +1,112 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Alert, Platform } from 'react-native'; +import * as Device from 'expo-device'; +import * as Notifications from 'expo-notifications'; +import Constants from 'expo-constants'; +import { NotificationMessage } from '@/constants/Types'; + +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: false, + shouldSetBadge: false, + }), +}); + +export const sendPushNotification = async(expoPushToken: string | null, notification: NotificationMessage) => { + if (!expoPushToken) { + Alert.alert('Error', 'No push token found.'); + return; + } + const message = { + to: expoPushToken, + sound: notification.sound ?? 'default', + title: notification.title, + body: notification.body, + data: notification.data ?? {}, + }; + try { + const response = await fetch('https://exp.host/--/api/v2/push/send', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Accept-encoding': 'gzip, deflate', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(message), + }); + const result = await response.json(); + } catch (error) { + console.error('Error sending push notification:', error); + Alert.alert('Error', 'Failed to send push notification.'); + } +}; + +const handleRegistrationError = (errorMessage: string) => { + alert(errorMessage); + throw new Error(errorMessage); +}; + +async function registerForPushNotificationsAsync() { + let token; + + if (Platform.OS === 'android') { + await Notifications.setNotificationChannelAsync('default', { + name: 'default', + importance: Notifications.AndroidImportance.MAX, + vibrationPattern: [0, 250, 250, 250], + lightColor: '#FF231F7C', + }); + } + + if (Device.isDevice) { + const { status: existingStatus } = await Notifications.getPermissionsAsync(); + let finalStatus = existingStatus; + if (existingStatus !== 'granted') { + const { status } = await Notifications.requestPermissionsAsync(); + finalStatus = status; + } + if (finalStatus !== 'granted') { + alert('Failed to get push token for push notification!'); + return; + } + const projectId = Constants.expoConfig?.extra?.eas?.projectId; + if (!projectId) { + alert('Project ID not found'); + return; + } + token = (await Notifications.getExpoPushTokenAsync({ projectId })).data; + } else { + alert('Must use physical device for Push Notifications'); + } + + return token; +} + +const PushNotificationManager = ({ children }: { children: React.ReactNode }) => { + const [expoPushToken, setExpoPushToken] = useState(''); + const [notification, setNotification] = useState(undefined); + const notificationListener = useRef(); + const responseListener = useRef(); + + useEffect(() => { + registerForPushNotificationsAsync().then(token => setExpoPushToken(token)); + + notificationListener.current = Notifications.addNotificationReceivedListener(notification => { + setNotification(notification); + }); + + responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { + console.log(response); + // Handle notification response here + }); + + return () => { + Notifications.removeNotificationSubscription(notificationListener.current!); + Notifications.removeNotificationSubscription(responseListener.current!); + }; + }, []); + + return <>{children}; +} +export default PushNotificationManager;