From b88f5bf2e462459942afcca309e3bbaec0a2671f Mon Sep 17 00:00:00 2001 From: Andy Malkin Date: Mon, 5 Feb 2024 22:48:29 -0800 Subject: [PATCH 1/6] added env vars types and start scripts --- .env | 2 -- .env.development | 4 ++-- .env.production | 2 +- App.tsx | 11 +++++++---- config.ts | 21 +++++++++++++++++++++ env.d.tsx | 11 +++++++---- graphql/client.tsx | 8 +++++--- package.json | 2 ++ yarn.lock | 4 ++-- 9 files changed, 47 insertions(+), 18 deletions(-) delete mode 100644 .env create mode 100644 config.ts diff --git a/.env b/.env deleted file mode 100644 index 7e0a5f8..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -# .env.development -API_URI=https://api-dev.railbird.ai/graphql diff --git a/.env.development b/.env.development index 3828d94..444b872 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,3 @@ # .env.development -API_URI="http://192.168.1.28:8000/graphql" -DEV_USER_ID=1 +EXPO_PUBLIC_API_URI="http://192.168.1.28:8000/graphql" +EXPO_PUBLIC_DEV_USER_ID=1 diff --git a/.env.production b/.env.production index 11c7832..dda7acd 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,2 @@ # .env.production -API_URI=https://api-dev.railbird.ai/graphql +EXPO_PUBLIC_API_URI=https://api-dev.railbird.ai/graphql diff --git a/App.tsx b/App.tsx index 5026945..6fd03f3 100644 --- a/App.tsx +++ b/App.tsx @@ -1,15 +1,18 @@ -import { DEV_USER_ID } from "@env"; import React from "react"; import { ClientProvider, useAuthHeader } from "./graphql/client"; import AppNavigator from "./navigation/app-navigator"; +import { devUserId } from "./config"; + const SetAuthHeaderBasedOnEnv = () => { const { setAuthHeader } = useAuthHeader(); React.useEffect(() => { - if (DEV_USER_ID) { - console.log("Setting fake authorization user to: ", DEV_USER_ID); - setAuthHeader({ key: "user_id", value: DEV_USER_ID }); + if (devUserId) { + console.log("Setting fake authorization user to: ", devUserId); + setAuthHeader({ key: "user_id", value: devUserId }); + } else { + console.log("Production auth."); } }, [setAuthHeader]); diff --git a/config.ts b/config.ts new file mode 100644 index 0000000..a979824 --- /dev/null +++ b/config.ts @@ -0,0 +1,21 @@ +const warnEnv = [ + 'EXPO_PUBLIC_API_URI' +]; + +const errMsg = + 'does not exist in the environment.'; + +const missingEnv: string[] = []; + +for (const key of warnEnv) { + if (!process.env[key]) { + missingEnv.push(key); + } +} + +if (missingEnv.length > 0) { + throw new Error(`${missingEnv.join(', ')} ${errMsg}`); +} + +export const apiUrl = process.env.EXPO_PUBLIC_API_URI; +export const devUserId = process.env.EXPO_PUBLIC_DEV_USER_ID ?? false; diff --git a/env.d.tsx b/env.d.tsx index e534b18..210b888 100644 --- a/env.d.tsx +++ b/env.d.tsx @@ -1,4 +1,7 @@ -declare module "@env" { - export const API_URI: string; - export const DEV_USER_ID: string; -} +// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars +declare namespace NodeJS { + interface ProcessEnv { + EXPO_PUBLIC_API_URI: string; + EXPO_PUBLIC_DEV_USER_ID?: string; + } +} \ No newline at end of file diff --git a/graphql/client.tsx b/graphql/client.tsx index 81c2348..66e3f28 100644 --- a/graphql/client.tsx +++ b/graphql/client.tsx @@ -6,7 +6,7 @@ import { InMemoryCache, from, } from "@apollo/client"; -import { API_URI } from "@env"; + import React, { ReactNode, createContext, @@ -15,6 +15,8 @@ import React, { useState, } from "react"; +import { apiUrl } from '../config' + type Props = { children: ReactNode; }; @@ -40,9 +42,9 @@ export const useAuthHeader = () => { export const ClientProvider: React.FC = ({ children }) => { const [authHeader, setAuthHeader] = useState({ key: "", value: "" }); - console.log(`The api uri is ${API_URI}`); + console.log(`The api uri is ${apiUrl}`); const httpLink = new HttpLink({ - uri: API_URI, + uri: apiUrl, }); const cache = new InMemoryCache({}); diff --git a/package.json b/package.json index 3a59f2b..39b8851 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "start:android": "expo start --android", "start:ios": "expo start --ios", "android": "expo run:android", + "android:dev": "NODE_ENV=development expo run:android", + "android:prod": "NODE_ENV=production expo run:android", "ios": "expo run:ios", "web": "expo start --web", "lint": "eslint . --ext .js,.ts,.tsx", diff --git a/yarn.lock b/yarn.lock index 903653a..01d04f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9658,9 +9658,9 @@ queue@6.0.2: dependencies: inherits "~2.0.3" -"railbird-gql@git+https://dev.railbird.ai/railbird/railbird-gql.git#db82f66c5d3600d90f09c813f71287e176dc078b": +"railbird-gql@git+https://dev.railbird.ai/railbird/railbird-gql.git#838304efdd49a093bd8f9cea6cd6ef9e306d888a": version "1.0.0" - resolved "git+https://dev.railbird.ai/railbird/railbird-gql.git#db82f66c5d3600d90f09c813f71287e176dc078b" + resolved "git+https://dev.railbird.ai/railbird/railbird-gql.git#838304efdd49a093bd8f9cea6cd6ef9e306d888a" dependencies: "@apollo/client" "^3.9.2" "@graphql-codegen/cli" "^5.0.0" From 6bfd0621ad8fdde282259e25c7bbd9e4727f38c0 Mon Sep 17 00:00:00 2001 From: Loewy Date: Mon, 5 Feb 2024 22:50:24 -0800 Subject: [PATCH 2/6] wip: connect firebase token to apollo headers --- App.tsx | 26 +++++++++--- auth/firebase-auth.tsx | 48 +++++++++++++++++++++ auth/index.ts | 15 +++++++ navigation/app-navigator.tsx | 8 ++-- package.json | 4 +- screens/login.tsx | 82 +++++++++++------------------------- 6 files changed, 114 insertions(+), 69 deletions(-) create mode 100644 auth/firebase-auth.tsx create mode 100644 auth/index.ts diff --git a/App.tsx b/App.tsx index 5026945..2a594f0 100644 --- a/App.tsx +++ b/App.tsx @@ -1,16 +1,30 @@ import { DEV_USER_ID } from "@env"; -import React from "react"; +import React, { useEffect } from "react"; import { ClientProvider, useAuthHeader } from "./graphql/client"; import AppNavigator from "./navigation/app-navigator"; +import { getCurrentUserToken } from "./auth"; +// TODO: can be done when we go with a src top level directory -- should live on cofig const SetAuthHeaderBasedOnEnv = () => { const { setAuthHeader } = useAuthHeader(); - React.useEffect(() => { - if (DEV_USER_ID) { - console.log("Setting fake authorization user to: ", DEV_USER_ID); - setAuthHeader({ key: "user_id", value: DEV_USER_ID }); - } + + useEffect(() => { + const setAuthAsync = async () => { + // if (DEV_USER_ID) { + // console.log("Setting fake authorization user to: ", DEV_USER_ID); + // setAuthHeader({ key: "user_id", value: DEV_USER_ID }); + // } else { + // Fetch token for authenticated users in production + const token = await getCurrentUserToken(); + if (token) { + console.log("Setting firebase auth token"); + setAuthHeader({ key: "Authorization", value: `Bearer ${token}` }); + } + // } + }; + + setAuthAsync(); }, [setAuthHeader]); return null; diff --git a/auth/firebase-auth.tsx b/auth/firebase-auth.tsx new file mode 100644 index 0000000..1f52e82 --- /dev/null +++ b/auth/firebase-auth.tsx @@ -0,0 +1,48 @@ +import auth, { FirebaseAuthTypes } from '@react-native-firebase/auth'; +import { Alert } from 'react-native'; + +export const signInWithPhoneNumber = async (phoneNumber: string): Promise => { + if (!phoneNumber) { + Alert.alert("Please enter a valid phone number with a country code"); + return; + } + try { + const confirmation = await auth().signInWithPhoneNumber(phoneNumber); + return confirmation; + } catch (err) { + console.warn(err); + Alert.alert("There was an error. Make sure you are using a country code (ex: +1 for US)"); + } +}; + +export const confirmCode = async (confirm: FirebaseAuthTypes.ConfirmationResult, code: string): Promise => { + try { + await confirm.confirm(code); + } catch { + Alert.alert("Invalid code, please try again."); + } +}; + +export const onAuthStateChanged = (callback: (user: FirebaseAuthTypes.User | null) => void) => { + return auth().onAuthStateChanged(callback); +}; + + +export const getCurrentUserToken = async (): Promise => { + const user = auth().currentUser; + if (user) { + return await user.getIdToken(); + } + return null; +}; + +export const signOut = async (): Promise => { + try { + auth().signOut() + // tie in to AppNav + } catch (err) { + console.error(err) + // toggle appnav state regardless + // Handle sign out error - have to look into best way to do this - asyncstorage? + } +} \ No newline at end of file diff --git a/auth/index.ts b/auth/index.ts new file mode 100644 index 0000000..ba5a593 --- /dev/null +++ b/auth/index.ts @@ -0,0 +1,15 @@ +import { + signInWithPhoneNumber, + confirmCode, + onAuthStateChanged, + getCurrentUserToken, + signOut +} from './firebase-auth' + +export { + signInWithPhoneNumber, + confirmCode, + onAuthStateChanged, + getCurrentUserToken, + signOut +} \ No newline at end of file diff --git a/navigation/app-navigator.tsx b/navigation/app-navigator.tsx index 32ea4ce..8469631 100644 --- a/navigation/app-navigator.tsx +++ b/navigation/app-navigator.tsx @@ -14,13 +14,13 @@ const Stack = createNativeStackNavigator(); const ScreensStack = () => ( diff --git a/package.json b/package.json index f245095..a8660a0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "start:android": "expo start --android", "start:ios": "expo start --ios", "android": "expo run:android", + "android:prod": "cp .env.production .env && cat .env && NODE_ENV=production expo run:android", "ios": "expo run:ios", + "ios:prod": "cp .env.production .env && expo run:ios", "web": "expo start --web", "lint": "eslint . --ext .js,.ts,.tsx", "lint:fix": "eslint . --ext .ts,.tsx --fix", @@ -83,4 +85,4 @@ "@babel/core": "^7.20.2", "babel-loader": "^8.3.0" } -} +} \ No newline at end of file diff --git a/screens/login.tsx b/screens/login.tsx index 75e3e0a..d16de55 100644 --- a/screens/login.tsx +++ b/screens/login.tsx @@ -1,72 +1,34 @@ -import auth, { FirebaseAuthTypes } from "@react-native-firebase/auth"; -import React, { useEffect, useState } from "react"; +// Login.tsx +import React, { useEffect, useState } from 'react'; import { - Alert, Button, Keyboard, Text, TextInput, TouchableWithoutFeedback, View, -} from "react-native"; - -// This code is beginning of Auth Implementation - actual implementation will differ and involve more UI -// Does not have a restart or proper handling of code confirmation, should only be used for obtaining token/testing -// Currently working for Android builds, iOS has open issue #56 +} from 'react-native'; +import { signInWithPhoneNumber, confirmCode, onAuthStateChanged, signOut } from '../auth'; // Adjust the path as necessary +import { FirebaseAuthTypes } from '@react-native-firebase/auth'; +import { useAuthHeader } from '../graphql/client'; export default function Login() { - const [phoneNumber, setPhoneNumber] = useState(""); - const [code, setCode] = useState(""); - - const [user, setUser] = useState(null); - const [confirm, setConfirm] = - useState(null); - - async function onAuthStateChanged(user: any) { - setUser(user); - if (user) { - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - const token = await auth().currentUser?.getIdToken(); - // To debug/check token & user return, use these logs - // console.log(token) // token log - // console.log(user) // user log - } - } - - async function signInWithPhoneNumber(phoneNumber: string) { - if (!phoneNumber) { - return Alert.alert( - "Please enter a valid phone number with a country code", - ); - } - try { - const confirmation = await auth().signInWithPhoneNumber(phoneNumber); - setConfirm(confirmation); - } catch (err) { - // TODO: implement more robust error handling by parsing err message - console.warn(err); - Alert.alert( - "There was an error. Make sure you are using a country code (ex: +1 for US)", - ); - } - } - - async function confirmCode() { - try { - await confirm?.confirm(code); - } catch { - Alert.alert("Invalid code, please try again."); - } - } + const [phoneNumber, setPhoneNumber] = useState(''); + const [code, setCode] = useState(''); + const [user, setUser] = useState(null); + const [confirm, setConfirm] = useState(null); + const authHeader = useAuthHeader() useEffect(() => { - const subscriber = auth().onAuthStateChanged(onAuthStateChanged); - return subscriber; + const subscriber = onAuthStateChanged(setUser); + return subscriber; // This may need adjustment based on your specific implementation }, []); + + return ( Keyboard.dismiss()}> - + )}