Chrome扩展MV3模板:Supabase Auth,Plasmo,Tailwinds CSS和Shadcn UI
#javascript #网络开发人员 #tailwindcss #extensions

设置plasmo,tailwinds&Shadcn-UI

设置Plasmo

pnpm create plasmo
# OR
yarn create plasmo
# OR
npm create plasmo

从下面的基本命令开始使用Plasmo,在本指南中坚持使用PNPM,因为我们将使用的是。

https://docs.plasmo.com/framework

可以手动添加尾风CSS。

pnpm create plasmo --with-tailwindcss

您可以通过使用上面的命令跳过设置尾风,但是如果您想知道如何在Plasmo项目中手动将CSS添加到Parswinds CSS,请继续阅读。下面的链接也是另一个很好的参考。

https://docs.plasmo.com/quickstarts/with-tailwindcss

pnpm i -D tailwindcss postcss autoprefixer
npx tailwindcss init

使用您的项目使用create plasmo命令进行设置,我们可以开始设置Tailwinds CSS。

/**
 * @type {import('postcss').ProcessOptions}
 */
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {}
  }
}

创建一个称为postcss.config.js的文件,将代码粘贴到该文件中。

@tailwind base;
@tailwind components;
@tailwind utilities;

创建一个名为style.css的文件,这等同于您通常会在其他尾风项目中找到的globals.css文件。

创建一个src文件夹,然后将popup.tsxstyle.css文件放入其中

tsconfig.ts文件中的路径更改为导入别名~的源文件夹。

{
  "extends": "plasmo/templates/tsconfig.base",
  "exclude": [
    "node_modules"
  ],
  "include": [
    ".plasmo/index.d.ts",
    "./**/*.ts",
    "./**/*.tsx"
  ],
  **"compilerOptions": {
    "paths": {
      "~*": [
        "./src/*"
      ]
    },**
    "baseUrl": "."
  }
}

设置shadcn-ui

既然已经添加了尾风,我们将通过CLI添加Shadcn-UI。跟随或以下面的指南为参考。

https://ui.shadcn.com/docs/installation/next

pnpm dlx shadcn-ui@latest init

运行此命令

Image description

选择屏幕截图中显示的以下选项。

globals.css的要求指向位于src目录内的style.css文件。

使用导入别名~src目录中导入组件,这是可选的,但建议使用。对于lib/util做同样的操作,请确保不要执行~~lib/utils.ts~~,因为它会自动添加ts文件结束。

如何在popup.tsx文件中使用尾风

您必须导入style.css,使用我们设置的导入别名

import "~style.css"

MV3 Chrome扩展的Supabase Auth

安装supabasejs客户端

pnpm add @supabase/supabase-js

授权介绍在镀铬扩展中

身份验证本质上是一种以访问令牌和刷新令牌的形式存储的JWT。当访问令牌到期时,将刷新令牌交换为新的访问令牌。在下一节中,我们讨论了如何访问和刷新令牌访问并存储在Chrome MV3 Chrome扩展中。

访问令牌和刷新令牌存储 - 三种方法

我们将涵盖有关如何存储和访问以及刷新令牌的三种方法。第一种方法利用chrome.storage API,而无需修改Supabase createClient函数中的任何选项。基本上,您可以手动创建逻辑来验证令牌是否已过期,Supabasejs库的createClient功能部分通常在WebApp的背景中使用。此手册方法可以与任何可为您提供访问令牌和刷新令牌的API配合使用。第二种方法修改了supabase createClient功能,以在后台利用chrome.storage api。第三种方法指出,存储到Plasmo存储API,仅在Plasmo框架中工作。

方法将被称为supabaseManual,方法两个将称为supabaseAuto,第三种方法将称为supabasePlasmo

设置Supabase客户端

import { createClient } from "@supabase/supabase-js"

// this is the default for method 1, we leave all the options on by default
export const supabaseManualStorage = createClient(
  process.env.PLASMO_PUBLIC_SUPABASE_URL,
  process.env.PLASMO_PUBLIC_SUPABASE_KEY
)

