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;