Mastodonnのクラインアント用ネイティブアプリをReact NativeExpoで作成している。

バージョンは下記の通り。

    "expo": "~52.0.11",
    "react-native": "0.76.3",

実装自体はできたんだけれど、正直いろいろと理解が足りていないので、整理してみる。

インストールしたPackage

"expo-auth-session": "^6.0.1",
"expo-secure-store": "^14.0.0"

expo-auth-session

ブラウザベースの認証を処理するための API

最初は認証時にWebViewを開いて認証に成功したらリダイレクトURIを用いてアプリ画面に戻すという仕組みを実装しようとしたんだけれど、そもそもリダイレクトURIがわからない。Expoの開発用URLを指定してみたけど、

java.io.IOException: Remote update request not successful

という謎のエラーが出てアプリ側に戻らない。

そしてタスク管理でアプリに戻ると認証が成功しているという謎の現象が発生した。

色々悩んでいたけどexpo-auth-session を使用したらリダイレクトURIの作成をいい感じにやってくれた。

Expo AuthSessionにはexpo-crypto と一緒にインストールする必要があるって書いてあったけど、インストールしていない。今のところ問題はないので気にしないでおく。

expo-secure-store

Expo SecureStore

Expo用のローカルストレージ。

// 保存
await SecureStore.setItemAsync("key", value);

// 参照
await SecureStore.getItemAsync("key");

// 削除
await SecureStore.deleteItemAsync("kye");

で、一通りの作業ができる。

認証に用いたMastodon API

認証までのフロー

アプリケーションを登録

クライアント アプリケーションを登録APIを叩く。

Bodyclient_nameredirect_urisscopeswebsiteが必要になる。

client_nameはアプリ名を指定し、redirect_urisexpo-auth-sessionを使用し作成する。

redirect_uris: AuthSession.makeRedirectUri({
  native: "{APPNAME}://redirect",
}),

scopes権限を指定し、websiteはアプリのウェブサイトをインプットする。

成功すると、

{
  "id": "563419",
  "name": "Test Application",
  "website": "https://app.example",
  "scopes": ["read", "write", "push"],
  "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
  "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob"],
  "client_id": "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
  "client_secret": "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw"
}

が取得できる。

client_idclient_secretは後ほど使用する。関数をまたいで使用するので、expo-secure-storeに保存した。

認証画面を開く

取得したclient_idを用い、認証URLを作成しブラウザで開く。

その際、expo-web-browserを使用した。Expo WebBrowserだけどインストールした記憶がないので、たぶん標準で入っていた。

react-native-webviewとは異なり、ネイティブなブラウザモーダルを使用するらしい。

使い分けの参考は下記の通り。

要素expo-web-browserreact-native-webview
利用シーン外部リンクを簡単に開く場合ウェブページをアプリの一部として組み込む場合
操作性操作不可DOM操作やイベントリスニングが可能
UIの統合性外部ブラウザ風のUI(独立性あり)アプリ内に統合されたUI
インストールの必要性不要(Expoに標準搭載)必要(個別にインストール)
パフォーマンス高速(ネイティブブラウザ)やや劣る可能性あり

で、expo-web-browserには認証フローのメソッドがある。

const result = await WebBrowser.openAuthSessionAsync(
  authUrl, // 認証を開始するURL
  redirectUri, // 認証後にリダイレクトされるURL
);

上記メソッドを実行すると下記フローが走る。

  1. 認証プロバイダーとのやり取り:
    authUrlに指定したプロバイダー(例: GoogleやTwitter)に接続し、ユーザーが認証情報を入力できる画面を表示。
  2. リダイレクトハンドリング:
    認証が成功すると、プロバイダーはredirectUriにリダイレクト。
    アプリはそのリダイレクトURLを受け取り、アクセストークンや認証コードを抽出して利用。
  3. ネイティブモーダルの使用:
    WebBrowser.openAuthSessionAsyncは、ネイティブの認証モーダル(Safari View ControllerやChrome Custom Tabs)を使用するため、認証が安全かつシームレスに行える。

アクセストークンの取得

認証が完了したら、認証結果に含まれるcodeを用い、アクセストークンを取得する。 その際、先ほど保存したclient_idclient_secretBodyに含める。 成功後したら認証が成功したということなので、取得したアクセストークンをexpo-secure-storeに保存して、アクセストークンを持っている人用のページにリダイレクトする。

ルートに関係ないディレクトがルートに紐づく問題

app以下にmodelという型フォルダを作成したら、

"./model/App.ts" is missing the required default export. Ensure a React component is exported as default.

というエラーが吐き出された。 どうやらReact nativeはファイル構造に基づいてルートを自動生成するらしい。

しかしこれはルート判定してほしくない。 除外判定の仕方がわからなかったので、appと同階層にsrcを作成しそこに格納した。

これが正しいかはわからない。