// this options is for method 2, method 3 also has it's own custom options
const optionsForMethod2 = {
  auth: {
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: true,
      storage: {
          async getItem(key: string): Promise<string | null> {
              // @ts-ignore
              const storage = await chrome.storage.local.get(key);
              return storage?.[key];
          },
          async setItem(key: string, value: string): Promise<void> {
              // @ts-ignore
              await chrome.storage.local.set({
                  [key]: JSON.parse(value)
              });
          },
          async removeItem(key: string): Promise<void> {
              // @ts-ignore
              await chrome.storage.local.remove(key);
          }
      }
  }
}

export const supabaseAuto = createClient(
  process.env.PLASMO_PUBLIC_SUPABASE_URL,
  process.env.PLASMO_PUBLIC_SUPABASE_KEY,
  optionsForMethod2
)

对于此示例的情况,我们有两个不同的supabase变量要导出,一个以supabaseManualsupabaseAuto的形式出口,前者用于手动存储方法,用于修改supabase createClient()配置中的存储变量的梯子使用chrome.storage或Plasmo存储API。在一个典型的工作项目中,该变量应仅被称为supabase,而不是supabaseAutosupabaseManual,但是对于此演示,我们进行了澄清。

在涵盖background.ts文件中涵盖BGSW的部分中,将解释其余的supabaseManual功能。

supabaseAuto的灵感来自下面的GitHub线程。

https://gist.github.com/lcmchris/da979cbbf56c9452b6e5847ece7ee6ca

optionsForMethod2中,为chrome.storage API,getItemsetItemremoveItem中使用的每种方法创建了一个函数。

为什么getItemsetItemremoveItem

Image description

下面的方法仅在您在本地设备上安装库时才能起作用,我无法在下面发布的GitHub存储库中找到这些类型。

https://github.com/supabase/supabase-js/blob/master/src/SupabaseClient.ts

Path
转到node_modules文件夹中的@supabase/supabase-js文件夹,转到types.ts

node_modules @supabase/supabase-js src - lib - types.ts

GoTrueClient
命令 +单击(控制 +单击Windows)在GoTrueClient导入功能

Supported Storage
命令 +单击(控制 +单击Windows)在SupportedStorage

GetItem, RemoveItem, SetItem
那应该带您进入突出显示的线路。

Plasmo存储API方法

Plasmo存储API方法是最简单的解决方案,但仅限于Plasmo扩展框架。但是,使用下面的链接作为参考,但是,指令不太清楚。

https://docs.plasmo.com/quickstarts/with-supabase

pnpm add @plasmohq/storage

将Plasmo存储API添加到您的项目

import { createClient } from "@supabase/supabase-js"
import { Storage } from "@plasmohq/storage"

const storage = new Storage({
  area: "local"
})

const options = {
    auth: {
        storage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true
    }
}

// this is for method 2 & 3
export const supabasePlasmo = createClient(
  process.env.PLASMO_PUBLIC_SUPABASE_URL,
  process.env.PLASMO_PUBLIC_SUPABASE_KEY, 
    options
)

创建一个称为storage的变量,该变量桥接到plasmo存储API上,应该全部。 popup.tsx页面还有更多设置,但在以下各节中涵盖。

背景服务工作者bgsw - Background.ts

background.ts放在哪里?

可以将background.ts文件放入后台文件夹中名为index.ts的文件夹中。您可能要使用文件夹方法的原因是您可以在背景文件夹中嵌套消息传递文件夹。否则,您只需在不在背景文件夹中的src文件夹中粘贴background.ts即可。

手动添加和删除访问令牌

supabaseManual方法中,有一些基本操作用于访问和修改访问令牌,getSupabaseKeys()validateToken()getKeyFromStorage()getKeyFromStorage()setKeyInStorage()removeKeysFromStorage()。指向对象变量中的访问,该对象变量存储特定chrome.storage位置的字符串值。虽然从技术上讲,这并不需要尝试记住每次确切的字符串会变得乏味。

