浏览器插件接入 Google 登录

前言

VideoTracker v0.0.1 用户记录的视频信息都保存在本地,如果更换电脑或者浏览器,记录就无法同步,体验欠佳

在最新 v0.0.3 版本我加入了云端同步功能,突破本地单浏览器限制,实现随时随地同步

技术选型

服务端只需要登录和保存数据信息,supabase 即可满足需求。第三方登录使用 google 登录,没有 google 账户可以使用邮箱注册账号

正式开发前还需要完成 Google Cloud 和 Supabase 登录设置

Google Cloud

登录Google Cloud,创建项目

打开新建项目video-tracker,左侧菜单选择API和服务-已启动的API和服务,在 api 库开启Google+ API服务

选择API和服务-凭证新建凭证,选择OAuth 客户端 ID,虽然我们的应用是浏览器插件,但是需要在 Supabase 完成登录,所以要选择Web应用,点击创建。创建完成弹框里的客户端ID和客户端密钥记得保存下来,等一下需要用到

Supabase

登录Supabase,创建新项目

左侧菜单选择Authentication-Sign In / Providers,勾选Confirm email,新用户邮箱注册成功会收到确认邮件,点击确认邮件即可完成注册

下滑Auth Providers打开Google设置,输入刚才 Google Cloud 创建 OAuth 创建应用的 IDs 和密钥,回调地址填入刚才的 OAuth 应用,到这里 Google 授权登录配置已经完成

注册邮箱授权

如果是邮箱注册,收到授权链接是插件的地址,这里还需要在 Supabase 添加授权 url 回调,打开Authentication-URL Configuration,添加插件地址

代码实现

先让 ai 总结一下代码实现,我们实现某些功能时候,也可以先让 ai 创建功能文档

plain 复制代码
  插件登录页                                                                                                                                                             
    -> Supabase signInWithOAuth({ provider: 'google', redirectTo: chrome.identity.getRedirectURL() })                                                                    
    -> 打开 Supabase 生成的 Google OAuth URL                                                                                                                             
    -> 用户在 Google 授权                                                                                                                                                
    -> Google 重定向到 Supabase Callback URL                                                                                                                             
    -> Supabase 校验 Google 返回结果                                                                                                                                     
    -> Supabase 生成自己的 session / access_token / refresh_token                                                                                                        
    -> Supabase 再重定向到你传入的 redirectTo                                                                                                                            
    -> redirectTo 是 chrome.identity.getRedirectURL()                                                                                                                    
    -> Chrome 捕获 chromiumapp.org 回调                                                                                                                                  
    -> launchWebAuthFlow callback 收到 token URL                                                                                                                         
    -> 插件 setSession 登录成功 
  1. supabase 获取 google 授权地址(data.url),redirectUrl 也就是我们在 supabase 配置的授权回调 url,上面第 8 步的地址
typescript 复制代码
  const { data, error } = await supabase.auth.signInWithOAuth({                                                                                                        
    provider,                                                                                                                                                          
    options: {                                                                                                                                                         
      redirectTo: redirectUrl,                                                                                                                                         
      skipBrowserRedirect: true,                                                                                                                                       
    },                                                                                                                                                                 
  }); 
  1. chrome.identity.launchWebAuthFlow 打开授权窗口
typescript 复制代码
chrome.identity.launchWebAuthFlow(                                                                                                                                 
  {                                                                                                                                                                
    url: data.url,                                                                                                                                                 
    interactive: true,                                                                                                                                             
  },                                                                                                                                                               
  async (responseUrl) => { })
  1. google 授权后,经过上面 5-9 的过程,返回 access_token 和 refresh_token 信息,在回调里处理
plain 复制代码
typescript 复制代码
async function signInWithOAuth(provider: 'github' | 'google') {
  // 
  const redirectUrl = chrome.identity.getRedirectURL();                                                                                                                

  const { data, error } = await supabase.auth.signInWithOAuth({                                                                                                        
    provider,                                                                                                                                                          
    options: {                                                                                                                                                         
      redirectTo: redirectUrl,                                                                                                                                         
      skipBrowserRedirect: true,                                                                                                                                       
    },                                                                                                                                                                 
  });                                                                                                                                                                  

  if (error) throw error;                                                                                                                                              

  return new Promise((resolve) => {                                                                                                                                    
    chrome.identity.launchWebAuthFlow(                                                                                                                                 
      {                                                                                                                                                                
        url: data.url,                                                                                                                                                 
        interactive: true,                                                                                                                                             
        },                                                                                                                                                               
        async (responseUrl) => {                                                                                                                                         
          if (chrome.runtime.lastError) {                                                                                                                                
            resolve({ success: false, error: chrome.runtime.lastError.message });                                                                                        
            return;                                                                                                                                                      
          }                                                                                                                                                              
                                                                                                                                                                         
          if (!responseUrl) {                                                                                                                                            
            resolve({ success: false, error: 'No response URL' });                                                                                                       
            return;                                                                                                                                                      
          }                                                                                                                                                              
                                                                                                                                                                         
          const url = new URL(responseUrl);                                                                                                                              
          const hashParams = new URLSearchParams(url.hash.substring(1));                                                                                                 
          const accessToken = hashParams.get('access_token');                                                                                                            
          const refreshToken = hashParams.get('refresh_token');                                                                                                          
                                                                                                                                                                         
          if (!accessToken) {                                                                                                                                            
            resolve({ success: false, error: 'No access token in response' });                                                                                           
            return;                                                                                                                                                      
          }                                                                                                                                                              
                                                                                                                                                                         
          const { data: { session }, error: sessionError } =                                                                                                             
            await supabase.auth.setSession({                                                                                                                             
              access_token: accessToken,                                                                                                                                 
              refresh_token: refreshToken || '',                                                                                                                         
            });                                                                                                                                                          
                                                                                                                                                                         
          if (sessionError || !session) {                                                                                                                                
            resolve({                                                                                                                                                    
              success: false,                                                                                                                                            
              error: sessionError?.message || 'Failed to establish session',                                                                                             
            });                                                                                                                                                          
            return;                                                                                                                                                      
          }                                                                                                                                                              
                                                                                                                                                                         
          await checkSession();                                                                                                                                          
          resolve({ success: true });                                                                                                                                    
        }                                                                                                                                                                
      );                                                                                                                                                                 
    });                                                                                                                                                                  
  }                      
相关推荐
福兮说4 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理5 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6666 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)6 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan6 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_7 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒7 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技7 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu8 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
默_笙9 小时前
🛴 从散件到整机:DeepAgents 与 Agent 身上预留的那些"插槽"(前置介绍)
前端·javascript