notifications ok

This commit is contained in:
Leon Morival 2026-04-19 18:59:59 +02:00
parent 9f7dabbe86
commit 823059d630
18 changed files with 642 additions and 21 deletions

View File

@ -3,4 +3,4 @@ EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=
EXPO_PUBLIC_FIREBASE_PROJECT_ID= EXPO_PUBLIC_FIREBASE_PROJECT_ID=
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET= EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
EXPO_PUBLIC_FIREBASE_APP_ID= EXPO_PUBLIC_FIREBASE_APP_ID=

View File

@ -10,7 +10,7 @@
"newArchEnabled": true, "newArchEnabled": true,
"ios": { "ios": {
"supportsTablet": true, "supportsTablet": true,
"bundleIdentifier": "com.pierre69007.expostarter" "bundleIdentifier": "com.starter.expo"
}, },
"android": { "android": {
"adaptiveIcon": { "adaptiveIcon": {
@ -21,7 +21,8 @@
}, },
"edgeToEdgeEnabled": true, "edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false, "predictiveBackGestureEnabled": false,
"package": "com.pierre69007.expostarter" "googleServicesFile": "./google-services.json",
"package": "com.starter.expo"
}, },
"web": { "web": {
"output": "static", "output": "static",
@ -29,6 +30,7 @@
}, },
"plugins": [ "plugins": [
"expo-router", "expo-router",
"expo-notifications",
[ [
"expo-splash-screen", "expo-splash-screen",
{ {
@ -45,6 +47,13 @@
"experiments": { "experiments": {
"typedRoutes": true, "typedRoutes": true,
"reactCompiler": true "reactCompiler": true
} },
"extra": {
"router": {},
"eas": {
"projectId": "3e92de71-0ed6-43b0-903b-e020d4b110b5"
}
},
"owner": "leonmorival"
} }
} }

View File

@ -1,12 +1,21 @@
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Redirect, router } from 'expo-router'; import { Redirect, router } from 'expo-router';
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native'; import { useState } from 'react';
import {
ActivityIndicator,
Alert,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';
import { Avatar } from '@/components/common/Avatar'; import { Avatar } from '@/components/common/Avatar';
import { Catamaran, FunnelSans } from '@/constants/fonts'; import { Catamaran, FunnelSans } from '@/constants/fonts';
import { Palette } from '@/constants/palette'; import { Palette } from '@/constants/palette';
import { Radius, Spacing } from '@/constants/styles'; import { Radius, Spacing } from '@/constants/styles';
import { useAuth } from '@/providers/AuthProvider'; import { useAuth } from '@/providers/AuthProvider';
import { sendTestPushNotification } from '@/services/notifications';
function SettingsRow({ function SettingsRow({
label, label,
@ -34,6 +43,7 @@ function SettingsRow({
export default function SettingsScreen() { export default function SettingsScreen() {
const { initializing, user } = useAuth(); const { initializing, user } = useAuth();
const [sendingNotification, setSendingNotification] = useState(false);
if (initializing) { if (initializing) {
return ( return (
@ -47,6 +57,28 @@ export default function SettingsScreen() {
return <Redirect href="/login" />; return <Redirect href="/login" />;
} }
const registeredPushTokens = user.expoPushTokens?.length ?? 0;
async function handleSendTestNotification() {
try {
setSendingNotification(true);
await sendTestPushNotification();
Alert.alert(
'Notification envoyee',
'Une notification push de test a ete demandee.'
);
} catch (error) {
const message =
error instanceof Error
? error.message
: "Impossible d'envoyer la notification.";
Alert.alert('Erreur', message);
} finally {
setSendingNotification(false);
}
}
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.header}> <View style={styles.header}>
@ -77,6 +109,30 @@ export default function SettingsScreen() {
/> />
</View> </View>
</View> </View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Notifications</Text>
<View style={styles.sectionCard}>
<SettingsRow
description={
registeredPushTokens > 0
? `${registeredPushTokens} appareil(x) enregistre(s).`
: 'Aucun appareil enregistre pour les notifications.'
}
label="Etat des notifications"
onPress={() => undefined}
/>
<SettingsRow
description={
sendingNotification
? 'Envoi en cours...'
: 'Envoyer une notification push de test.'
}
label="Tester une notification"
onPress={handleSendTestNotification}
/>
</View>
</View>
</View> </View>
); );
} }
@ -139,7 +195,7 @@ const styles = StyleSheet.create({
sectionCard: { sectionCard: {
borderWidth: 1, borderWidth: 1,
borderColor: Palette.cream, borderColor: Palette.cream,
borderRadius: Radius.xl, borderRadius: Radius.sm,
backgroundColor: Palette.white, backgroundColor: Palette.white,
overflow: 'hidden', overflow: 'hidden',
}, },
@ -148,7 +204,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
gap: Spacing.md, gap: Spacing.md,
paddingHorizontal: Spacing.md, paddingHorizontal: Spacing.md,
paddingVertical: Spacing.md, paddingVertical: Spacing.sm,
}, },
rowContent: { rowContent: {
flex: 1, flex: 1,

View File

@ -12,10 +12,12 @@
"@react-navigation/native": "^7.1.8", "@react-navigation/native": "^7.1.8",
"expo": "~54.0.33", "expo": "~54.0.33",
"expo-constants": "~18.0.13", "expo-constants": "~18.0.13",
"expo-device": "~8.0.10",
"expo-font": "~14.0.11", "expo-font": "~14.0.11",
"expo-haptics": "~15.0.8", "expo-haptics": "~15.0.8",
"expo-image": "~3.0.11", "expo-image": "~3.0.11",
"expo-linking": "~8.0.11", "expo-linking": "~8.0.11",
"expo-notifications": "~0.32.16",
"expo-router": "~6.0.23", "expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13", "expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9", "expo-status-bar": "~3.0.9",
@ -413,6 +415,8 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@ide/backoff": ["@ide/backoff@1.0.0", "", {}, "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="], "@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="],
@ -705,6 +709,8 @@
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
"assert": ["assert@2.1.0", "", { "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" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="],
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"async-limiter": ["async-limiter@1.0.1", "", {}, "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="], "async-limiter": ["async-limiter@1.0.1", "", {}, "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="],
@ -737,6 +743,8 @@
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="], "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
"badgin": ["badgin@1.2.3", "", {}, "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
@ -941,10 +949,14 @@
"expo": ["expo@54.0.33", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", "@expo/config": "~12.0.13", "@expo/config-plugins": "~54.0.4", "@expo/devtools": "0.1.8", "@expo/fingerprint": "0.15.4", "@expo/metro": "~54.2.0", "@expo/metro-config": "54.0.14", "@expo/vector-icons": "^15.0.3", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~54.0.10", "expo-asset": "~12.0.12", "expo-constants": "~18.0.13", "expo-file-system": "~19.0.21", "expo-font": "~14.0.11", "expo-keep-awake": "~15.0.8", "expo-modules-autolinking": "3.0.24", "expo-modules-core": "3.0.29", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw=="], "expo": ["expo@54.0.33", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", "@expo/config": "~12.0.13", "@expo/config-plugins": "~54.0.4", "@expo/devtools": "0.1.8", "@expo/fingerprint": "0.15.4", "@expo/metro": "~54.2.0", "@expo/metro-config": "54.0.14", "@expo/vector-icons": "^15.0.3", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~54.0.10", "expo-asset": "~12.0.12", "expo-constants": "~18.0.13", "expo-file-system": "~19.0.21", "expo-font": "~14.0.11", "expo-keep-awake": "~15.0.8", "expo-modules-autolinking": "3.0.24", "expo-modules-core": "3.0.29", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw=="],
"expo-application": ["expo-application@7.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q=="],
"expo-asset": ["expo-asset@12.0.12", "", { "dependencies": { "@expo/image-utils": "^0.8.8", "expo-constants": "~18.0.12" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ=="], "expo-asset": ["expo-asset@12.0.12", "", { "dependencies": { "@expo/image-utils": "^0.8.8", "expo-constants": "~18.0.12" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ=="],
"expo-constants": ["expo-constants@18.0.13", "", { "dependencies": { "@expo/config": "~12.0.13", "@expo/env": "~2.0.8" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ=="], "expo-constants": ["expo-constants@18.0.13", "", { "dependencies": { "@expo/config": "~12.0.13", "@expo/env": "~2.0.8" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ=="],
"expo-device": ["expo-device@8.0.10", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-jd5BxjaF7382JkDMaC+P04aXXknB2UhWaVx5WiQKA05ugm/8GH5uaz9P9ckWdMKZGQVVEOC8MHaUADoT26KmFA=="],
"expo-file-system": ["expo-file-system@19.0.21", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg=="], "expo-file-system": ["expo-file-system@19.0.21", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg=="],
"expo-font": ["expo-font@14.0.11", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg=="], "expo-font": ["expo-font@14.0.11", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg=="],
@ -961,6 +973,8 @@
"expo-modules-core": ["expo-modules-core@3.0.29", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-LzipcjGqk8gvkrOUf7O2mejNWugPkf3lmd9GkqL9WuNyeN2fRwU0Dn77e3ZUKI3k6sI+DNwjkq4Nu9fNN9WS7Q=="], "expo-modules-core": ["expo-modules-core@3.0.29", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-LzipcjGqk8gvkrOUf7O2mejNWugPkf3lmd9GkqL9WuNyeN2fRwU0Dn77e3ZUKI3k6sI+DNwjkq4Nu9fNN9WS7Q=="],
"expo-notifications": ["expo-notifications@0.32.16", "", { "dependencies": { "@expo/image-utils": "^0.8.8", "@ide/backoff": "^1.0.0", "abort-controller": "^3.0.0", "assert": "^2.0.0", "badgin": "^1.1.5", "expo-application": "~7.0.8", "expo-constants": "~18.0.13" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-QQD/UA6v7LgvwIJ+tS7tSvqJZkdp0nCSj9MxsDk/jU1GttYdK49/5L2LvE/4U0H7sNBz1NZAyhDZozg8xgBLXw=="],
"expo-router": ["expo-router@6.0.23", "", { "dependencies": { "@expo/metro-runtime": "^6.1.2", "@expo/schema-utils": "^0.1.8", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/native": "^7.1.8", "@react-navigation/native-stack": "^7.3.16", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-server": "^1.0.5", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@react-navigation/drawer": "^7.5.0", "@testing-library/react-native": ">= 12.0.0", "expo": "*", "expo-constants": "^18.0.13", "expo-linking": "^8.0.11", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-qCxVAiCrCyu0npky6azEZ6dJDMt77OmCzEbpF6RbUTlfkaCA417LvY14SBkk0xyGruSxy/7pvJOI6tuThaUVCA=="], "expo-router": ["expo-router@6.0.23", "", { "dependencies": { "@expo/metro-runtime": "^6.1.2", "@expo/schema-utils": "^0.1.8", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/native": "^7.1.8", "@react-navigation/native-stack": "^7.3.16", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-server": "^1.0.5", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@react-navigation/drawer": "^7.5.0", "@testing-library/react-native": ">= 12.0.0", "expo": "*", "expo-constants": "^18.0.13", "expo-linking": "^8.0.11", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-qCxVAiCrCyu0npky6azEZ6dJDMt77OmCzEbpF6RbUTlfkaCA417LvY14SBkk0xyGruSxy/7pvJOI6tuThaUVCA=="],
"expo-server": ["expo-server@1.0.5", "", {}, "sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA=="], "expo-server": ["expo-server@1.0.5", "", {}, "sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA=="],
@ -1115,6 +1129,8 @@
"invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
"is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
"is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
@ -1149,6 +1165,8 @@
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="],
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
@ -1379,6 +1397,8 @@
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
"object.assign": ["object.assign@4.1.7", "", { "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" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], "object.assign": ["object.assign@4.1.7", "", { "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" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
@ -1713,7 +1733,7 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="], "ua-parser-js": ["ua-parser-js@0.7.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
@ -1745,6 +1765,8 @@
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], "uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
@ -1941,6 +1963,8 @@
"fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="], "fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
"fbjs/ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],

View File

@ -42,8 +42,8 @@ const copyByMode = {
export function AuthForm({ mode }: AuthFormProps) { export function AuthForm({ mode }: AuthFormProps) {
const copy = copyByMode[mode]; const copy = copyByMode[mode];
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [email, setEmail] = useState(''); const [email, setEmail] = useState('leon.morival@gmail.com');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('Furioza98*');
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);

21
eas.json Normal file
View File

@ -0,0 +1,21 @@
{
"cli": {
"version": ">= 18.7.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

217
firebase-debug.log Normal file
View File

@ -0,0 +1,217 @@
[debug] [2026-04-19T16:48:31.474Z] ----------------------------------------------------------------------
[debug] [2026-04-19T16:48:31.476Z] Command: /usr/local/bin/firebase /home/leon/.cache/firebase/tools/lib/node_modules/firebase-tools/lib/bin/firebase emulators:start --only functions
[debug] [2026-04-19T16:48:31.476Z] CLI Version: 15.15.0
[debug] [2026-04-19T16:48:31.476Z] Platform: linux
[debug] [2026-04-19T16:48:31.476Z] Node Version: v20.18.2
[debug] [2026-04-19T16:48:31.476Z] Time: Sun Apr 19 2026 18:48:31 GMT+0200 (GMT+02:00)
[debug] [2026-04-19T16:48:31.476Z] ----------------------------------------------------------------------
[debug]
[debug] [2026-04-19T16:48:31.907Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
[debug] [2026-04-19T16:48:31.907Z] > authorizing via signed-in user (leon.morival@gmail.com)
[info] i emulators: Starting emulators: functions {"metadata":{"emulator":{"name":"hub"},"message":"Starting emulators: functions"}}
[debug] [2026-04-19T16:48:31.917Z] [logging] Logging Emulator only supports listening on one address (127.0.0.1). Not listening on ::1
[debug] [2026-04-19T16:48:31.917Z] assigned listening specs for emulators {"user":{"ui":[{"address":"0.0.0.0","family":"IPv4","port":4000}],"hub":[{"address":"127.0.0.1","family":"IPv4","port":4400},{"address":"::1","family":"IPv6","port":4400}],"logging":[{"address":"127.0.0.1","family":"IPv4","port":4500}]},"metadata":{"message":"assigned listening specs for emulators"}}
[debug] [2026-04-19T16:48:31.919Z] Write emulator hub locator at /tmp/hub-expo-starter-9daef.json
[debug] [2026-04-19T16:48:31.923Z] [eventarc] Eventarc Emulator only supports listening on one address (127.0.0.1). Not listening on ::1
[debug] [2026-04-19T16:48:31.923Z] [tasks] Cloud Tasks Emulator only supports listening on one address (127.0.0.1). Not listening on ::1
[debug] [2026-04-19T16:48:31.923Z] late-assigned ports for functions and eventarc emulators {"user":{"ui":[{"address":"0.0.0.0","family":"IPv4","port":4000}],"hub":[{"address":"127.0.0.1","family":"IPv4","port":4400},{"address":"::1","family":"IPv6","port":4400}],"logging":[{"address":"127.0.0.1","family":"IPv4","port":4500}],"functions":[{"address":"0.0.0.0","family":"IPv4","port":5001}],"eventarc":[{"address":"127.0.0.1","family":"IPv4","port":9299}],"tasks":[{"address":"127.0.0.1","family":"IPv4","port":9499}]},"metadata":{"message":"late-assigned ports for functions and eventarc emulators"}}
[warn] ⚠ functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: apphosting, auth, firestore, database, hosting, pubsub, storage, dataconnect {"metadata":{"emulator":{"name":"functions"},"message":"The following emulators are not running, calls to these services from the Functions emulator will affect production: \u001b[1mapphosting, auth, firestore, database, hosting, pubsub, storage, dataconnect\u001b[22m"}}
[debug] [2026-04-19T16:48:31.939Z] defaultcredentials: writing to file /home/leon/.config/firebase/leon_morival_gmail.com_application_default_credentials.json
[debug] [2026-04-19T16:48:31.940Z] Setting GAC to /home/leon/.config/firebase/leon_morival_gmail.com_application_default_credentials.json {"metadata":{"emulator":{"name":"functions"},"message":"Setting GAC to /home/leon/.config/firebase/leon_morival_gmail.com_application_default_credentials.json"}}
[debug] [2026-04-19T16:48:31.940Z] Checked if tokens are valid: true, expires at: 1776620760607
[debug] [2026-04-19T16:48:31.940Z] Checked if tokens are valid: true, expires at: 1776620760607
[debug] [2026-04-19T16:48:31.941Z] >>> [apiv2][query] GET https://firebase.googleapis.com/v1beta1/projects/expo-starter-9daef/adminSdkConfig [none]
[debug] [2026-04-19T16:48:32.358Z] <<< [apiv2][status] GET https://firebase.googleapis.com/v1beta1/projects/expo-starter-9daef/adminSdkConfig 200
[debug] [2026-04-19T16:48:32.358Z] <<< [apiv2][body] GET https://firebase.googleapis.com/v1beta1/projects/expo-starter-9daef/adminSdkConfig {"projectId":"expo-starter-9daef","storageBucket":"expo-starter-9daef.firebasestorage.app"}
[info] i functions: Watching "/home/leon/Workspace/perso/starter/expo-starter/functions" for Cloud Functions... {"metadata":{"emulator":{"name":"functions"},"message":"Watching \"/home/leon/Workspace/perso/starter/expo-starter/functions\" for Cloud Functions..."}}
[debug] [2026-04-19T16:48:32.373Z] Validating nodejs source
[debug] [2026-04-19T16:48:32.726Z] > [functions] package.json contents: {
"name": "functions",
"scripts": {
"lint": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.ts src",
"build": "tsc",
"build:watch": "tsc --watch",
"serve": "npm run build && firebase emulators:start --only functions",
"shell": "npm run build && firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "24"
},
"main": "lib/index.js",
"dependencies": {
"firebase-admin": "^13.6.0",
"firebase-functions": "^7.0.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "^5.12.0",
"eslint": "^8.9.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-import": "^2.25.4",
"firebase-functions-test": "^3.4.1",
"typescript": "^6.0.0"
},
"private": true
}
[debug] [2026-04-19T16:48:32.726Z] Building nodejs source
[debug] [2026-04-19T16:48:32.727Z] Failed to find version of module node: reached end of search path /home/leon/Workspace/perso/starter/expo-starter/functions/node_modules
[warn] ⚠ functions: You've requested "node" version "24", but the standalone Firebase CLI comes with bundled Node "20".
[info] ✔ functions: To use a different Node.js version, consider removing the standalone Firebase CLI and switching to "firebase-tools" on npm.
[debug] [2026-04-19T16:48:32.728Z] Could not find functions.yaml. Must use http discovery
[debug] [2026-04-19T16:48:32.730Z] Found firebase-functions binary at '/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/.bin/firebase-functions'
[info] Serving at port 8679
[debug] [2026-04-19T16:48:33.073Z] Got response from /__/functions.yaml {"endpoints":{"deleteUserAuthOnUserDocumentDeleted":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":10,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"eventTrigger":{"eventType":"google.cloud.firestore.document.v1.deleted","eventFilters":{"database":"(default)","namespace":"(default)"},"eventFilterPathPatterns":{"document":"users/{uid}"},"retry":false},"entryPoint":"deleteUserAuthOnUserDocumentDeleted"},"sendTestPushNotification":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":10,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"callableTrigger":{},"entryPoint":"sendTestPushNotification"}},"specVersion":"v1alpha1","requiredAPIs":[],"extensions":{}}
[debug] [2026-04-19T16:48:34.822Z] [work-queue] {"queuedWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:48:34.822Z"],"queueLength":1,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:48:34.822Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:48:34.822Z"],"workRunningCount":1}
[debug] [2026-04-19T16:48:34.823Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":[],"workRunningCount":0}
[info] ✔ functions: Loaded functions definitions from source: deleteUserAuthOnUserDocumentDeleted, sendTestPushNotification. {"metadata":{"emulator":{"name":"functions"},"message":"Loaded functions definitions from source: deleteUserAuthOnUserDocumentDeleted, sendTestPushNotification."}}
[info] i functions[us-central1-deleteUserAuthOnUserDocumentDeleted]: function ignored because the firestore emulator does not exist or is not running. {"metadata":{"emulator":{"name":"functions"},"message":"function ignored because the firestore emulator does not exist or is not running."}}
[info] ✔ functions[us-central1-sendTestPushNotification]: http function initialized (http://127.0.0.1:5001/expo-starter-9daef/us-central1/sendTestPushNotification). {"metadata":{"emulator":{"name":"functions"},"message":"\u001b[1mhttp\u001b[22m function initialized (http://127.0.0.1:5001/expo-starter-9daef/us-central1/sendTestPushNotification)."}}
[debug] [2026-04-19T16:48:37.112Z] Could not find VSCode notification endpoint: FetchError: request to http://localhost:40001/vscode/notify failed, reason: connect ECONNREFUSED 127.0.0.1:40001. If you are not running the Firebase SQL Connect VSCode extension, this is expected and not an issue.
[info]
┌─────────────────────────────────────────────────────────────┐
│ ✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://127.0.0.1:4000/ │
└─────────────────────────────────────────────────────────────┘
┌───────────┬──────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├───────────┼──────────────┼─────────────────────────────────┤
│ Functions │ 0.0.0.0:5001 │ http://127.0.0.1:4000/functions │
└───────────┴──────────────┴─────────────────────────────────┘
Emulator Hub host: 127.0.0.1 port: 4400
Other reserved ports: 4500
Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
[debug] [2026-04-19T16:48:40.336Z] [work-queue] {"queuedWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:48:40.336Z"],"queueLength":1,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:48:40.337Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:48:40.336Z"],"workRunningCount":1}
[debug] [2026-04-19T16:48:40.337Z] Accepted request POST /expo-starter-9daef/us-central1/sendTestPushNotification --> us-central1-sendTestPushNotification
[debug] [2026-04-19T16:48:40.338Z] [functions] Runtime ready! Sending request! {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Runtime ready! Sending request!"}}
[debug] [2026-04-19T16:48:40.339Z] [functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/ {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/"}}
[debug] [2026-04-19T16:48:40.344Z] [worker-pool] addWorker(us-central1-sendTestPushNotification) {"metadata":{"emulator":{"name":"functions"},"message":"[worker-pool] addWorker(us-central1-sendTestPushNotification)"}}
[debug] [2026-04-19T16:48:40.345Z] [worker-pool] Adding worker with key us-central1-sendTestPushNotification, total=1 {"metadata":{"emulator":{"name":"functions"},"message":"[worker-pool] Adding worker with key us-central1-sendTestPushNotification, total=1"}}
[debug] [2026-04-19T16:48:40.707Z] [runtime-status] [63867] Resolved module firebase-admin {"declared":true,"installed":true,"version":"13.8.0","resolution":"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-admin/lib/index.js"} {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Resolved module firebase-admin {\"declared\":true,\"installed\":true,\"version\":\"13.8.0\",\"resolution\":\"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-admin/lib/index.js\"}"}}
[debug] [2026-04-19T16:48:40.707Z] [runtime-status] [63867] Resolved module firebase-functions {"declared":true,"installed":true,"version":"7.2.5","resolution":"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-functions/lib/v2/index.js"} {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Resolved module firebase-functions {\"declared\":true,\"installed\":true,\"version\":\"7.2.5\",\"resolution\":\"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-functions/lib/v2/index.js\"}"}}
[debug] [2026-04-19T16:48:40.707Z] [runtime-status] [63867] Outgoing network have been stubbed. [{"name":"http","status":"mocked"},{"name":"http","status":"mocked"},{"name":"https","status":"mocked"},{"name":"https","status":"mocked"},{"name":"net","status":"mocked"}] {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Outgoing network have been stubbed. [{\"name\":\"http\",\"status\":\"mocked\"},{\"name\":\"http\",\"status\":\"mocked\"},{\"name\":\"https\",\"status\":\"mocked\"},{\"name\":\"https\",\"status\":\"mocked\"},{\"name\":\"net\",\"status\":\"mocked\"}]"}}
[debug] [2026-04-19T16:48:40.707Z] [runtime-status] [63867] Resolved module firebase-functions {"declared":true,"installed":true,"version":"7.2.5","resolution":"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-functions/lib/v2/index.js"} {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Resolved module firebase-functions {\"declared\":true,\"installed\":true,\"version\":\"7.2.5\",\"resolution\":\"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-functions/lib/v2/index.js\"}"}}
[debug] [2026-04-19T16:48:40.707Z] [runtime-status] [63867] Detected firebase-functions v7+, skipping legacy stubs. {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Detected firebase-functions v7+, skipping legacy stubs."}}
[debug] [2026-04-19T16:48:40.707Z] [runtime-status] [63867] Resolved module firebase-admin {"declared":true,"installed":true,"version":"13.8.0","resolution":"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-admin/lib/index.js"} {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Resolved module firebase-admin {\"declared\":true,\"installed\":true,\"version\":\"13.8.0\",\"resolution\":\"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-admin/lib/index.js\"}"}}
[debug] [2026-04-19T16:48:40.727Z] [runtime-status] [63867] Resolved module firebase-functions {"declared":true,"installed":true,"version":"7.2.5","resolution":"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-functions/lib/v2/index.js"} {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Resolved module firebase-functions {\"declared\":true,\"installed\":true,\"version\":\"7.2.5\",\"resolution\":\"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-functions/lib/v2/index.js\"}"}}
[debug] [2026-04-19T16:48:40.798Z] [runtime-status] [63867] firebase-admin has been stubbed. {"adminResolution":{"declared":true,"installed":true,"version":"13.8.0","resolution":"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-admin/lib/index.js"}} {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] firebase-admin has been stubbed. {\"adminResolution\":{\"declared\":true,\"installed\":true,\"version\":\"13.8.0\",\"resolution\":\"/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/firebase-admin/lib/index.js\"}}"}}
[debug] [2026-04-19T16:48:40.803Z] [runtime-status] [63867] Functions runtime initialized. {"cwd":"/home/leon/Workspace/perso/starter/expo-starter/functions","node_version":"20.18.2"} {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Functions runtime initialized. {\"cwd\":\"/home/leon/Workspace/perso/starter/expo-starter/functions\",\"node_version\":\"20.18.2\"}"}}
[debug] [2026-04-19T16:48:40.803Z] [runtime-status] [63867] Listening to port: /tmp/fire_emu_5ad7eed774b9d705.sock {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[runtime-status] [63867] Listening to port: /tmp/fire_emu_5ad7eed774b9d705.sock"}}
[debug] [2026-04-19T16:48:40.855Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE"}}
[debug] [2026-04-19T16:48:40.855Z] [worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification) {"metadata":{"emulator":{"name":"functions"},"message":"[worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification)"}}
[info] i functions: Beginning execution of "us-central1-sendTestPushNotification" {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Beginning execution of \"us-central1-sendTestPushNotification\""}}
[debug] [2026-04-19T16:48:40.856Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY"}}
[info] > {"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"} {"user":{"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"verifications\":{\"app\":\"MISSING\",\"auth\":\"VALID\"},\"logging.googleapis.com/labels\":{\"firebase-log-type\":\"callable-request-verification\"},\"severity\":\"DEBUG\",\"message\":\"Callable request verification passed\"}"}}
[warn] ⚠ Google API requested!
- URL: "https://oauth2.googleapis.com/token"
- Be careful, this may be a production service. {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Google API requested!\n - URL: \"https://oauth2.googleapis.com/token\"\n - Be careful, this may be a production service."}}
[info] > {"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"error","message":"\"fn_xv7aZRAGC92nuZbTRYM:APA91bE...wLvTQTf_J3VmP7wcfgFOmXiYgYo\" is not a valid Expo push token","details":{"error":"DeviceNotRegistered","expoPushToken":"fn_xv7aZRAGC92nuZbTRYM:APA91bE...wLvTQTf_J3VmP7wcfgFOmXiYgYo"}},{"status":"error","message":"Unable to retrieve the FCM server key for the recipient's app. Make sure you have provided a server key as directed by the Expo FCM documentation.","details":{"error":"InvalidCredentials","fault":"developer"}}],"severity":"INFO","message":"Test push notification sent."} {"user":{"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"error","message":"\"fn_xv7aZRAGC92nuZbTRYM:APA91bE...wLvTQTf_J3VmP7wcfgFOmXiYgYo\" is not a valid Expo push token","details":{"error":"DeviceNotRegistered","expoPushToken":"fn_xv7aZRAGC92nuZbTRYM:APA91bE...wLvTQTf_J3VmP7wcfgFOmXiYgYo"}},{"status":"error","message":"Unable to retrieve the FCM server key for the recipient's app. Make sure you have provided a server key as directed by the Expo FCM documentation.","details":{"error":"InvalidCredentials","fault":"developer"}}],"severity":"INFO","message":"Test push notification sent."},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"uid\":\"O8lgsPxjejS7WZYVN1qa8wAZfiA2\",\"response\":[{\"status\":\"error\",\"message\":\"\\\"fn_xv7aZRAGC92nuZbTRYM:APA91bE...wLvTQTf_J3VmP7wcfgFOmXiYgYo\\\" is not a valid Expo push token\",\"details\":{\"error\":\"DeviceNotRegistered\",\"expoPushToken\":\"fn_xv7aZRAGC92nuZbTRYM:APA91bE...wLvTQTf_J3VmP7wcfgFOmXiYgYo\"}},{\"status\":\"error\",\"message\":\"Unable to retrieve the FCM server key for the recipient's app. Make sure you have provided a server key as directed by the Expo FCM documentation.\",\"details\":{\"error\":\"InvalidCredentials\",\"fault\":\"developer\"}}],\"severity\":\"INFO\",\"message\":\"Test push notification sent.\"}"}}
[debug] [2026-04-19T16:48:41.877Z] Finishing up request with event=pause {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=pause"}}
[info] i functions: Finished "us-central1-sendTestPushNotification" in 1021.358853ms {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finished \"us-central1-sendTestPushNotification\" in 1021.358853ms"}}
[debug] [2026-04-19T16:48:41.878Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE"}}
[debug] [2026-04-19T16:48:41.878Z] Finishing up request with event=finish {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=finish"}}
[debug] [2026-04-19T16:48:41.878Z] Finishing up request with event=close {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=close"}}
[debug] [2026-04-19T16:48:41.878Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:50:23.257Z] [work-queue] {"queuedWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:50:23.257Z"],"queueLength":1,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:50:23.257Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:50:23.257Z"],"workRunningCount":1}
[debug] [2026-04-19T16:50:23.257Z] Accepted request POST /expo-starter-9daef/us-central1/sendTestPushNotification --> us-central1-sendTestPushNotification
[debug] [2026-04-19T16:50:23.258Z] [functions] Runtime ready! Sending request! {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Runtime ready! Sending request!"}}
[debug] [2026-04-19T16:50:23.259Z] [functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/ {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/"}}
[debug] [2026-04-19T16:50:23.259Z] [worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification) {"metadata":{"emulator":{"name":"functions"},"message":"[worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification)"}}
[info] i functions: Beginning execution of "us-central1-sendTestPushNotification" {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Beginning execution of \"us-central1-sendTestPushNotification\""}}
[debug] [2026-04-19T16:50:23.260Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY"}}
[info] > {"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"} {"user":{"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"verifications\":{\"app\":\"MISSING\",\"auth\":\"VALID\"},\"logging.googleapis.com/labels\":{\"firebase-log-type\":\"callable-request-verification\"},\"severity\":\"DEBUG\",\"message\":\"Callable request verification passed\"}"}}
[info] > {"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"error","message":"Unable to retrieve the FCM server key for the recipient's app. Make sure you have provided a server key as directed by the Expo FCM documentation.","details":{"error":"InvalidCredentials","fault":"developer"}}],"severity":"INFO","message":"Test push notification sent."} {"user":{"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"error","message":"Unable to retrieve the FCM server key for the recipient's app. Make sure you have provided a server key as directed by the Expo FCM documentation.","details":{"error":"InvalidCredentials","fault":"developer"}}],"severity":"INFO","message":"Test push notification sent."},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"uid\":\"O8lgsPxjejS7WZYVN1qa8wAZfiA2\",\"response\":[{\"status\":\"error\",\"message\":\"Unable to retrieve the FCM server key for the recipient's app. Make sure you have provided a server key as directed by the Expo FCM documentation.\",\"details\":{\"error\":\"InvalidCredentials\",\"fault\":\"developer\"}}],\"severity\":\"INFO\",\"message\":\"Test push notification sent.\"}"}}
[debug] [2026-04-19T16:50:23.660Z] Finishing up request with event=pause {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=pause"}}
[info] i functions: Finished "us-central1-sendTestPushNotification" in 400.343794ms {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finished \"us-central1-sendTestPushNotification\" in 400.343794ms"}}
[debug] [2026-04-19T16:50:23.660Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE"}}
[debug] [2026-04-19T16:50:23.660Z] Finishing up request with event=finish {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=finish"}}
[debug] [2026-04-19T16:50:23.661Z] Finishing up request with event=close {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=close"}}
[debug] [2026-04-19T16:50:23.661Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:54:50.209Z] [work-queue] {"queuedWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:54:50.209Z"],"queueLength":1,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:54:50.209Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:54:50.209Z"],"workRunningCount":1}
[debug] [2026-04-19T16:54:50.210Z] Accepted request POST /expo-starter-9daef/us-central1/sendTestPushNotification --> us-central1-sendTestPushNotification
[debug] [2026-04-19T16:54:50.210Z] [functions] Runtime ready! Sending request! {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Runtime ready! Sending request!"}}
[debug] [2026-04-19T16:54:50.211Z] [functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/ {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/"}}
[debug] [2026-04-19T16:54:50.211Z] [worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification) {"metadata":{"emulator":{"name":"functions"},"message":"[worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification)"}}
[info] i functions: Beginning execution of "us-central1-sendTestPushNotification" {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Beginning execution of \"us-central1-sendTestPushNotification\""}}
[debug] [2026-04-19T16:54:50.211Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY"}}
[info] > {"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"} {"user":{"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"verifications\":{\"app\":\"MISSING\",\"auth\":\"VALID\"},\"logging.googleapis.com/labels\":{\"firebase-log-type\":\"callable-request-verification\"},\"severity\":\"DEBUG\",\"message\":\"Callable request verification passed\"}"}}
[info] > {"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"ok","id":"019da6aa-95b7-71d2-94fb-4eba9aa8650e"}],"severity":"INFO","message":"Test push notification sent."} {"user":{"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"ok","id":"019da6aa-95b7-71d2-94fb-4eba9aa8650e"}],"severity":"INFO","message":"Test push notification sent."},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"uid\":\"O8lgsPxjejS7WZYVN1qa8wAZfiA2\",\"response\":[{\"status\":\"ok\",\"id\":\"019da6aa-95b7-71d2-94fb-4eba9aa8650e\"}],\"severity\":\"INFO\",\"message\":\"Test push notification sent.\"}"}}
[debug] [2026-04-19T16:54:50.715Z] Finishing up request with event=pause {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=pause"}}
[info] i functions: Finished "us-central1-sendTestPushNotification" in 503.61932ms {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finished \"us-central1-sendTestPushNotification\" in 503.61932ms"}}
[debug] [2026-04-19T16:54:50.715Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE"}}
[debug] [2026-04-19T16:54:50.715Z] Finishing up request with event=finish {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=finish"}}
[debug] [2026-04-19T16:54:50.715Z] Finishing up request with event=close {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=close"}}
[debug] [2026-04-19T16:54:50.715Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:55:02.329Z] [work-queue] {"queuedWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:55:02.329Z"],"queueLength":1,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:55:02.329Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":["/expo-starter-9daef/us-central1/sendTestPushNotification-2026-04-19T16:55:02.329Z"],"workRunningCount":1}
[debug] [2026-04-19T16:55:02.330Z] Accepted request POST /expo-starter-9daef/us-central1/sendTestPushNotification --> us-central1-sendTestPushNotification
[debug] [2026-04-19T16:55:02.330Z] [functions] Runtime ready! Sending request! {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Runtime ready! Sending request!"}}
[debug] [2026-04-19T16:55:02.330Z] [functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/ {"metadata":{"emulator":{"name":"functions"},"message":"[functions] Got req.url=/expo-starter-9daef/us-central1/sendTestPushNotification, mapping to path=/"}}
[debug] [2026-04-19T16:55:02.331Z] [worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification) {"metadata":{"emulator":{"name":"functions"},"message":"[worker-pool] submitRequest(triggerId=us-central1-sendTestPushNotification)"}}
[info] i functions: Beginning execution of "us-central1-sendTestPushNotification" {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Beginning execution of \"us-central1-sendTestPushNotification\""}}
[debug] [2026-04-19T16:55:02.331Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: BUSY"}}
[info] > {"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"} {"user":{"verifications":{"app":"MISSING","auth":"VALID"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"DEBUG","message":"Callable request verification passed"},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"verifications\":{\"app\":\"MISSING\",\"auth\":\"VALID\"},\"logging.googleapis.com/labels\":{\"firebase-log-type\":\"callable-request-verification\"},\"severity\":\"DEBUG\",\"message\":\"Callable request verification passed\"}"}}
[info] > {"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"ok","id":"019da6aa-c4a6-75d0-8e77-b9ff5b055321"}],"severity":"INFO","message":"Test push notification sent."} {"user":{"uid":"O8lgsPxjejS7WZYVN1qa8wAZfiA2","response":[{"status":"ok","id":"019da6aa-c4a6-75d0-8e77-b9ff5b055321"}],"severity":"INFO","message":"Test push notification sent."},"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"\u001b[90m> \u001b[39m {\"uid\":\"O8lgsPxjejS7WZYVN1qa8wAZfiA2\",\"response\":[{\"status\":\"ok\",\"id\":\"019da6aa-c4a6-75d0-8e77-b9ff5b055321\"}],\"severity\":\"INFO\",\"message\":\"Test push notification sent.\"}"}}
[debug] [2026-04-19T16:55:02.693Z] Finishing up request with event=pause {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=pause"}}
[info] i functions: Finished "us-central1-sendTestPushNotification" in 361.626998ms {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finished \"us-central1-sendTestPushNotification\" in 361.626998ms"}}
[debug] [2026-04-19T16:55:02.693Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: IDLE"}}
[debug] [2026-04-19T16:55:02.693Z] Finishing up request with event=finish {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=finish"}}
[debug] [2026-04-19T16:55:02.694Z] Finishing up request with event=close {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"Finishing up request with event=close"}}
[debug] [2026-04-19T16:55:02.694Z] [work-queue] {"queuedWork":[],"queueLength":0,"runningWork":[],"workRunningCount":0}
[debug] [2026-04-19T16:57:32.477Z] File /home/leon/Workspace/perso/starter/expo-starter/functions/src/notifications.ts changed, reloading triggers {"metadata":{"emulator":{"name":"functions"},"message":"File /home/leon/Workspace/perso/starter/expo-starter/functions/src/notifications.ts changed, reloading triggers"}}
[debug] [2026-04-19T16:57:33.478Z] Validating nodejs source
[debug] [2026-04-19T16:57:33.929Z] > [functions] package.json contents: {
"name": "functions",
"scripts": {
"lint": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.ts src",
"build": "tsc",
"build:watch": "tsc --watch",
"serve": "npm run build && firebase emulators:start --only functions",
"shell": "npm run build && firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "24"
},
"main": "lib/index.js",
"dependencies": {
"firebase-admin": "^13.6.0",
"firebase-functions": "^7.0.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "^5.12.0",
"eslint": "^8.9.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-import": "^2.25.4",
"firebase-functions-test": "^3.4.1",
"typescript": "^6.0.0"
},
"private": true
}
[debug] [2026-04-19T16:57:33.929Z] Building nodejs source
[debug] [2026-04-19T16:57:33.929Z] Failed to find version of module node: reached end of search path /home/leon/Workspace/perso/starter/expo-starter/functions/node_modules
[warn] ⚠ functions: You've requested "node" version "24", but the standalone Firebase CLI comes with bundled Node "20".
[info] ✔ functions: To use a different Node.js version, consider removing the standalone Firebase CLI and switching to "firebase-tools" on npm.
[debug] [2026-04-19T16:57:33.930Z] Could not find functions.yaml. Must use http discovery
[debug] [2026-04-19T16:57:33.930Z] Found firebase-functions binary at '/home/leon/Workspace/perso/starter/expo-starter/functions/node_modules/.bin/firebase-functions'
[info] Serving at port 8523
[debug] [2026-04-19T16:57:34.271Z] Got response from /__/functions.yaml {"endpoints":{"deleteUserAuthOnUserDocumentDeleted":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":10,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"eventTrigger":{"eventType":"google.cloud.firestore.document.v1.deleted","eventFilters":{"database":"(default)","namespace":"(default)"},"eventFilterPathPatterns":{"document":"users/{uid}"},"retry":false},"entryPoint":"deleteUserAuthOnUserDocumentDeleted"},"sendTestPushNotification":{"availableMemoryMb":null,"timeoutSeconds":null,"minInstances":null,"maxInstances":10,"ingressSettings":null,"concurrency":null,"serviceAccountEmail":null,"vpc":null,"platform":"gcfv2","labels":{},"callableTrigger":{},"entryPoint":"sendTestPushNotification"}},"specVersion":"v1alpha1","requiredAPIs":[],"extensions":{}}
[info] ✔ functions: Loaded functions definitions from source: deleteUserAuthOnUserDocumentDeleted, sendTestPushNotification. {"metadata":{"emulator":{"name":"functions"},"message":"Loaded functions definitions from source: deleteUserAuthOnUserDocumentDeleted, sendTestPushNotification."}}
[debug] [2026-04-19T16:57:38.293Z] [worker-pool] Shutting down IDLE worker (us-central1-sendTestPushNotification) {"metadata":{"emulator":{"name":"functions"},"message":"[worker-pool] Shutting down IDLE worker (us-central1-sendTestPushNotification)"}}
[debug] [2026-04-19T16:57:38.293Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: FINISHING {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: FINISHING"}}
[debug] [2026-04-19T16:57:38.302Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: exited {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: exited"}}
[debug] [2026-04-19T16:57:38.302Z] [worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: FINISHED {"metadata":{"emulator":{"name":"functions"},"function":{"name":"us-central1-sendTestPushNotification"},"extension":{},"message":"[worker-us-central1-sendTestPushNotification-b55f52a6-94a7-4563-9b9b-a70f4e52f589]: FINISHED"}}

View File

@ -12,5 +12,25 @@
"*.local" "*.local"
] ]
} }
] ],
"emulators": {
"auth": {
"host": "0.0.0.0",
"port": 9099
},
"firestore": {
"host": "0.0.0.0",
"port": 8080
},
"functions": {
"host": "0.0.0.0",
"port": 5001
},
"ui": {
"enabled": true,
"host": "0.0.0.0",
"port": 4000
},
"singleProjectMode": true
}
} }

View File

@ -3,3 +3,4 @@ import {setGlobalOptions} from "firebase-functions/v2";
setGlobalOptions({maxInstances: 10}); setGlobalOptions({maxInstances: 10});
export * from "./users"; export * from "./users";
export * from "./notifications";

View File

@ -0,0 +1,90 @@
import {getApps, initializeApp} from "firebase-admin/app";
import {getFirestore} from "firebase-admin/firestore";
import * as logger from "firebase-functions/logger";
import {HttpsError, onCall} from "firebase-functions/v2/https";
const adminApp = getApps().length > 0 ? getApps()[0] : initializeApp();
const db = getFirestore(adminApp);
type UserDocument = {
expoPushTokens?: string[];
};
type ExpoPushMessage = {
to: string;
title: string;
body: string;
data?: Record<string, unknown>;
};
type ExpoPushResponse = {
data?: unknown;
errors?: unknown[];
};
/**
* Sends one or more push notifications through the Expo Push API.
* @param {ExpoPushMessage[]} messages Notification payloads to send.
* @return {Promise<ExpoPushResponse>} Expo push API response payload.
*/
async function sendExpoPushNotification(messages: ExpoPushMessage[]) {
const response = await fetch("https://exp.host/--/api/v2/push/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"Accept-Encoding": "gzip, deflate",
},
body: JSON.stringify(messages),
});
if (!response.ok) {
throw new HttpsError(
"internal",
`Expo push API returned ${response.status}.`
);
}
return (await response.json()) as ExpoPushResponse;
}
export const sendTestPushNotification = onCall(async (request) => {
if (!request.auth?.uid) {
throw new HttpsError("unauthenticated", "Authentication is required.");
}
const userSnapshot = await db.doc(`users/${request.auth.uid}`).get();
const user = userSnapshot.data() as UserDocument | undefined;
const expoPushTokens = user?.expoPushTokens ?? [];
if (expoPushTokens.length === 0) {
throw new HttpsError(
"failed-precondition",
"No Expo push tokens found for this user."
);
}
const result = await sendExpoPushNotification(
expoPushTokens.map((token) => ({
to: token,
title: "Notification de test",
body: "Le flux Expo Notifications est correctement configure.",
data: {
type: "test",
sentAt: new Date(),
},
}))
);
if (result.errors?.length) {
logger.error("Expo push API returned errors.", {errors: result.errors});
throw new HttpsError("internal", "Expo push API returned errors.");
}
logger.info("Test push notification sent.", {
uid: request.auth.uid,
response: result.data,
});
return {success: true};
});

29
google-services.json Normal file
View File

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "157599391636",
"project_id": "expo-starter-9daef",
"storage_bucket": "expo-starter-9daef.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:157599391636:android:785115f243be601be17981",
"android_client_info": {
"package_name": "com.starter.expo"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBhAvRoINlRgpbtzDn0bJ3gGja4ZdvRvYs"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -10,7 +10,7 @@ const defaultFirebaseAuthErrorMessage =
export function mapFirebaseAuthError(error: unknown) { export function mapFirebaseAuthError(error: unknown) {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
console.log("error message", message)
for (const [code, mappedMessage] of Object.entries(firebaseAuthErrorMessages)) { for (const [code, mappedMessage] of Object.entries(firebaseAuthErrorMessages)) {
if (message.includes(code)) { if (message.includes(code)) {
return mappedMessage; return mappedMessage;

View File

@ -1,7 +1,8 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { getApps, initializeApp } from 'firebase/app'; import { getApps, initializeApp } from 'firebase/app';
import { getAuth, type Auth } from 'firebase/auth'; import { connectAuthEmulator, getAuth, type Auth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore'; import { connectFirestoreEmulator, getFirestore } from 'firebase/firestore';
import { connectFunctionsEmulator, getFunctions } from 'firebase/functions';
import { Platform } from 'react-native'; import { Platform } from 'react-native';
import type { FirebaseApp } from 'firebase/app'; import type { FirebaseApp } from 'firebase/app';
@ -15,21 +16,23 @@ type ReactNativeAuthModule = {
}; };
const firebaseConfig = { const firebaseConfig = {
apiKey: apiKey:
process.env.EXPO_PUBLIC_FIREBASE_API_KEY, process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
authDomain: authDomain:
process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN, process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: projectId:
process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID, process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: storageBucket:
process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET, process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: messagingSenderId:
process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID, appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
}; };
const isAppInitialized = getApps().length > 0; const isAppInitialized = getApps().length > 0;
const app = isAppInitialized ? getApps()[0] : initializeApp(firebaseConfig); const app = isAppInitialized ? getApps()[0] : initializeApp(firebaseConfig);
const useFirebaseEmulators = true;
const firebaseEmulatorHost = '192.168.1.171';
const reactNativeAuth = const reactNativeAuth =
Platform.OS === 'web' Platform.OS === 'web'
? null ? null
@ -44,5 +47,20 @@ const auth =
persistence: (reactNativeAuth as ReactNativeAuthModule).getReactNativePersistence(AsyncStorage), persistence: (reactNativeAuth as ReactNativeAuthModule).getReactNativePersistence(AsyncStorage),
}); });
export { auth }; const db = getFirestore(app);
export const db = getFirestore(app); const functions = getFunctions(app);
if (useFirebaseEmulators ) {
console.info(
`[firebase] using local emulators on ${firebaseEmulatorHost} (auth:9099, firestore:8080, functions:5001)`
);
// connectAuthEmulator(auth, `http://${firebaseEmulatorHost}:9099`, {
// disableWarnings: true,
// });
// connectFirestoreEmulator(db, firebaseEmulatorHost, 8080);
connectFunctionsEmulator(functions, firebaseEmulatorHost, 5001);
} else {
console.info('[firebase] using production Firebase services');
}
export { auth, db, functions };

View File

@ -18,10 +18,12 @@
"@react-navigation/native": "^7.1.8", "@react-navigation/native": "^7.1.8",
"expo": "~54.0.33", "expo": "~54.0.33",
"expo-constants": "~18.0.13", "expo-constants": "~18.0.13",
"expo-device": "~8.0.10",
"expo-font": "~14.0.11", "expo-font": "~14.0.11",
"expo-haptics": "~15.0.8", "expo-haptics": "~15.0.8",
"expo-image": "~3.0.11", "expo-image": "~3.0.11",
"expo-linking": "~8.0.11", "expo-linking": "~8.0.11",
"expo-notifications": "~0.32.16",
"expo-router": "~6.0.23", "expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13", "expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9", "expo-status-bar": "~3.0.9",

View File

@ -11,6 +11,10 @@ import { onSnapshot } from 'firebase/firestore';
import { auth } from '@/lib/firebase'; import { auth } from '@/lib/firebase';
import { userDoc } from '@/lib/firestore'; import { userDoc } from '@/lib/firestore';
import {
registerForPushNotifications,
subscribeToPushTokenRefresh,
} from '@/services/notifications';
import type { UserDocument } from '@/types/firestore'; import type { UserDocument } from '@/types/firestore';
type AuthContextValue = { type AuthContextValue = {
@ -62,6 +66,20 @@ export function AuthProvider({ children }: PropsWithChildren) {
}; };
}, []); }, []);
useEffect(() => {
if (!authUser) {
return;
}
void registerForPushNotifications(authUser.uid);
const unsubscribePushTokenRefresh = subscribeToPushTokenRefresh(
authUser.uid
);
return unsubscribePushTokenRefresh;
}, [authUser]);
const value = useMemo( const value = useMemo(
() => ({ () => ({
authUser, authUser,

View File

@ -42,6 +42,7 @@ export async function registerWithEmail({
email: credential.user.email ?? normalizedEmail, email: credential.user.email ?? normalizedEmail,
username: normalizedUsername, username: normalizedUsername,
profilePicture: credential.user.photoURL, profilePicture: credential.user.photoURL,
expoPushTokens: [],
createdAt: serverTimestamp(), createdAt: serverTimestamp(),
updatedAt: serverTimestamp(), updatedAt: serverTimestamp(),
}); });

113
services/notifications.ts Normal file
View File

@ -0,0 +1,113 @@
import Constants from 'expo-constants';
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import { httpsCallable } from 'firebase/functions';
import {
arrayUnion,
serverTimestamp,
setDoc,
} from 'firebase/firestore';
import { Platform } from 'react-native';
import { functions } from '@/lib/firebase';
import { userDocRef } from '@/lib/firestore';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldPlaySound: false,
shouldSetBadge: false,
shouldShowBanner: true,
shouldShowList: true,
}),
});
type NotificationSettingsPayload = {
expoPushToken: string | null;
pushEnabled: boolean;
platform: 'android' | 'ios' | 'web' | null;
};
type SendTestPushNotificationResult = {
success: boolean;
};
async function saveExpoPushToken(
uid: string,
expoPushToken: string
) {
await setDoc(
userDocRef(uid),
{
expoPushTokens: arrayUnion(expoPushToken),
updatedAt: serverTimestamp(),
},
{ merge: true }
);
}
function getProjectId() {
const projectId =
Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId;
if (!projectId) {
throw new Error('EAS projectId introuvable pour Expo Notifications.');
}
return projectId;
}
export async function registerForPushNotifications(uid: string) {
if (Platform.OS === 'web') {
return null;
}
if (!Device.isDevice) {
return null;
}
const existingPermissions = await Notifications.getPermissionsAsync();
let finalStatus = existingPermissions.status;
if (finalStatus !== 'granted') {
const requestedPermissions = await Notifications.requestPermissionsAsync();
finalStatus = requestedPermissions.status;
}
if (finalStatus !== 'granted') {
return null;
}
const expoPushToken = (
await Notifications.getExpoPushTokenAsync({
projectId: getProjectId(),
})
).data;
await saveExpoPushToken(uid, expoPushToken);
return expoPushToken;
}
export function subscribeToPushTokenRefresh(uid: string) {
if (Platform.OS === 'web') {
return () => undefined;
}
const subscription = Notifications.addPushTokenListener((token) => {
void saveExpoPushToken(uid, token.data);
});
return () => {
subscription.remove();
};
}
export async function sendTestPushNotification() {
const callable = httpsCallable<undefined, SendTestPushNotificationResult>(
functions,
'sendTestPushNotification'
);
await callable();
}

View File

@ -5,6 +5,7 @@ export type UserDocument = {
email: string; email: string;
username: string | null; username: string | null;
profilePicture: string | null; profilePicture: string | null;
expoPushTokens?: string[];
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
}; };
@ -14,6 +15,7 @@ export type UserFirestoreDocument = {
email: string | null; email: string | null;
username: string | null; username: string | null;
profilePicture: string | null; profilePicture: string | null;
expoPushTokens?: string[];
createdAt: Timestamp; createdAt: Timestamp;
updatedAt: Timestamp; updatedAt: Timestamp;
}; };