気づいたら、Expoで実行時にAndroidのstandaloneのみGoogle SignInが失敗しています。
元々Expoの以下の機能を使用してGoogle SignInをしていました。
以下ではログインできました。
確か、実装時にAndroidのstandaloneもログインできたよなぁ。。。と思いながら、認証周りの設定を見直したけど直らなかったので、ググったら以下が原因でした。
結論からいうとExpo SDK33以降は以下のようにする必要があるみたいです。
Expoとstandaloneで動作が変わるのは気持ち悪いですが、 ここは仕方なさそうなので改修しました。
■改修pull request
最終的なコードはこうなりました。
import * as GoogleSignIn from "expo-google-sign-in";
import * as Google from "expo-google-app-auth";
import { AsyncStorage, Platform } from "react-native";
import React, { createContext, Component } from "react";
import Constants from "expo-constants";
import { Sentry } from "react-native-sentry";
import * as firebase from "firebase";
const Context = createContext({});
const { Provider } = Context;
interface Props {}
interface State {
uid: string;
email: string;
}
const isStandaloneAndAndroid = () => {
return Platform.OS === "android" && Constants.appOwnership !== "expo";
};
export default class extends Component<Props, State> {
state = {
email: "",
uid: ""
};
async componentDidMount() {
if (isStandaloneAndAndroid()) {
const androidClientId = process.env.GOOGLE_LOGIN_ANDROID_CLIENT_ID;
try {
await GoogleSignIn.initAsync({
clientId: String(androidClientId)
});
} catch ({ message }) {
Sentry.captureMessage(JSON.stringify(message));
}
}
const loggedIn = await this.loggedIn();
if (loggedIn && !this.state.uid) {
const uid = await AsyncStorage.getItem("uid");
if (uid) {
this.setState({
uid
});
}
}
if (loggedIn && !this.state.email) {
const email = await AsyncStorage.getItem("email");
if (email) {
this.setState({
email
});
}
}
}
onGoogleLogin = async () => {
if (isStandaloneAndAndroid()) {
// TODO: AndroidのstandaloneのみGoogleSignInを使わないとエラーになる
// https://github.com/expo/expo/issues/3253
await GoogleSignIn.askForPlayServicesAsync();
const result = await GoogleSignIn.signInAsync();
if (result.type === "success" && result.user && result.user.auth) {
const { idToken, accessToken } = result.user.auth;
await this.firebaseLogin(idToken || "", accessToken || "");
} else {
Sentry.captureMessage(JSON.stringify(result));
}
} else {
const androidClientId = process.env.GOOGLE_LOGIN_ANDROID_CLIENT_ID;
const iosClientId = process.env.GOOGLE_LOGIN_IOS_CLIENT_ID;
const result = await Google.logInAsync({
clientId:
Platform.OS === "ios" ? String(iosClientId) : String(androidClientId),
iosClientId,
androidClientId,
scopes: ["profile", "email"]
});
if (result.type === "success") {
const { idToken, accessToken } = result;
await this.firebaseLogin(idToken || "", accessToken || "");
} else {
Sentry.captureMessage(JSON.stringify(result));
}
}
};
firebaseLogin = async (idToken: string, accessToken: string) => {
const credential = firebase.auth.GoogleAuthProvider.credential(
idToken,
accessToken
);
await firebase
.auth()
.signInAndRetrieveDataWithCredential(credential)
.catch(error => {
Sentry.captureMessage(JSON.stringify(error));
});
await this.setSession(true);
};
loggedIn = async () => {
const idToken = await this.getIdToken();
return Boolean(idToken);
};
logout = async () => {
await firebase.auth().signOut();
await AsyncStorage.removeItem("id_token");
await AsyncStorage.removeItem("expiration");
await AsyncStorage.removeItem("email");
};
setSession = async (refresh = false) => {
const user = firebase.auth().currentUser;
if (!user) {
return;
}
if (user.email) {
await AsyncStorage.setItem("email", user.email);
await AsyncStorage.setItem("uid", user.uid);
this.setState({
email: user.email,
uid: user.uid
});
}
const idToken = await user.getIdToken(refresh);
await AsyncStorage.setItem("id_token", idToken);
await AsyncStorage.setItem(
"expiration",
String(new Date().getTime() + 60 * 60)
);
return idToken;
};
getIdToken = async () => {
const idToken = await AsyncStorage.getItem("id_token");
if (!idToken) {
return null;
}
const expiration = await AsyncStorage.getItem("expiration");
if (Number(expiration) > new Date().getTime()) {
return idToken;
}
return this.setSession(true);
};
render() {
return (
<Provider
value={{
onGoogleLogin: this.onGoogleLogin,
getIdToken: this.getIdToken,
loggedIn: this.loggedIn,
logout: this.logout,
email: this.state.email,
uid: this.state.uid
}}
>
{this.props.children}
</Provider>
);
}
}
export const Consumer = Context.Consumer;
趣味プロダクトだから良いけど、仕事のアプリでリリース前のタイミングで 起きたら発狂しそう