// init chrome storage keys
const chromeStorageKeys = {
  supabaseAccessToken: "supabaseAccessToken",
  supabaseRefreshToken: "supabaseRefreshToken",
  supabaseUserData: "supabaseUserData",
  supabaseExpiration: "supabaseExpiration",
  supabaseUserId: "supabaseUserId"
}

您可以在适合您的需求时向chromeStorageKeys对象添加任意数量的所需属性。

// get the supabase keys
async function getSupabaseKeys() {
  const supabaseAccessToken = await getKeyFromStorage(
    chromeStorageKeys.supabaseAccessToken
  )
  const supabaseExpiration = (await getKeyFromStorage(
    chromeStorageKeys.supabaseExpiration
  )) as number
  const userId = await getKeyFromStorage(chromeStorageKeys.supabaseUserId)

  return { supabaseAccessToken, supabaseExpiration, userId }
}

getSupabaseKeys()函数基本上是更基本的getKeyFromStorage()方法的中介。

// get the key from storage
async function getKeyFromStorage(key) {
  return new Promise((resolve, reject) => {
    chrome.storage.local.get(key, (result) => {
      if (chrome.runtime.lastError) {
        reject(chrome.runtime.lastError)
      } else {
        resolve(result[key])
      }
    })
  })
}

getKeyFromStorage()方法将chrome.storage.local.get承诺置于异步函数中,它的功能非常有限,但我们将getSupabaseKeys()功能中的所有其他内容抽象。

//setting keys in local storage
async function setKeyInStorage(
  keyValuePairs: Record<string, any>
): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    chrome.storage.local.set(keyValuePairs, () => {
      if (chrome.runtime.lastError) {
        reject(chrome.runtime.lastError)
      } else {
        resolve()
      }
    })
  })
}

setKeyInStorage()基本上将密钥上传到存储中,因为使用chrome.storage api时没有更新功能。

//removing keys from local storage
async function removeKeysFromStorage(keys: string[]): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    chrome.storage.local.remove(keys, () => {
      if (chrome.runtime.lastError) {
        reject(chrome.runtime.lastError)
      } else {
        resolve()
      }
    })
  })
}

当用户登录和 /或其访问令已过期时,removeKeysFromStorage()派上用场,因此要求用户再次登录。

使用getSupabaseKeys返回supabaseAccessTokensupabaseExpirationuserId的方法,可以采取下一步以验证请求。

async uploadFunction() {
    const { supabaseAccessToken, supabaseExpiration, userId } = await getSupabaseKeys()
  await validateToken(supabaseAccessToken, supabaseExpiration)

    // do something here
    // either send a message to the bgsw or upload data to the server
}

上面的代码块是示例函数的模板,其中访问和到期时间从getSupabaseKeys()函数输出到validateToken()函数中。使用经过验证的访问令牌可以将请求发送回服务器和发送到BGSW的消息。

// validate the token
async function validateToken(supabaseAccessToken, supabaseExpiration) {
  const currentTime = Math.floor(Date.now() / 1000)
  if (!supabaseAccessToken) {
    throw new Error("No Supabase access token found")
  }
  if (currentTime > supabaseExpiration) {
    handleMessage({ action: "refresh", value: null })
    throw new Error("Supabase access token is expired")
  }
}

这是validateToken()函数,它在supabaseManual方法的handleMessage()函数中利用。

supabaseAutosupabaseManual的消息处理程序

// the event listener
chrome.runtime.onMessage.addListener((message, sender, response) => {
//   handleMessageManual(message, sender, response)
  handleMessageAutomatic(message, sender, response)
  return true
})

chrome.runtime.onMessage事件侦听器需要在后台运行,其中适当的handleMessage()函数将在您使用supabaseAuto还是supabaseManual方法的情况下有效。 supabasePlasmo方法不使用消息传递来验证popup.tsx和BGSW之间,因此它没有消息处理程序功能。

handleMessageManual() for supabaseManual

