设置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.tsx
和style.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
运行此命令
选择屏幕截图中显示的以下选项。
将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变量要导出,一个以supabaseManual
和supabaseAuto
的形式出口,前者用于手动存储方法,用于修改supabase createClient()
配置中的存储变量的梯子使用chrome.storage
或Plasmo存储API。在一个典型的工作项目中,该变量应仅被称为supabase
,而不是supabaseAuto
或supabaseManual
,但是对于此演示,我们进行了澄清。
在涵盖background.ts
文件中涵盖BGSW的部分中,将解释其余的supabaseManual
功能。
supabaseAuto
的灵感来自下面的GitHub线程。
https://gist.github.com/lcmchris/da979cbbf56c9452b6e5847ece7ee6ca
在optionsForMethod2
中,为chrome.storage
API,getItem
,setItem
和removeItem
中使用的每种方法创建了一个函数。
为什么getItem
,setItem
和removeItem
?
下面的方法仅在您在本地设备上安装库时才能起作用,我无法在下面发布的GitHub存储库中找到这些类型。
https://github.com/supabase/supabase-js/blob/master/src/SupabaseClient.ts
转到node_modules
文件夹中的@supabase/supabase-js
文件夹,转到types.ts
node_modules
@supabase/supabase-js
src
- lib
- types.ts
命令 +单击(控制 +单击Windows)在GoTrueClient
导入功能
命令 +单击(控制 +单击Windows)在SupportedStorage
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
返回supabaseAccessToken
,supabaseExpiration
和userId
的方法,可以采取下一步以验证请求。
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()
函数中利用。
supabaseAuto
&supabaseManual
的消息处理程序
// 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()
对于supabaseAuto
和supabaseManual
方法将有所不同。基本上,它检查是否有一个活动会话,如果令牌过期,则使用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")
}
})
}, [])
supabaseAuto
的useEffect()
比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>
)
}
否则,它与supabaseAuto
和supabaseManual
非常相似,但是,登录,签名和签名函数有些不同,因为它直接修改了supabase变量而不利用消息传递功能。
其他资源
以下是一篇很好的文章,讲述了如何在MV3 Chrome扩展中进行OAuth身份验证。
https://gourav.io/blog/supabase-auth-chrome-extension
结论
有很多用于存储Auth的访问令牌的方法,我介绍了三种方法。这三种方法都适用于MV3,这应该是一个合理的模板,以构建其余的镀铬扩展,我希望这对阅读它的任何人都有帮助。如果您喜欢我的工作,请让我关注twitter。如果您想跳过教程,请查看下面的GitHub存储库。
https://github.com/remusris/plasmo_tailwinds_supabase_scaffold