TypeScript+React+Redux+Firebase AuthでLINEミニアプリ (liff)開発
Zenn初めての投稿です。よろしくお願いします。
モリモリなタイトルとなりましたが、
LINE Mini AppをFirebase Authenticationを用いて、TypeScript+React+Reduxで開発してみました。
↓デモ動画です。
LINEミニアプリ(LINE Mini app)とは
LINE内で使えるWebアプリです。LINEボットと連携して、商品やチケットの購入をさせるなどできることが広がります。
スマホアプリやWebアプリと比べてLINEボットからかん単に使えるため圧倒的UX向上につながります。
LIFFとは
LINE Front-end Frameworkの略です。
公式サイトによると、LINEミニアプリが動作するプラットフォームという位置付けです。
なぜFirebase Authenticationを用いたのか
Firebase AuthenticationはスマホアプリやWebなど様々なクライアントに対して簡単にユーザー認証を導入できます。スマホアプリにも同じユーザー認証を対応させたいのでFirebase Authenticationを使用しました。
開発方法
Firebase Authenticationの導入
src/firebaseAuth.ts
でセットアップします。
環境変数から情報を読み込みます。
import * as firebase from 'firebase/app';
import 'firebase/auth';
export const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
});
Store
RootState(CombinedState)
export interface CombinedState {
router: RouterState;
authStore: AuthState;
}
RouterState
今回には特に関係ないが、ルーティングを使うならば必要です。
connected-react-router
で定義されたRouterState
を使いました。
AuthState
firebaseのユーザーとLINEのユーザーの情報を入れておけます。
export interface AuthState {
user: firebase.User | null;
lineProfile: LineProfile | null;
}
export interface LineProfile {
userId: string;
displayName: string;
pictureUrl?: string | undefined;
statusMessage?: string | undefined;
}
Action
typescript-fsa
を使って、Actionを作成します。
自動で、actions.signUp.started
、actions.signUp.done
、actions.signUp.failed
が作られます。
import actionCreatorFactory from 'typescript-fsa';
import 'firebase/auth';
import { LineProfile } from './reducers/authStore';
const actionCreator = actionCreatorFactory();
export interface SignUpResult {
user: firebase.User;
lineProfile: LineProfile;
}
export const actions = {
signUp: actionCreator.async<null, SignUpResult>('SIGN_UP'),
};
signUp
Firebase AuthとLINEを連携させます。
LINEと連携するにはサーバー側でCustomToken
を生成する必要があります。
Firebaseのドキュメントのカスタム トークンを作成するを参照ください。
そして、サーバーからCustomToken
を受け取ったら、signInWithCustomToken()
でログインできます。
その後、LINEのProfileを取得しています。
export const signUp = () => {
return (dispatch: Dispatch): void => {
app.auth().onAuthStateChanged((currentUser) => {
liff.init({
liffId: process.env.REACT_APP_LIFF_ID as string,
})
.then(() => {
if (!liff.isLoggedIn()) {
liff.login({});
}
if (currentUser) {
liff.getProfile()
.then((profile: LineProfile) => {
dispatch(
actions.signUp.done({
params: null,
result: {
user: currentUser,
lineProfile: profile,
},
})
);
})
.catch((err) => console.log(err));
return;
}
const accessToken = liff.getAccessToken();
axios
.post<FirebaseTokenResponse>(
'https://サーバーのホスト/users/verifyToken',
{
token: accessToken,
}
)
.then((res) => {
console.log(res);
app.auth()
.signInWithCustomToken(res.data.firebase_token)
.then((res) => {
liff.getProfile()
.then((profile: LineProfile) => {
dispatch(
actions.signUp.done({
params: null,
result: {
user: res.user as firebase.User,
lineProfile: profile,
},
})
);
})
.catch((e) => {
console.log(e);
});
})
.catch((e) => {
console.log(e);
});
})
.catch((err) => console.log(err));
})
.catch((err) => console.log(err));
});
};
};
Reducer
RootReducer(CombinedReducer)
import { combineReducers, Reducer } from 'redux';
import authStore from './authStore';
import { connectRouter, RouterState } from 'connected-react-router';
import { createBrowserHistory } from 'history';
import { AuthState } from './authStore';
export const history = createBrowserHistory();
export type CombineReducerMap<S extends unknown> = {
[K in keyof S]: Reducer<S[K]>;
};
export interface CombinedState {
router: RouterState;
authStore: AuthState;
}
const reducerMap: CombineReducerMap<CombinedState> = {
router: connectRouter(history) as Reducer,
authStore: authStore,
};
export default combineReducers<CombinedState>(reducerMap);
AuthReducer
typescript-fsa-reducers
を用いて、いい感じに reducer
を定義します。
const initialState: AuthState = {
user: null,
lineProfile: null,
};
const reducer = reducerWithInitialState(initialState)
.case(actions.signUp.started, (state, _) => {
return { ...state };
})
.case(actions.signUp.done, (state, payload) => {
return {
...state,
user: payload.result.user,
lineProfile: payload.result.lineProfile,
};
})
.case(actions.signUp.failed, (state, _) => {
return {
...state,
};
});
export default reducer;
View
React Hooks APIを使って、Actionを実行します。
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getSignUpStore } from '../redux/selectors';
import { signUp } from '../redux/actions';
import { AuthState } from '../redux/reducers/authStore';
import { CombinedState } from '../redux/reducers';
const SignUpPage = ({
signUpStore,
signUp,
}: {
signUpStore: AuthState;
signUp: () => void;
}) => {
useEffect(() => {
signUp();
}, [signUp]);
return (
<div>
{signUpStore.lineProfile && signUpStore.user ? (
<p>あなたは「{signUpStore.lineProfile.displayName}」</p>
) : (
<p>ログイン中</p>
)}
</div>
);
};
const mapStateToProps = (state: CombinedState) => {
const signUpStore = getSignUpStore(state);
return { signUpStore };
};
export default connect(mapStateToProps, { signUp })(SignUpPage);
デプロイ
Netlifyにデプロイしました。
LINEで使う方法
LINE DevelopersからLIFFアプリのチャネルを作ります。
詳しくはLINEの公式ドキュメントのLIFFアプリをチャネルに追加するを参照してください。
設定にEndpoint URLをNetlifyのURLにします。
そして、https://liff.line.me/
から始まるLIFF URLという文字列が設定されます。それをLINEのチャットに送りそのリンクを押すと、LINE内で使うことができます。
また、LINEボットのリッチメニューを使うとそれっぽくなります。
LINE Official Account Managerからリッチメニューを作成してみましょう。
リッチメニューの作成画面に移ります。
コンテンツ設定のテンプレートの大の右下を選択します。
そして、画像作成ボタンを押して適当に画像を作ります。
アクションはリンクタイプを選び、LIFFのリンクを入力します。
そうすることでLIFFアプリが開けるリッチメニューを作れました。
Discussion