// handle message functionality for manual keys
async function handleMessageManual({ action, value }, sender?, response?) {
  if (action === "signin") {
    console.log("requesting auth")

    const { data, error } = await supabase.auth.signInWithPassword(value)
    if (data && data.session) {
      await setKeyInStorage({
        [chromeStorageKeys.supabaseAccessToken]: data.session.access_token,
        [chromeStorageKeys.supabaseRefreshToken]: data.session.refresh_token,
        [chromeStorageKeys.supabaseUserData]: data.user,
        [chromeStorageKeys.supabaseExpiration]: data.session.expires_at,
        [chromeStorageKeys.supabaseUserId]: data.user.id
      })
      console.log("User data stored in chrome.storage.sync")
      response({ data, error })
    } else {
      console.log("failed login attempt", error)
      response({ data: null, error: error })
    }
  } else if (action === "signup") {
    const { data, error } = await supabase.auth.signUp(value)
    if (data) {
      await setKeyInStorage({
        [chromeStorageKeys.supabaseAccessToken]: data.session.access_token,
        [chromeStorageKeys.supabaseRefreshToken]: data.session.refresh_token,
        [chromeStorageKeys.supabaseUserData]: data.user,
        [chromeStorageKeys.supabaseExpiration]: data.session.expires_at,
        [chromeStorageKeys.supabaseUserId]: data.user.id
      })
      console.log("User data stored in chrome.storage.sync")
      response({ message: "Successfully signed up!", data: data })
    } else {
      response({ data: null, error: error?.message || "Signup failed" })
    }
  } else if (action === "signout") {
    const { error } = await supabase.auth.signOut()
    if (!error) {
      await removeKeysFromStorage([
        chromeStorageKeys.supabaseAccessToken,
        chromeStorageKeys.supabaseRefreshToken,
        chromeStorageKeys.supabaseUserData,
        chromeStorageKeys.supabaseExpiration,
        chromeStorageKeys.supabaseUserId
      ])
      console.log("User data removed from chrome.storage.sync")
      response({ message: "Successfully signed out!" })
    } else {
      response({ error: error?.message || "Signout failed" })
    }
  } else if (action === "refresh") {
    const refreshToken = (await getKeyFromStorage(
      chromeStorageKeys.supabaseRefreshToken
    )) as string
    if (refreshToken) {
      const { data, error } = await supabase.auth.refreshSession({
        refresh_token: refreshToken
      })

      if (error) {
        response({ data: null, error: error.message })
        return
      }

      console.log("token data", data)

      if (!data || !data.session || !data.user) {
        await handleMessageManual(
          { action: "signout", value: null },
          sender,
          console.log
        )
        response({
          data: null,
          error: "Session expired. Please log in again."
        })
      } else {
        await setKeyInStorage({
          [chromeStorageKeys.supabaseAccessToken]: data.session.access_token,
          [chromeStorageKeys.supabaseRefreshToken]: data.session.refresh_token,
          [chromeStorageKeys.supabaseUserData]: data.user,
          [chromeStorageKeys.supabaseExpiration]: data.session.expires_at,
          [chromeStorageKeys.supabaseUserId]: data.user.id
        })

        console.log("User data refreshed in chrome.storage.sync")
        response({ data: data })
      }
    } else {
      response({ data: null, error: "No refresh token available" })
    }
  }
}

BGSW是位于chrome.storage API和popup.tsx文件中的AccessTokens之间的桥梁。它涵盖了Signin,签名,注册和刷新的基本操作。

handleMessageAutomatic()用于supabaseAuto

// handler for automatic messages
async function handleMessageAutomatic({ action, value }, sender?, response?) {
    if (action === "signin") {

        const { data, error } = await supabaseAuto.auth.signInWithPassword(value);

        if (data && data.session) {
            response({ data, error });
        } else {
            console.log("failed login attempt", error);
            response({ data: null, error: error });
        }

    } else if (action === "signup") {
        const { data, error } = await supabaseAuto.auth.signUp(value);

        if (data) {
            response({ message: "Successfully signed up!", data: data });
        } else {
            response({ data: null, error: error?.message || "Signup failed" });
        }

    } else if (action === "signout") {
        const { error } = await supabaseAuto.auth.signOut();
        if (!error) {
            response({ message: "Successfully signed out!" });
        } else {
            response({ error: error?.message || "Signout failed" });
        }

    } else if (action === "getsession") {
        console.log("inside get session")
        const { data, error } = await supabaseAuto.auth.getSession();

        console.log("data inside getSession", data)

        if (data && data.session) {
            const sessionExpiration = data.session.expires_at;
            const currentTime = Math.floor(Date.now() / 1000); // Convert to seconds

            if (sessionExpiration <= currentTime) {
                response({ error: "Session has expired" });
            } else {
                console.log("going to send data")
                response({ data: data });
            }

        } else {
            response({ error: "No session available" });
        }

    } else if (action === "refreshsession") {
        const { data, error } = await supabaseAuto.auth.refreshSession();

        response({ data: data, error: error });
    }
}

