Chrome扩展开发之四——核心功能的实现思路

目录:
 
  • 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述

  • 1.Chrome扩展开发之一——Chrome扩展的文件结构

  • 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制和通信方式

  • 3.Chrome扩展开发之三——Chrome扩展中的数据本地存储和下载

  • 4.Chrome扩展开发之四——核心功能的实现思路

  • 5.Chrome扩展开发之五——采用指数退避算法实现ajax请求的重发,全部完成时触发回调函数

如果你对GmailAssist感兴趣,可以在chrome商店中搜索“Gmail助手”,或点击这里直接访问商店来安装试用;
如果你对GmailAssist的源码感兴趣,可以在我的
GitHub上查看它的源码。

原计划专门用一篇博文简单介绍一下 gmail api 的,但考虑了一下觉得官方的文档已经很清楚了,也没什么不好理解的地方,我就不再专门写一篇文章来说它了。介绍 GmailAssist 的功能实现的过程中,如果有关于 gmail api 的需要再说明的地方,我会补充。

 

一、授权及界面显示

用户点击地址栏图标后,调用前一篇提到的用于chrome扩展进行 OAuth2 授权的库中的函数 authorize 来完成授权(详情可以查看其源码,很直观)。并向当前页面的 content script 发消息,使其通过修改页面的DOM元素,来显示 GmailAssist 的 UI。

需要在后台脚本中监听点击图标的事件,因而代码如下:

chrome.pageAction.onClicked.addListener(function () { google.authorize(function () { chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { chrome.tabs.sendMessage(tabs[0].id, {token: google.getAccessToken()}, function (response) { //可以写点处理response的逻辑。另外,上面google.getAccessToken() 是库中的函数。
 }); }); }); })

二、获取附件列表

这个很有说头。

先看几个相关的 gmail API:

首先几乎 gmail API 中所有的方法都需要提供授权信息,即通过OAuth2获得的token。几乎所有的方法也都要求一个参数指定所要操作的邮箱(userId),而这个参数可以通过赋值”me”来表明当前用户完成授权的邮箱。gmail API 的调用,都可以通过 HTTP 请求来完成。

1. Users.messages: list

用户点击“获取附件列表”按钮,就调用 gmail api 中的 Users.messages: list 方法来获取符合条件的(该方法可以带参数,获取特定的邮件集合)全部邮件。但该方法返回的结果中每个 message 默认只有 messageId 和 threadId 两个字段(后来了解到,应该可以通过指定参数,使其返回更多的字段),如这样:

{ "messages" : [ { "id": "152a445862514939" , "threadId": "152a445862514939"

 }, { 
"id": "152a348995acc143" , "threadId": "152a348995acc143"

 }, ... { 
"id": "14956ab84abc30c8" , "threadId": "14956ab84abc30c8"

 } ], 
"nextPageToken": "16041633375627414246" , "resultSizeEstimate": 103

}

在GmailAssist 中,我们要的只是带有附件的邮件,因而可以在list方法的参数中指定 q=has:attachment 来获取所有含附件的邮件信息。

这个参数是咋回事呢?可以在请求的URL中指定q字段。例如:

GET https://www.googleapis.com/gmail/v1/users/me/messages?q=has:attachment

能不能指定别的搜索条件?让q等于别的内容?可以。这个参数的值是支持gmail内置的搜索过滤条件语法的,详情可以参考这里

从上面的 HTTP 请求结果示例中不难看出,返回信息中有个字段:nextPageToken。顾名思义,这是获取list结果的下一页的令牌。list方法返回的结果在比较多时会分页,每页默认上限100项邮件信息,因而需要返回一页后用这个token去拿下一页。最后一页没有这个字段,因此可以通过

if (list.nextPageToken) { fetchNextList(list.nextPageToken); }

来递归地获取到完整的邮件列表。

2. Users.messages: get

通过 list 方法拿到了带附件的所有邮件的 id,接下来为了拿到里面的附件,就得用 get 方法,挨个获取具体的邮件,并从中提取出附件的信息。get 方法的具体信息,可以看 google 的文档,不多提了。

其中,附件的下载,没有直接的 api 可用,也不需要。根据自己多次手动在邮箱里操作的试验,找到了附件下载地址的规律,在程序里把它拼出来,在用户要下载相应附件时,把下载地址从content script 发到后台即可。对具体的下载地址长什么样子感兴趣的话,可以看源码

3. Users.drafts: listUsers.drafts: getUsers.drafts: update

(补充一点:如果你熟悉 gmail,或者已经玩了玩它的这些 API 的话,不难发现,gmail 中除草稿之外的信件们都是 message 类型的,而草稿是draft类型包装了一个message。)

要完成把选中的附件插到最新草稿中,没有直接的API可用,那就只能让程序麻烦一点了。我现在采用的思路如下:

首先获取最新草稿,即先调用drafts. list 方法返回草稿列表,由于结果是按照时间顺序排列的,所以我们再拿第一个草稿的 id 来调用 get 方法,获得这封草稿的全文。

