快速入门: Supabase

快速入门 Supabase

简介

Supabase 是一个开源的 Firebase 替代品。

本快速入门是一个简单的示例,展示如何将 Supabase 与 Plasmo 结合使用。

先决条件

使用 Supabase 初始化 Plasmo 项目

pnpm create plasmo --with-supabase

设置环境变量

为了使 Supabase 正常工作,我们需要定义一个 URL 和一个 KEY。

你可以在 Supabase 项目仪表板中找到它们:

让我们在 .env 文件中添加它们:

.env
PLASMO_PUBLIC_SUPABASE_URL="更改我"
PLASMO_PUBLIC_SUPABASE_KEY="更改我"

Supabase 存储库

我们需要初始化 Supabase,因此让我们添加一个名为 core/supabase.ts 的文件:

core/supabase.ts
import { createClient } from "@supabase/supabase-js"
 
import { Storage } from "@plasmohq/storage"
 
const storage = new Storage({
  area: "local"
})
 
export const supabase = createClient(
  process.env.PLASMO_PUBLIC_SUPABASE_URL,
  process.env.PLASMO_PUBLIC_SUPABASE_KEY,
  {
    auth: {
      storage,
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: true
    }
  }
)

添加重定向 URL

当用户注册时,他们需要确认他们的电子邮件。为此,我们需要为我们的 Supabase 项目添加一个重定向 URL。

首先,我们需要为开发创建一个一致的扩展 ID。当你推送到不同的网络商店时,你会得到一个不同的 ID。要了解所有这些是如何工作的,请查看我们关于创建一致的扩展 ID (opens in a new tab)的博客文章。

前往 Itero KeyPair 工具 (opens in a new tab)生成你的扩展 ID。

我们可以将 ID 和公钥存储在我们的 .env 文件中:

.env
CRX_ID="替换为 Itero KeyPair 工具提供的 CRX ID 值"
CRX_KEY="替换为 Itero KeyPair 工具提供的公钥值"

然后,在你的 package.json 中引用公钥作为 manifest.key 值:

"manifest": {
  "host_permissions": [
    "https://*/*"
  ],
  "key": "$CRX_KEY",
}

现在我们需要确保浏览器不会阻止对选项页面的访问。为此,我们必须将其添加到清单中的 web_accessible_resources

"web_accessible_resources": [
  {
    "resources": [
      "options.html"
    ],
    "matches": [
      "<all_urls>"
    ],
    "extension_ids": [
      "$CRX_ID"
    ]
  }
]

前往 Supabase 控制台并点击“身份验证”选项卡,然后点击 URL 配置。

现在在站点 URL 和重定向 URL 中添加以下 URL:

chrome-extension://<CRX_ID>/options.html

CRX_ID 替换为 Itero KeyPairs 工具或生产网络商店给你的实际扩展 ID。

与 React 组件集成

现在我们可以在 React 组件中编写代码,利用 Supabase!

这里是一个使用 Supabase 在扩展选项页面中的 React 组件示例。

options.tsx
import type { Provider, User } from "@supabase/supabase-js"
import { useEffect, useState } from "react"
 
import { Storage } from "@plasmohq/storage"
import { useStorage } from "@plasmohq/storage/hook"
 
import { supabase } from "~core/supabase"
 
function IndexOptions() {
  const [user, setUser] = useStorage<User>({
    key: "user",
    instance: new Storage({
      area: "local"
    })
  })
 
  const [username, setUsername] = useState("")
  const [password, setPassword] = useState("")
 
  useEffect(() => {
    async function init() {
      const { data, error } = await supabase.auth.getSession()
 
      if (error) {
        console.error(error)
        return
      }
      if (!!data.session) {
        setUser(data.session.user)
      }
    }
 
    init()
  }, [])
 
  const handleEmailLogin = async (
    type: "LOGIN" | "SIGNUP",
    username: string,
    password: string
  ) => {
    try {
      const {
        error,
        data: { user }
      } =
        type === "LOGIN"
          ? await supabase.auth.signInWithPassword({
              email: username,
              password
            })
          : await supabase.auth.signUp({ email: username, password })
 
      if (error) {
        alert("身份验证错误: " + error.message)
      } else if (!user) {
        alert("注册成功,确认邮件应该很快发送!")
      } else {
        setUser(user)
      }
    } catch (error) {
      console.log("error", error)
      alert(error.error_description || error)
    }
  }
 
  const handleOAuthLogin = async (provider: Provider, scopes = "email") => {
    await supabase.auth.signInWithOAuth({
      provider,
      options: {
        scopes,
        redirectTo: location.href
      }
    })
  }
 
  return (
    <main
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        width: "100%",
        top: 240,
        position: "relative"
      }}>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          width: 240,
          justifyContent: "space-between",
          gap: 4.2
        }}>
        {user && (
          <>
            <h3>
              {user.email} - {user.id}
            </h3>
            <button
              onClick={() => {
                supabase.auth.signOut()
                setUser(null)
              }}>
              登出
            </button>
          </>
        )}
        {!user && (
          <>
            <label>邮箱</label>
            <input
              type="text"
              placeholder="您的用户名"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
            />
            <label>密码</label>
            <input
              type="password"
              placeholder="您的密码"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
 
            <button
              onClick={(e) => {
                handleEmailLogin("SIGNUP", username, password)
              }}>
              注册
            </button>
            <button
              onClick={(e) => {
                handleEmailLogin("LOGIN", username, password)
              }}>
              登录
            </button>
 
            <button
              onClick={(e) => {
                handleOAuthLogin("github")
              }}>
              使用 GitHub 登录
            </button>
          </>
        )}
      </div>
    </main>
  )
}
 
export default IndexOptions

完整示例

要查看完整的示例,请查看我们示例仓库中的 with-supabase (opens in a new tab)