From 823059d6300b7b9d7fe51986fb01f823e403846d Mon Sep 17 00:00:00 2001 From: Leon Morival Date: Sun, 19 Apr 2026 18:59:59 +0200 Subject: [PATCH] notifications ok --- .env.example | 2 +- app.json | 15 ++- app/(tabs)/settings.tsx | 62 +++++++++- bun.lock | 26 +++- components/auth/AuthForm.tsx | 4 +- eas.json | 21 ++++ firebase-debug.log | 217 +++++++++++++++++++++++++++++++++ firebase.json | 22 +++- functions/src/index.ts | 1 + functions/src/notifications.ts | 90 ++++++++++++++ google-services.json | 29 +++++ lib/firebase-auth-errors.ts | 2 +- lib/firebase.ts | 36 ++++-- package.json | 2 + providers/AuthProvider.tsx | 18 +++ services/auth.ts | 1 + services/notifications.ts | 113 +++++++++++++++++ types/firestore.ts | 2 + 18 files changed, 642 insertions(+), 21 deletions(-) create mode 100644 eas.json create mode 100644 firebase-debug.log create mode 100644 functions/src/notifications.ts create mode 100644 google-services.json create mode 100644 services/notifications.ts diff --git a/.env.example b/.env.example index 25380d7..889d3b2 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,4 @@ EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN= EXPO_PUBLIC_FIREBASE_PROJECT_ID= EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET= EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= -EXPO_PUBLIC_FIREBASE_APP_ID= \ No newline at end of file +EXPO_PUBLIC_FIREBASE_APP_ID= diff --git a/app.json b/app.json index 0e41ae5..a60f6f7 100644 --- a/app.json +++ b/app.json @@ -10,7 +10,7 @@ "newArchEnabled": true, "ios": { "supportsTablet": true, - "bundleIdentifier": "com.pierre69007.expostarter" + "bundleIdentifier": "com.starter.expo" }, "android": { "adaptiveIcon": { @@ -21,7 +21,8 @@ }, "edgeToEdgeEnabled": true, "predictiveBackGestureEnabled": false, - "package": "com.pierre69007.expostarter" + "googleServicesFile": "./google-services.json", + "package": "com.starter.expo" }, "web": { "output": "static", @@ -29,6 +30,7 @@ }, "plugins": [ "expo-router", + "expo-notifications", [ "expo-splash-screen", { @@ -45,6 +47,13 @@ "experiments": { "typedRoutes": true, "reactCompiler": true - } + }, + "extra": { + "router": {}, + "eas": { + "projectId": "3e92de71-0ed6-43b0-903b-e020d4b110b5" + } + }, + "owner": "leonmorival" } } diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx index 61b0f7d..da719a8 100644 --- a/app/(tabs)/settings.tsx +++ b/app/(tabs)/settings.tsx @@ -1,12 +1,21 @@ import { MaterialCommunityIcons } from '@expo/vector-icons'; 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 { Catamaran, FunnelSans } from '@/constants/fonts'; import { Palette } from '@/constants/palette'; import { Radius, Spacing } from '@/constants/styles'; import { useAuth } from '@/providers/AuthProvider'; +import { sendTestPushNotification } from '@/services/notifications'; function SettingsRow({ label, @@ -34,6 +43,7 @@ function SettingsRow({ export default function SettingsScreen() { const { initializing, user } = useAuth(); + const [sendingNotification, setSendingNotification] = useState(false); if (initializing) { return ( @@ -47,6 +57,28 @@ export default function SettingsScreen() { return ; } + 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 ( @@ -77,6 +109,30 @@ export default function SettingsScreen() { /> + + + Notifications + + 0 + ? `${registeredPushTokens} appareil(x) enregistre(s).` + : 'Aucun appareil enregistre pour les notifications.' + } + label="Etat des notifications" + onPress={() => undefined} + /> + + + ); } @@ -139,7 +195,7 @@ const styles = StyleSheet.create({ sectionCard: { borderWidth: 1, borderColor: Palette.cream, - borderRadius: Radius.xl, + borderRadius: Radius.sm, backgroundColor: Palette.white, overflow: 'hidden', }, @@ -148,7 +204,7 @@ const styles = StyleSheet.create({ alignItems: 'center', gap: Spacing.md, paddingHorizontal: Spacing.md, - paddingVertical: Spacing.md, + paddingVertical: Spacing.sm, }, rowContent: { flex: 1, diff --git a/bun.lock b/bun.lock index 27f5bd9..54d1281 100644 --- a/bun.lock +++ b/bun.lock @@ -12,10 +12,12 @@ "@react-navigation/native": "^7.1.8", "expo": "~54.0.33", "expo-constants": "~18.0.13", + "expo-device": "~8.0.10", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linking": "~8.0.11", + "expo-notifications": "~0.32.16", "expo-router": "~6.0.23", "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", @@ -413,6 +415,8 @@ "@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/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="], @@ -705,6 +709,8 @@ "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-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=="], + "badgin": ["badgin@1.2.3", "", {}, "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "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-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-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-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-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-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=="], + "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-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-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-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], @@ -1379,6 +1397,8 @@ "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.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=="], - "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=="], @@ -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=="], + "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=="], "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/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/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], diff --git a/components/auth/AuthForm.tsx b/components/auth/AuthForm.tsx index 01bbee7..a938862 100644 --- a/components/auth/AuthForm.tsx +++ b/components/auth/AuthForm.tsx @@ -42,8 +42,8 @@ const copyByMode = { export function AuthForm({ mode }: AuthFormProps) { const copy = copyByMode[mode]; const [username, setUsername] = useState(''); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); + const [email, setEmail] = useState('leon.morival@gmail.com'); + const [password, setPassword] = useState('Furioza98*'); const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..7e2e4ae --- /dev/null +++ b/eas.json @@ -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": {} + } +} diff --git a/firebase-debug.log b/firebase-debug.log new file mode 100644 index 0000000..164cf0d --- /dev/null +++ b/firebase-debug.log @@ -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"}} diff --git a/firebase.json b/firebase.json index 330af33..75ac9a5 100644 --- a/firebase.json +++ b/firebase.json @@ -12,5 +12,25 @@ "*.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 + } } diff --git a/functions/src/index.ts b/functions/src/index.ts index babc0b7..f53e515 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,3 +3,4 @@ import {setGlobalOptions} from "firebase-functions/v2"; setGlobalOptions({maxInstances: 10}); export * from "./users"; +export * from "./notifications"; diff --git a/functions/src/notifications.ts b/functions/src/notifications.ts new file mode 100644 index 0000000..6a0bf15 --- /dev/null +++ b/functions/src/notifications.ts @@ -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; +}; + +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} 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}; +}); diff --git a/google-services.json b/google-services.json new file mode 100644 index 0000000..a7d977f --- /dev/null +++ b/google-services.json @@ -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" +} \ No newline at end of file diff --git a/lib/firebase-auth-errors.ts b/lib/firebase-auth-errors.ts index fafd921..d818476 100644 --- a/lib/firebase-auth-errors.ts +++ b/lib/firebase-auth-errors.ts @@ -10,7 +10,7 @@ const defaultFirebaseAuthErrorMessage = export function mapFirebaseAuthError(error: unknown) { const message = error instanceof Error ? error.message : String(error); - +console.log("error message", message) for (const [code, mappedMessage] of Object.entries(firebaseAuthErrorMessages)) { if (message.includes(code)) { return mappedMessage; diff --git a/lib/firebase.ts b/lib/firebase.ts index 756a51f..3391289 100644 --- a/lib/firebase.ts +++ b/lib/firebase.ts @@ -1,7 +1,8 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { getApps, initializeApp } from 'firebase/app'; -import { getAuth, type Auth } from 'firebase/auth'; -import { getFirestore } from 'firebase/firestore'; +import { connectAuthEmulator, getAuth, type Auth } from 'firebase/auth'; +import { connectFirestoreEmulator, getFirestore } from 'firebase/firestore'; +import { connectFunctionsEmulator, getFunctions } from 'firebase/functions'; import { Platform } from 'react-native'; import type { FirebaseApp } from 'firebase/app'; @@ -15,21 +16,23 @@ type ReactNativeAuthModule = { }; const firebaseConfig = { - apiKey: + apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY, - authDomain: + authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN, - projectId: + projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID, - storageBucket: + storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET, - messagingSenderId: + messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID, }; const isAppInitialized = getApps().length > 0; const app = isAppInitialized ? getApps()[0] : initializeApp(firebaseConfig); +const useFirebaseEmulators = true; +const firebaseEmulatorHost = '192.168.1.171'; const reactNativeAuth = Platform.OS === 'web' ? null @@ -44,5 +47,20 @@ const auth = persistence: (reactNativeAuth as ReactNativeAuthModule).getReactNativePersistence(AsyncStorage), }); -export { auth }; -export const db = getFirestore(app); +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 }; diff --git a/package.json b/package.json index eaecbed..bd6b0fd 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,12 @@ "@react-navigation/native": "^7.1.8", "expo": "~54.0.33", "expo-constants": "~18.0.13", + "expo-device": "~8.0.10", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linking": "~8.0.11", + "expo-notifications": "~0.32.16", "expo-router": "~6.0.23", "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", diff --git a/providers/AuthProvider.tsx b/providers/AuthProvider.tsx index b66a5b1..3231d59 100644 --- a/providers/AuthProvider.tsx +++ b/providers/AuthProvider.tsx @@ -11,6 +11,10 @@ import { onSnapshot } from 'firebase/firestore'; import { auth } from '@/lib/firebase'; import { userDoc } from '@/lib/firestore'; +import { + registerForPushNotifications, + subscribeToPushTokenRefresh, +} from '@/services/notifications'; import type { UserDocument } from '@/types/firestore'; 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( () => ({ authUser, diff --git a/services/auth.ts b/services/auth.ts index 302669d..1c3c498 100644 --- a/services/auth.ts +++ b/services/auth.ts @@ -42,6 +42,7 @@ export async function registerWithEmail({ email: credential.user.email ?? normalizedEmail, username: normalizedUsername, profilePicture: credential.user.photoURL, + expoPushTokens: [], createdAt: serverTimestamp(), updatedAt: serverTimestamp(), }); diff --git a/services/notifications.ts b/services/notifications.ts new file mode 100644 index 0000000..b4ed161 --- /dev/null +++ b/services/notifications.ts @@ -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( + functions, + 'sendTestPushNotification' + ); + + await callable(); +} diff --git a/types/firestore.ts b/types/firestore.ts index 9524e7f..5f40321 100644 --- a/types/firestore.ts +++ b/types/firestore.ts @@ -5,6 +5,7 @@ export type UserDocument = { email: string; username: string | null; profilePicture: string | null; + expoPushTokens?: string[]; createdAt: Date; updatedAt: Date; }; @@ -14,6 +15,7 @@ export type UserFirestoreDocument = { email: string | null; username: string | null; profilePicture: string | null; + expoPushTokens?: string[]; createdAt: Timestamp; updatedAt: Timestamp; };