把要插入的附件所在的邮件的全文用 messages.get 获取到,从中截取出选中的附件,将这些(如果有多个的话)附件插入一个队列中,然后再依次出队,拼到之前拿到的原草稿末尾。(这中间需要一点操作,主要是为了保证格式正确。细节可以参考RFC822协议,或者自己get几个 RAW 格式的message,按base64urlsafe格式来转码看看就清楚格式了)

拼好之后,用 drafts.update 方法直接把整个拼好的新草稿按base64urlsafe格式转码的结果,作为RAW字段传回服务器即可完成草稿的更新,也即完成了附件的插入。

这三个方法具体操作跟上面的 messages.list 和 messages.get 是类似的。需要注意的是,根据上述思路,通过 get 来获取草稿时,需要在参数中指明返回 RAW 类型的草稿内容。获得的RAW字段是base64编码后的,在本地要操作需要先解码,操作完之后再编码,再发回服务器。这个编码解码可以用js的函数atob()、btoa():

decodedMsg = atob(raw.replace(/-/g, '+').replace(/_/g, '/' )); //raw是指服务器返回的raw格式的message全文(是一个字符串)

//上面这句是先完成把'-'和'_'替换成'+'和'/'(即把base64urlsafe变成base64),再进行base64解码
//
之所以有这样的替换,是因为'+'和'/'出现在URI中是有含义的

encodedMsg 
= btoa(newdraft).replace(/\//g, '_').replace(/\+/g, '-' ); //newdraft是一个字符串,即待编码的内容

//上面这句和第一句差不多,先base64编码,然后完成替换,变成base64urlsafe

当然,这种思路虽然可以实现想要的功能,但并不好。我认为在这种思路的基础上还可以有一种优化:

在获取附件时,不通过 messages.get 方法获取邮件全文,而是获取每个附件的AttachmentId字段,用相应的字段结合 messageId 一起,通过 messages.attachments.get 方法获取附件内容。这样获取到的是MIME的一些注释和base64编码的附件内容。

进一步还可以考虑在本地 cache 一波,就是把用过的附件内容(包括相应MIME 字段)用 chrome.storage 接口来保存在本地。这里需要注意,如果这样,就需要在manifest中声明一个unlimited storage权限,否则会受到5MB的存储上限限制。

或者再换一种思路,直接上传附件。但这种思路要求先把附件文件下载,然后用 drafts.update 方法来完成附件上传。

总之几种思路都是一个核心想法:把附件先下载下来,然后再上传到草稿中,只不过咱的程序是把这套工作自动完成从而解放了用户。为啥非得这样整呢?下载再上传,多麻烦啊?没辙。因为邮件本身的格式就这么限制着了,附件都是作为MIME part 写在邮件文中的,而并不是像我最初想象的那样——在邮件中留一个指向附件的URI,附件和邮件正文分开。

 

三、gmail API 的使用限制

很关键!

点链接进去看官方文档,具体细节都说得很清楚。我这里大致说几点:

1. 限制分两种:总数限制速率限制

前者是针对 你的整个程序 而言的,即不同的gmail用户共享着你的程序拥有的配额。你的程序要使用gmail API是需要在google注册的(这个过程没啥复杂的,照着官方文档一点点弄就行),然后你的程序就相当于有一个自己的账号,这个所谓的“总数限制”就是针对你这个程序的“账号”而言的。具体是:免费用户(就是你了,开发者)一天可以发 10亿个单位 的请求。注意,是你的程序每天有这么多配额。具体到GmailAssist而言吧,不同的gmail用户使用它的时候,是都在消耗我的开发者账号的这个 10亿单位/天 的配额总数的。每天下来你的配额都会刷新。那么怎么查看自己的程序这一天消耗了多少配额呢?

进入Google Developer Console,选择你要查看的项目,点下一步。然后就可以在右边看到配额啊,用量啊之类的信息了。

后者是针对 具体的gmail用户 而言的,免费项目支持的最大请求速率限制为 250单位/秒/用户。但这个限制并不是绝对的,google允许短暂的burst,即你的程序可以暂时突破这一限制,但要是你持续突破它,你就会收到 HTTP 429 或者 403 或者 502 等错误,表明服务器受不了了,你太猛了。(这时候咋办?采用指数退避算法尝试重发请求。具体的我会在下一篇博文中说。)

2. 描述限制或描述使用情况的单位是啥?

我在上面都说的是多少多少单位。即官方文档中所谓的 quota unit,它是衡量和描述你的API使用量的最小单位,每次调用API中的不同方法,会消耗不同数量的 quota unit,从5到100不等,具体可以参考官方文档。

3. 除 gmail API 本身之外的限制

上面说的都是gmail API本身的限制,但用户在用你的程序时,还受到 gmail 本身使用的速率限制等,不过你的程序开发中不需要考虑这一点,你也几乎没法针对这个限制做什么。

4. Batch request 不是一个突破限制的trick

gmail API 鼓励开发者使用batch request来提高程序的性能。顾名思义,即把多个请求合并为一个请求。但这种方式下,这个合成的请求里的每个单独的请求所消耗的配额,仍然是单独计算的。

你可能感兴趣的:(Chrome扩展开发之四——核心功能的实现思路)