Closed22

firebase auth

kodukakoduka

shadcn ui でbuttonを入れると追加されたコンポーネントは、モジュール '@radix-ui/react-slot' またはそれに対応する型宣言が見つかりません。とエラーが出るので、一度@radix-ui/react-slotを削除してから、再度入れ直せば解決する。

pnpm remove @radix-ui/react-slot
pnpm add @radix-ui/react-slot

https://github.com/radix-ui/primitives/issues/2168

kodukakoduka

記事で_app.tsxにAuthProviderを埋め込むとあったので、それに沿って実装していたら、NextJS14から_app.tsxはないことが判明。代わりにlayout.tsxかtemplate.tsxを使用するとのことなので、layout.tsxに実装することにした。

libs/components/providers/auth.tsx
'use client'

import { auth, signIn, signInWithGoogle, signInWithLine, signOut } from '@/libs/firebase/client/auth'
import { deepEqual } from 'fast-equals'
import { User } from 'firebase/auth'
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'

type AuthContextValue = {
  user?: User
  signIn: (email: string, password: string) => Promise<void>
  signInWithGoogle: () => Promise<void>
  signInWithLine: () => Promise<void>
  signOut: () => Promise<void>
}
type AuthProviderProps = {
  children?: ReactNode
}

const errorHandle = () => Promise.reject(new Error('親コンポーネントのツリー内にAuthProviderが見当たりません。'))

const AuthContext = createContext<AuthContextValue>({
  signIn: errorHandle,
  signInWithGoogle: errorHandle,
  signInWithLine: errorHandle,
  signOut: errorHandle,
})

export const useAuthContext = () => useContext(AuthContext)

export function AuthProvider({ children }: AuthProviderProps) {
  const [user, setUser] = useState<User>()

  useEffect(() => {
    const unsubscribed = auth.onAuthStateChanged((_user) => {
      if (!deepEqual(user, _user)) {
        if (_user) {
          setUser(_user)
        } else {
          setUser(undefined)
        }
      }
    })
    return () => {
      unsubscribed()
    }
  }, []) // <= 第二引数の[]がないと、onAuthStateChangedが2回呼ばれる。

  return <AuthContext.Provider value={{ user, signIn, signInWithGoogle, signInWithLine, signOut }}>{children}</AuthContext.Provider>
}

app/layout.tsx
import { AuthProvider } from '@/libs/components/providers/auth'
import { cn } from '@/libs/utils'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'], variable: '--font-sans' })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="ja">
      <body className={cn('min-h-screen bg-background font-sans antialiased', inter.variable)}>
        <AuthProvider>{children}</AuthProvider>
      </body>
    </html>
  )
}

https://yumeno.me/nextjs_app_document

Next.jsのappルートにした場合_appが使えなくなり、代わりにtemplateとlayoutになった。 そのため、共有のレイアウト、ページ全体に反映させたい場合はtemplateまたは、layoutを使うことで解決する。

templateとlayoutの違い
ページ全体に適用させるtemplateとlayoutの違いは再レンダリングするか、しないかの違いがある。ページ移行時、layoutは再レンダリングせず。templateは再レンダリングされる。
そのため、パフォーマンスを考慮するならlayoutを使う方がよい。

https://reffect.co.jp/react/react-firebase-auth

kodukakoduka

LINE認証を試みたら、This channel is now developing status. User need to have developer role.と出た。チャンネルのステータスが開発中になっている場合は、ユーザーをそのチャンネルに招待して、権限を割り当てる必要があるとのこと。
Testerの権限を割り当てて招待したら、認証ができるようになった。
また、招待ユーザーはLINEのビジネスアカウントで必要があるっぽい。

https://qiita.com/mono_glyceride/items/88255aa0401c40fcaaa9

kodukakoduka

サーバーアクション内でfirebase adminを読んだら、TypeError: Cannot read properties of undefined (reading 'INTERNAL')と出た。firebase-adminの初期化するときのimportの仕方違っていた。

- import { initializeApp } from 'firebase-admin'
+ import admin from 'firebase-admin'

- export const app = initializeApp()
+ export const app = admin.initializeApp()
kodukakoduka

TypescriptのArray型で配列が空かどうかの判別をarray.length === 0でやるのダサいなー。