supabaseAuto方法的消息处理程序不是100%需要的,因为我们与第三方API进行了互动,最好从BGSW中调用supabase函数,因为这是任何外部API函数最佳的地方在镀铬扩展中。与supabaseManual不同,chrome.storage的功能被利用,因为所有这些功能都在后台发生。

创建Popup.tsx的组件,带有消息传递到bgsw

pnpm dlx shadcn-ui@latest add form

从ShadCN-UI库中添加内置的形式组件。

pnpm dlx shadcn-ui@latest add input

另外,添加输入组件

pnpm dlx shadcn-ui@latest add toast

如果发生不正确的登录,添加烤面包组件以表示向用户表示

// react stuff
import { useEffect, useState } from "react"

// supabase stuff
import type { Provider, User } from "@supabase/supabase-js"
import { supabase } from "./core/supabase"

// react-hook-form stuff
import { z } from "zod"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"

// shadcn-ui form components imports
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage
} from "~components/ui/form"
import { Toaster } from "~components/ui/toaster"
import { useToast } from "~components/ui/use-toast"

// tailwind stuff
import "~style.css"

// the supabase variable inputs
import { supabaseAuto, supabaseManual, supabasePlasmo } from "./core/supabase"

添加所有导入

// creating a form schema 
const formSchema = z.object({
  username: z.string().min(2).max(50),
  password: z.string().min(8)
})

创建一个表单架构组件,用户名和密码的最低和最大字符取决于您的要求,它们不需要。这将是React组件以外的唯一功能。

const { toast } = useToast();

const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      username: "",
      password: "",
    },
  });

实例化useToast()函数,以便烤面包机可以使用来自React-Hook-forms库的useForm挂钩创建一个称为form的变量。

useEffect()用于supabaseManual

useEffect(() => {
    setLoadingUser(true)

    chrome.storage.local.get(
      [
        chromeStorageKeys.supabaseAccessToken,
        chromeStorageKeys.supabaseExpiration,
        chromeStorageKeys.supabaseUserData
      ],
      (result) => {
        if (result && result[chromeStorageKeys.supabaseAccessToken]) {
          const currentTime = Math.floor(Date.now() / 1000) // convert to seconds
          const timeUntilExpiration =
            result[chromeStorageKeys.supabaseExpiration] - currentTime

          const refreshAndUpdate = () => {
            chrome.runtime.sendMessage({ action: "refresh" }, (response) => {
              if (response.error) {
                console.log("Error refreshing token: " + response.error)
              } else {
                if (response.data && response.data.session) {
                  console.log("Token refreshed successfully")
                  setUser(response.data.user)
                  setExpiration(response.data.session.expires_at)
                } else {
                  console.log("Error: session data is not available")
                }
              }
              setLoadingUser(false)
            })
          }

          if (timeUntilExpiration <= 0) {
            // Token is expired, request a refresh and update user and expiration
            console.log("Session expired, refreshing token")
            refreshAndUpdate()
          } else {
            // Token is not expired, set user data and expiration
            setUser(result[chromeStorageKeys.supabaseUserData])
            setExpiration(result[chromeStorageKeys.supabaseExpiration])

            if (timeUntilExpiration < 24 * 60 * 60) {
              // less than 24 hours left, request a refresh and update user and expiration
              console.log("Token is about to expire, refreshing token")
              refreshAndUpdate()
            } else {
              setLoadingUser(false) //Add this line
            }
          }
        } else {
          setLoadingUser(false) //Add this line
        }
      }
    )
  }, [])

