Node のバージョン管理を nodebrew から Volta に変える
これまで Node のバージョン管理ツールにはずっと nodebrew を使っていたのですが、プロジェクトごとにバージョンを変える必要がある際に不便だったのと、シンプルに飽きてきたというのもあり、比較的新しい Volta に変更してみました。
nodebrew および Node.js をアンインストール
Volta を導入するにあたって、まずは既存の nodebrew および Node.js をアンインストールしました。 Node.js の環境をいじるのは Mac に nodebrew をセットアップして以来でちょっとドキドキする作業でしたが、以下あたりの記事を参考にしつつ、問題なく進めることができました。
最後に、node -v
でなにもバージョンが表示されなくなると、Node.js が無事アンインストールされているということなので、次のステップに進みます。
Volta をインストール
基本的には以下のコマンドのみでOKです。
node
の他に、yarn
も同様にインストールしておきました。
# install Volta % curl https://get.volta.sh | bash # install Node % volta install node ## バージョン指定する場合 % volta install node@latest % volta install node@16.13.1 # start using Node % node
これで、Node.js がインストールできました。
% node -v v16.13.1
プロジェクトごとにバージョンを固定する
Volta にはプロジェクトごとにツールのバージョンを指定できる volta pin
コマンドが用意されています。
試しに、適当なプロジェクトで、volta pin node@16.13.1
と指定し実行します。(package.json が存在する前提)
すると、プロジェクトの package.json
に以下の記述が追加されます。これによって Volta 側でバージョンを汲み取って指定のバージョンで実行することができます。特に、複数人で開発する際に力を発揮します。
"volta": { "node": "16.13.1", }
参考リンク
SupabaseのJavaScriptクライアントでテーブル結合を行う
Supabase の JavaScript クライアントでテーブル結合を行う方法をメモ。
テーブルが以下のようにあるとします。ユーザーに紐づく投稿画像があり、その画像に対してコメントが付くようなイメージです。
# ユーザー users: id: string fullname: string # 投稿画像 photos: id: string userId: string(※foreign key relation: users.id) createdAt: Timestamp updatedAt: Timestamp title: string url: string # 画像へのコメント comments: id: string userId: string(※foreign key relation: users.id) createdAt: Timestamp updatedAt: Timestamp body: string photoId: number(※foreign key relation: photos.id)
photos を基準に comments と user を結合する
photos を基準に comments と user を結合する場合は以下のように記述します。
const { data: photos } = await supabase .from("photos") .select(` *, comments(*), user: userId(*), // こう書くことでプロパティ名を指定できる `)
photos に結合させた comments 内の user も結合させる(入れ子構造にする)
上記から少し発展させ、photos に結合させた comments に紐づく user も結合させる場合は以下のように記述します。シンプルに select 文を入れ子にすることで実現できます。
const { data: photos } = await supabase .from("photos") .select(`*, user: userId(*), comments(*, user: userId(*))`)
Supabase の storage から getPublicUrl() した値が404になる問題
Supabase では storage にアップロードしたリソースのパブリックなURLを取得するメソッド getPublicUrl()
が用意されているのですが、このメソッドを使って取得した値が 404 になるケースに遭遇しました。
export const removeBucketPath = (key: string, bucketName: string) => { return key.slice(bucketName.length + 1) // "/"の分だけ加算している } // この結果が404になる const { publicURL, error } = supabase.storage.from("photos").getPublicUrl(removeBucketPath(photo.url, "photos"))
storage のキーも合っているし、getPublicUrl()
に渡す bucket のパスも合っているので、原因がなにか分からずしばらく途方に暮れていました。
しばらく Supabase のダッシュボードを眺めていたら、この問題に気がつきました。
▼これ
supabase の bucket は private
か public
かのいずれかの状態があるのですが、これが private
になっていました(上記の画像は修正後なので public
になっています)。
ここを public
に変更したら、無事取得できるようになりました。
The bucket needs to be set to public, either via updateBucket() or by going to Storage on app.supabase.io, clicking the overflow menu on a bucket and choosing "Make public"
【TypeScript】Arrow Functions で Generics を使う方法
TypeScript でアロー関数(Arrow Functions)を書く際に、Generics を使おうとすると、素直に書いた場合うまくコンパイルされない場合があります。
例えば、以下のような T型 の値を引数に取り、そのまま引数を return する関数があるとします。
const identity = <T>(arg: T): T => { return arg; }
これを素直に書くと、React.createElement()
メソッドとして認識されてしまい、エラーとなります。
これを防ぐには、<T>
の部分を <T, >
もしくは <T extends unknown>
のように小細工してやる必要があります。
const identity = <T, >(arg: T): T => { return arg; }
const identity = <T extends unknown>(arg: T): T => { return arg; }
【Firebase Auth】ソーシャルログインしたアカウントをすぐに削除しようとすると "auth/requires-recent-login" のエラーが出る
今作っているアプリケーションで、Firebase Authentication のソーシャルログインを活用しているものがある。
そこで、ソーシャルログインしたのち(この時点でFirebase Authentication 上にユーザー情報が作られる)、場合によってすぐにユーザー情報を削除する必要が出てきた。
しかし、ユーザー作成後すぐに同ユーザーを削除しようとすると、"auth/requires-recent-login"
のエラーが出ることがわかった。
エラーの内容は、公式ドキュメントによると、以下のように書いてある。
Thrown if the user's last sign-in time does not meet the security threshold. Use firebase.User.reauthenticateWithCredential to resolve. This does not apply if the user is anonymous.
つまり、そういう重要な操作は firebase.User.reauthenticateWithCredential
で再認証をしてから行ってくれ、ということらしい。
参照:
そこで、ひと手間はかかるが、以下のように一度サインインし、そこで取得した credential
を用いて再認証することで、ユーザー情報削除を実行できるようにした。
/** 会員情報削除 */ const deleteAuth = async () => { try { // 削除には再認証が必要なのでここで実行 firebase .auth() .signInWithPopup(provider) .then((result) => { const user = firebase.auth().currentUser if (!result || !result.credential) return user.reauthenticateWithCredential(result.credential) // credential を渡して削除を実行 .then(async () => { await user.delete() alert("会員情報を削除しました") }) }) } catch (error) { // eslint-disable-next-line no-console console.log(error) } }
これで無事ユーザー情報が削除されるようになった。
【Next】Firebase Authentication で取得したユーザ情報を整形してアプリケーション内で使う
Firebase Authentication を活用したアプリケーションにおいて、取得したユーザ情報を useContext などを使ってグローバルに保持するケースがあると思います。
具体的には以下のような実装です。
import { FC, createContext, useEffect, useState } from 'react'; import firebase from '../lib/firebase'; type AuthContextProps = { currentUser: firebase.User | null | undefined } const AuthContext = createContext<AuthContextProps>({ currentUser: undefined }); const AuthProvider: FC = ({ children }) => { const [currentUser, setCurrentUser] = useState<firebase.User | null | undefined>(undefined) useEffect(() => { // ログイン状態をウォッチ firebase.auth().onAuthStateChanged((user) => { if (user) { // ユーザ情報を格納する setCurrentUser(user) } }) },[]) return ( <AuthContext.Provider value={{ currentUser: currentUser }}> {children} </AuthContext.Provider> ) } export { AuthContext, AuthProvider }
これでもユーザ情報を格納する目的は果たされるのですが、不要なプロパティを含んだ未整形のユーザオブジェクトがごそっと保持されてしまい、使い勝手がよくありません。
その場合、ユーザ情報を格納する際には、アプリケーション内で必要なプロパティのみを取捨選択した新たなオブジェクト(mappedUser)を作り、それを格納する使い方が有効です。
(略) export type User = { uid: string displayName: string | null email: string | null emailVerified: boolean isAnonymous: boolean phoneNumber: string | null photoURL: string | null } useEffect(() => { // ログイン状態をウォッチ firebase.auth().onAuthStateChanged((user) => { if (user) { // 必要なプロパティのみを集めた `mappedUser` オブジェクトを定義 const mappedUser = { uid: user.uid, displayName: user.displayName, email: user.email, emailVerified: user.emailVerified, isAnonymous: user.isAnonymous, phoneNumber: user.phoneNumber, photoURL: user.photoURL } setCurrentUser(mappedUser) } }) },[]) (略)
Next.js でクライアントから環境変数が読み込めない
Next.js で今個人的に作っているアプリケーションにおいて、クライアント(ブラウザ)から環境変数(.env.*)が読めないという事象に遭遇したので、その原因と解決法をメモしておきます。
やりたいことは、ページコンポーネント上で、アクセス時に毎回 Firebase の匿名認証を実行するというもの。
useEffect(() => { // 匿名ログイン実行 async function initFirebase() { firebase.auth().onAuthStateChanged(async (user) => { if (!user) { await firebase.auth().signInAnonymously() } else { console.log(user) } }) } initFirebase() }, [])
この実行時に "auth/invalid-api-key" というエラーが出てしまいました。
ずばり原因としては、.env ファイルで設定した環境変数に問題があり、Firebase の 設定用ファイルにおいて、それを正しく読み込めていませんでした。
本来、Next.js でブラウザから環境変数を呼ぶ際は、以下のように 'NEXT_PUBLIC_****' と命名しなければいけないのですが、
NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY=xxxxxxx NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=xxxxxxx NEXT_PUBLIC_FIREBASE_PROJECT_ID=xxxxxxx NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=xxxxxxx NEXT_PUBLIC_FIREBASE_APP_ID=xxxxxxx
以下のようにしてしまっておりました。
FIREBASE_PUBLIC_API_KEY=xxxxxxx FIREBASE_AUTH_DOMAIN=xxxxxxx FIREBASE_PROJECT_ID=xxxxxxx FIREBASE_MESSAGING_SENDER_ID=xxxxxxx FIREBASE_APP_ID=xxxxxxx
上記のように環境変数名を変更すると、無事読み込むことができました。めでたしめでたし。