kodukakoduka

Firebase Adminからid tokenを取得することはできないっぽい。
クライアントから送信されたトークンを検証することはできる。
そのため、未認証のユーザーがデータを作成する際には、Firebase Adminを使用して作成させるか、サーバー匿名ユーザーを作成してクライアントからFirebaseのAPIを叩くかの二択になりそう。

新規アカウントを作成する場合はAuthも触るので、サーバー側で行わせたい。

認証後のデータ作成・更新はクライアントから行う。その際に、Firestoreルールを厳格に記述する。
また、ルールないでid tokenの期限チェックも行なってくれるので、サーバー側で実装する手間が減る。

https://firebase.google.com/docs/auth/admin/manage-sessions?hl=ja

Firebase ID トークンはステートレス JWT であるため、トークンの取り消しの判定は、Firebase Authentication バックエンドからトークンのステータスを要求するしかありません。そのため、このチェックをサーバーで実行すると、ネットワークで追加のラウンドトリップを必要とする負荷の大きい処理となります。このネットワーク リクエストは、Admin SDK を使用してチェックを行うのではなく、取り消しを確認する Firebase セキュリティ ルールを設定することで回避できます。

kodukakoduka

クラアントライブラリのFirestoreを使いたいからServerComponentsの選択肢はないな。
もしServerComponentsを使うなら、firebase admin sdkを使うことになる。
となると、SWRを使用することになるのかな。

https://zenn.dev/ojin/articles/8b383b0ac98eb9

しばらくの間は、ClientComponentsで使う時はSWRなどを使用するのが良さそう。そう考えると、fetchは現時点ではSWRを使うか、ServerComponentsに入れてしまうのがベターだろう。

kodukakoduka

SWR+Suspenceで使うことは推奨されていないな。
ということは、firestore sdkでSuspenceを利用することはできないのか。

https://github.com/vercel/swr/issues/1898#issuecomment-1094426113

SWR ではサスペンスはまだ実験段階なので、ベータ機能を使用するリスクがあります。以下の理由により、安定させることはできません。

まだすべての SWR 機能をカバーできるわけではありません。たとえば、ウォーターフォールや依存フェッチの回避などです。

SSR でのデータ取得ユースケースを十分にカバーすることはできません (多くのフレームワークがカバーしています)。以下を参照してください。

React 18はメジャーバージョンの変更であり、@stefee前述の通り、特にサスペンスの動作は変わります。17から18にアップグレードすると、

SSR 中にサスペンスが実行されるので、サーバー側で SWR リクエストが機能することを確認してください。

SSR 中に Suspense が実行されるため、レンダリングされた結果もハイドレーションされます。要求されたデータとレンダリングされたデータが SSR とハイドレーションの間で変更されると、ハイドレーション不一致エラーが発生します。そのため、Suspense + SSR を使用して動的データを取得しないようにしてください。

kodukakoduka

エラーコードのマップを使わないで、文字列でチェックした方が良いのか。

https://firebase.google.com/docs/reference/js/auth.md?hl=ja&_gl=114gx7tq_upMQ.._gaNTE3NzA3MTMyLjE3MjE1NDk4MDI._ga_CW55HF8NVT*MTcyMTU0OTgwMS4xLjAuMTcyMTU0OTgwMS4wLjAuMA..#autherrorcodes

SDK がスローするエラーを簡単に比較するための Auth エラーコードのマップ。

マップ内の個々のキーをツリー シェイクすることはできないため、マップを使用するとバンドルサイズが大幅に増加する可能性があります。

kodukakoduka

5回間違えたら、アカウントをロックしてくれるはずだが、リンク切れを起こしていて現状どうなのか分からない。

https://qiita.com/marchin_1989/items/ade93705dbf3c72e1ce0

また、パスワードを5回間違えると、以下のエラーが返ってきます。

何度もパスワードを間違えた
errorCode: auth/too-many-requests
message: Access to this account has been temporarily disabled due to many failed login attempts. You can immediately restore it by resetting your password or you can try again later.
https://firebase.google.com/docs/reference/js/firebase.auth.Error

kodukakoduka