安装了组件后,useEffect()需要运行以从存储中获取键,useEffect()对于supabaseAutosupabaseManual方法将有所不同。基本上,它检查是否有一个活动会话,如果令牌过期,则使用refreshAndUpdate()方法刷新了会话。 refreshAndUpdate()函数可以在useEffect()之外移动,然后进入React组件,然后在useEffect()内部调用,但这只是语义。

useEffect()用于supabaseAuto

useEffect(() => {
    chrome.runtime.sendMessage({ action: "getsession" }, (response) => {
      // console.log('sending getsession from popup')
      console.log("response", response)

      if (response.error) {
        // If session has expired, attempt to refresh it
        if (response.error === "Session has expired") {
          console.log("Session has expired, attempting to refresh...")
          refreshSession()
        } else {
          console.log("Error getting session: " + response.error)
        }
      } else if (response.data && response.data.session) {
        console.log("Session retrieved successfully")
        console.log("Session data: ", response.data.session)
        console.log("User data: ", response.data.session.user)
        setUser(response.data.session.user)
      } else {
        console.log("Error: session data is not available")
      }
    })
  }, [])

supabaseAutouseEffect()supabaseManual方法少得多,因为当这些操作发生在后台时,chrome.storage.local并未手动从chrome.storage.local中拉出。

handleSignin()handleSignout()refreshSession()方法

// create a function to handle login
  async function handleLogin(username: string, password: string) {
    try {
      // Send a message to the background script to initiate the login
      chrome.runtime.sendMessage(
        { action: "signin", value: { email: username, password: password } },
        (response) => {
          if (response.error) {
            // alert("Error with auth: " + response.error.message);

            toast({
              description: `Error with auth: ${response.error.message}`
            })

            console.log("Error with auth: " + response.error.message)
          } else if (response.data?.user) {
            setUser(response.data.user)
            setExpiration(response.data.session.expires_at)
          }
        }
      )
    } catch (error) {
      console.log("Error with auth: " + error.error.message)
      // alert(error.error_description || error);
    }
  }

  async function handleSignOut() {
    try {
        // Send a message to the background script to initiate the sign out
        chrome.runtime.sendMessage({ action: "signout" }, (response) => {
            if (response.error) {
                toast({ description: `Error signing out: ${response.error}` });
                console.log("Error signing out: ", response.error);
            } else {
                // Clear the user and session data upon successful sign-out
                setUser(null);
                setExpiration(0);
            }
        });
    } catch (error) {
        console.log("Error signing out: ", error.message);
    }
}

  const refreshSession = () => {
    chrome.runtime.sendMessage(
      { action: "refreshsession" },
      (refreshResponse) => {
        if (refreshResponse.error) {
          console.log("Error refreshing session: " + refreshResponse.error)
        } else if (refreshResponse.data && refreshResponse.data.session) {
          console.log("Session refreshed successfully")
          setUser(refreshResponse.data.user)
        } else {
          console.log("Error: refreshed session data is not available")
        }
      }
    )
  }

这些是用于将数据从popup.tsx发送到background.ts文件中BGSW的主要方法。 handleSignin()handleSignout()在返回语句中映射到按钮。这些功能都在反应组件内。

将所有内容放在返回语句中

return (
    <div className="w-96 px-5 py-4">
      <Toaster></Toaster>
      {user ? (
        // If user is logged in
        <div>
          <h1 className="text-xl font-bold mb-4">User Info</h1>
          <p>User ID: {user.id}</p>{" "}
          <Button onClick={handleSignOut}></Button>
        </div>
      ) : (
        <Form {...form}>
          <h1 className="text-xl font-bold mb-4">Basic Auth</h1>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
            <FormField
              control={form.control}
              name="username"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Password</FormLabel>
                  <FormControl>
                    <Input placeholder="username" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <FormField
              control={form.control}
              name="password"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Password</FormLabel>
                  <FormControl>
                    <Input placeholder="password" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <Button type="submit">Login</Button>
          </form>
        </Form>
      )}
    </div>
  )

