乐于分享
好东西不私藏

浏览器插件接入 Google 登录

浏览器插件接入 Google 登录

前言

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

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

技术选型

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

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

Google Cloud

登录Google Cloud[2],创建项目

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

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

Supabase

登录Supabase[3],创建新项目

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

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

注册邮箱授权

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

代码实现

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

  插件登录页                                                                                                                                                             
    -> 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. 1. supabase 获取 google 授权地址(data.url),redirectUrl 也就是我们在 supabase 配置的授权回调 url,上面第 8 步的地址
const { data, error } = await supabase.auth.signInWithOAuth({                                                                                                        
    provider,                                                                                                                                                          
options: {                                                                                                                                                         
redirectTo: redirectUrl,                                                                                                                                         
skipBrowserRedirecttrue,                                                                                                                                       
    },                                                                                                                                                                 
  }); 
  1. 2. chrome.identity.launchWebAuthFlow 打开授权窗口
chrome.identity.launchWebAuthFlow(                                                                                                                                 
  {                                                                                                                                                                
url: data.url,                                                                                                                                                 
interactivetrue,                                                                                                                                             
  },                                                                                                                                                               
async (responseUrl) => { })
  1. 3. google 授权后,经过上面 5-9 的过程,返回 access_token 和 refresh_token 信息,在回调里处理
asyncfunctionsignInWithOAuth(provider'github' | 'google') {
// 
const redirectUrl = chrome.identity.getRedirectURL();                                                                                                                

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

if (error) throw error;                                                                                                                                              

returnnewPromise((resolve) => {                                                                                                                                    
    chrome.identity.launchWebAuthFlow(                                                                                                                                 
      {                                                                                                                                                                
url: data.url,                                                                                                                                                 
interactivetrue,                                                                                                                                             
        },                                                                                                                                                               
async (responseUrl) => {                                                                                                                                         
if (chrome.runtime.lastError) {                                                                                                                                
resolve({ successfalseerror: chrome.runtime.lastError.message });                                                                                        
return;                                                                                                                                                      
          }                                                                                                                                                              

if (!responseUrl) {                                                                                                                                            
resolve({ successfalseerror'No response URL' });                                                                                                       
return;                                                                                                                                                      
          }                                                                                                                                                              

const url = newURL(responseUrl);                                                                                                                              
const hashParams = newURLSearchParams(url.hash.substring(1));                                                                                                 
const accessToken = hashParams.get('access_token');                                                                                                            
const refreshToken = hashParams.get('refresh_token');                                                                                                          

if (!accessToken) {                                                                                                                                            
resolve({ successfalseerror'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({                                                                                                                                                    
successfalse,                                                                                                                                            
error: sessionError?.message || 'Failed to establish session',                                                                                             
            });                                                                                                                                                          
return;                                                                                                                                                      
          }                                                                                                                                                              

awaitcheckSession();                                                                                                                                          
resolve({ successtrue });                                                                                                                                    
        }                                                                                                                                                                
      );                                                                                                                                                                 
    });                                                                                                                                                                  
  }                      

引用链接

[1] VideoTracker v0.0.1: https://video-tracker.ankkaya.top/
[2] Google Cloud: https://console.cloud.google.com/
[3] Supabase: https://supabase.com/dashboard/org/rfkfpbhbovkokfwlpmxd