ChatGPT先生に聞いた感じ、auth/too-many-requestsのエラーが発生した場合、Authの該当アカウントはロックされるが、具体的に、どれぐらいの回数のリクエストを送信したらロックされるかのドキュメントはないらしい。また、一定時の時間が経過すれば自動的にアカウントロックは解除されるが、手動で解除することは不可能らしい。

Firebase Authenticationのエラーコードauth/too-many-requestsは、短時間に過剰な数のリクエストを行った場合に発生しますが、具体的な回数は公開されていません。このエラーは、不正使用やスパムを防ぐためのレートリミットによるもので、特定の回数を超えると発生します。

auth/too-many-requestsエラーが発生した場合、Firebase Authenticationは過剰なリクエストを受けたアカウントを一時的にロックすることになります。これは不正使用やスパム行為からアカウントを保護するための措置です。

Firebase Authenticationでは、管理者が特定のアカウントのロックを解除するための直接的な機能は提供されていません。auth/too-many-requestsエラーは一時的なものであり、時間が経過すれば自動的に解除されます。

kodukakoduka

Suspenseはサーバーコンポーネント専用だと感じた。
クライアントコンポーネントでも利用できるらしいが、無限にレンダリングし、
throw promiseが一向に解決されない。

kodukakoduka

object-fit: cover;を適応すれば、画像のアスペクト比を保ったまま、サイズを合わせられる。
また、親コンポーネントでoverflow: hidden;が適用されていれば、border-radiusしたときにはみ出した部分を非表示にすることができる。

あれ?object-fit: contain;の方がしっくりくるな。

https://developer.mozilla.org/ja/docs/Web/CSS/object-fit#cover

置換コンテンツはアスペクト比を維持したまま、要素のコンテンツボックス全体を埋めるように拡大縮小されます。オブジェクトのアスペクト比がボックスのアスペクト比と合わない場合は、オブジェクトの方が合うように切り取られます。

kodukakoduka

sharpね。あとで試してみよう。

⚠ For production Image Optimization with Next.js, the optional 'sharp' package is strongly recommended. Run 'npm i sharp', and Next.js will use it automatically for Image Optimization.

kodukakoduka

next.config.mjs内でnext.config.mjs Parsing error: Cannot find module 'next/babel'が発生していた。.vscode/setting.jsonに以下を追加すれば解決することがわかった。

.vscode/setting.json
"eslint.workingDirectories": ["next-app"]

原因としては、Nextアプリのディレクトリをサブディレクトリにしていたことで、Eslintが管理先のディレクトリを見失う事が原因らしい。

https://zenn.dev/msk1206/articles/6d8731f6fc8fb3

kodukakoduka

firebaseにデプロイした時に、png画像が404で取得できない。
next.config.mjsの設定で画像の最適化をOFFにすれば、解決する。

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: false,
  images: {
    unoptimized: true,
  },
}

export default nextConfig

色々調べたけど、解決できないから、
もし画像の最適化をしたいなら、apiを用意してローダーで解決しろってことなんだろうな。
アセット画像を全てsvgで用意すれば上記のような問題は発生しないから、画像はsvgにしてくれると助かる。

kodukakoduka

外部アカウントでサインアップする際に、firestoreにアカウントデータが保存されていない場合、新規作成をし、アカウントが作成できたら、画面遷移をする。
firebase auth とfirestoreにアカウントデータの作成タイミングが異なるので、userがあるかどうかだけだと、firestoreにアカウントデータが作成される前に画面遷移できてしまう。
そのため、アカウントデータをonSnapshotを使って監視するのだが、onSnapshotはコミットされていなくても発火するため、以下のように工夫が必要。

data/account.ts
 const unsubscribe = onSnapshot(docRef, {
    next: async (snapshot) => {
      if (!snapshot.metadata.hasPendingWrites) { // trueの時、コミット中ということ
        observe(snapshot.data())
      }
    },
  })

(property) SnapshotMetadata.hasPendingWrites: boolean

スナップショットに、まだバックエンドにコミットされていないローカル書き込み (例えば set() や update() 呼び出し) の結果が含まれている場合、true を返します。リスナーが(SnapshotListenOptionsを介して)メタデータの更新をオプトインしている場合、書き込みがバックエンドにコミットされると、hasPendingWritesがfalseに等しい別のスナップショットを受け取ります。

このスクラップは3ヶ月前にクローズされました