有一个检查器检查是否有一个可用于用户的变量,如果没有用户,则显示登录表单,否则用户将进入应用程序。

使用下面的shadcn-ui表单链接作为学习如何使用React-Hook-Forms库的参考。

https://ui.shadcn.com/docs/components/form

Popup.tsx等离子体版本

此版本不会利用消息传递,因此登录/注册功能不会以相同的方式运行。使用下面的链接作为Supabase auth登录功能的参考。

https://docs.plasmo.com/quickstarts/with-supabase

useEffect()用于supabasePlasmo

useEffect(() => {
    async function init() {
      const { data, error } = await supabasePlasmo.auth.getSession()

      if (error) {
        console.error(error)
        return
      }
      if (!!data.session) {
        setUser(data.session.user)
      }
    }

    init()
  }, [])

由于没有发生消息传递,因此useEffect()对于supabasePlasmo版本更为简洁。

 // plasmo method
export default function LoginAuthFormPlasmo() {
  const [user, setUser] = useStorage<User>({
    key: "user",
    instance: new Storage({
      area: "local"
    })
  })

  const { toast } = useToast()
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      username: "",
      password: ""
    }
  })

  useEffect(() => {
    async function init() {
      const { data, error } = await supabasePlasmo.auth.getSession()

      if (error) {
        console.error(error)
        return
      }
      if (!!data.session) {
        setUser(data.session.user)
      }
    }

    init()
  }, [])

  const handleEmailLogin = async (type: "LOGIN" | "SIGNUP") => {
    const { username, password } = form.getValues()

    try {
      const { error, data } =
        type === "LOGIN"
          ? await supabasePlasmo.auth.signInWithPassword({
              email: username,
              password
            })
          : await supabasePlasmo.auth.signUp({
              email: username,
              password
            })

      if (error) {
        toast({
          description: `Error with auth: ${error.message}`
        })
      } else if (!data.user) {
        toast({
          description:
            "Signup successful, confirmation mail should be sent soon!"
        })
      } else {
        setUser(data.user)
      }
    } catch (error) {
      console.log("error", error)
      toast({
        description: error.error_description || error
      })
    }
  }

  return (
    <div className="w-96 px-5 py-4">
      <Toaster />
      {user ? (
        <>
          <h3>
            {user.email} - {user.id}
          </h3>
          <h1>this is plasmo </h1>
          <button
            onClick={() => {
              supabasePlasmo.auth.signOut()
              setUser(null)
            }}>
            Logout
          </button>
        </>
      ) : (
        <Form {...form}>
          <h1 className="text-xl font-bold mb-4">Login</h1>
          <form
            onSubmit={form.handleSubmit((data) => handleEmailLogin("LOGIN"))}
            className="space-y-8">
            <FormField
              control={form.control}
              name="username"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Email</FormLabel>
                  <FormControl>
                    <Input placeholder="Your Username" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <FormField
              control={form.control}
              name="password"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Password</FormLabel>
                  <FormControl>
                    <Input
                      type="password"
                      placeholder="Your Password"
                      {...field}
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <Button type="submit">Login</Button>
            <Button onClick={() => handleEmailLogin("SIGNUP")}>Sign up</Button>
          </form>
        </Form>
      )}
    </div>
  )
}

否则,它与supabaseAutosupabaseManual非常相似,但是,登录,签名和签名函数有些不同,因为它直接修改了supabase变量而不利用消息传递功能。

其他资源

以下是一篇很好的文章,讲述了如何在MV3 Chrome扩展中进行OAuth身份验证。


https://gourav.io/blog/supabase-auth-chrome-extension

结论

有很多用于存储Auth的访问令牌的方法,我介绍了三种方法。这三种方法都适用于MV3,这应该是一个合理的模板,以构建其余的镀铬扩展,我希望这对阅读它的任何人都有帮助。如果您喜欢我的工作,请让我关注twitter。如果您想跳过教程,请查看下面的GitHub存储库。

https://github.com/remusris/plasmo_tailwinds_supabase_scaffold