浏览器插件接入 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 });                                                                                                                                    
        }                                                                                                                                                                
      );                                                                                                                                                                 
    });                                                                                                                                                                  
  }                      
相关推荐
大模型码小白20 分钟前
数据可视化:AI 生成 HTML5 动态交互式数据图表
前端·数据库·人工智能·深度学习·机器学习·信息可视化·html5
八荒启·交互动画20 分钟前
Web特效01—什么是渲染
前端·javascript·网页特效·八荒启-交互动画
小肥君24 分钟前
前端测试websocket
前端·websocket·状态模式
南京兴帝文化传媒有限公司1 小时前
基于地图平台的本地商户信息优化:药店夜间服务标注与客户转化实操
前端·javascript·数据库·人工智能·geo 优化·geo优化避坑·ai搜索获客
八荒启·交互动画1 小时前
Web特效04——GPUvs CPU,为什么图形计算要交给GPU,什么是“并行计算”
前端
呃呃呃呃ex1 小时前
2026年古法编程的末法时代,如何评估自己完成迅速转行
前端·后端
南京兴帝文化传媒有限公司1 小时前
地图SEO与AI搜索优化结合实践:宁国摄影工作室本地获客案例分析
大数据·前端·人工智能·geo 优化·geo优化避坑
用户921080262861 小时前
前端 Vue 专栏 07:模板编译、虚拟 DOM、Diff 与 key
前端
光影少年1 小时前
react navite process.nextTick 和 Promise.then 的执行顺序
前端·react native·react.js
雪芽蓝域zzs1 小时前
第四十二节:全局字典封装(后端字典,下拉选择复用)
开发语言·前端·javascript