浏览器插件接入 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 });                                                                                                                                    
        }                                                                                                                                                                
      );                                                                                                                                                                 
    });                                                                                                                                                                  
  }                      
相关推荐
Asmewill1 小时前
DeepAgents学习笔记一(构建深度多智能体)
前端
万物皆对象6661 小时前
切换路由时页面空白问题(vue3)
前端·vue.js·typescript
突然好热1 小时前
TS 调试技巧
前端·javascript·typescript
h64648564h1 小时前
Flutter 国际化(i18n)全指南:一键切换中/英/日多语言
前端·javascript·flutter
令人头秃的代码0_01 小时前
AI时代下,如何做原子代码拆分
前端
我材不敲代码3 小时前
Python 函数核心:位置参数与关键字参数详解
java·前端·python
Kratzdisteln3 小时前
【无标题】
前端·python
Curvatureflight3 小时前
前端国际化 i18n 落地实践:语言包、动态文案和格式化问题怎么处理?
前端·c++·vue
kTR2hD1qb3 小时前
Claude Code Skill的介绍与使用
java·前端·数据库·人工智能