分类
技术

教程:搭建 SonarQube 服务并测试自己的项目

之前分享过 SonarQube 的使用体验,今天写篇教程介绍下如何部署自己的服务器实例,及测试自己的项目。

官方文档写得很详细,推荐大家阅读学习:Install the Server。这篇博文我主要分享实操的经验教训。

0. 安装 Docker

2021年即将过去,建议大家,部署各种服务能用 Docker 最好都用 Docker。省时省力,集中精力写业务代码。

Windows、macOS 用户推荐使用 Docker hub,Linux 用户可以命令行安装 Docker Engine——你都用 Linux 了,应该不需要我详细解释,参考 官方文档 即可。

1. 部署 SonarQube

1.1 保存配置文件

有了 docker 之后,部署就很简单。先找个地方,将下面的内容保存成配置文件 sonarqube.yml

version: "3"

services:
  sonarqube:
    image: sonarqube:community
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "9000:9000"
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:

注意第5行,“sonarqube:community”,是镜像的注册名称,默认是最新版本。如果你想使用长期支持(LTS)版,可以将其改成 sonarqube:lts-community。一般来说,LTS 版本新功能会落后于最新版本,但是会更稳定,安全系数也更高。目前这个时间点,如果你想部署中文版本,必须选择 LTS,也就是 8.9.x 版本,因为中文翻译插件最高只支持 9.1,在最新版 9.2 下会报错。

1.2 拉取镜像:

docker-compose -f sonarqube.yml pull

1.3 启动容器:

docker-compose -f sonarqube.yml up

第一次启动建议只用 up,方便看日志,判断状态。我的经验,不管是 Windows + WSL,还是云上小水管,都会报告:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 错误。这是虚拟机内存配置不够导致的,所以调高。

Linux

  1. 永久修改配置:编辑 /etc/sysctl,增加 vm.max_map_count = 262144
  2. 修改当前环境:执行 sysctl -w vm.max_map_count=262144
  3. 重启 docker 服务:systemctl restart docker

WSL

先进入 docker 环境:

wsl -d docker-desktop

然后

sysctl -w vm.max_map_count=262144

1.4 启动为服务

第一次启动会自动创建 Volumes(相当于磁盘),安装软件、部署各种功能、执行初始化操作等,会比较久。最后停下来时,如果 DB 服务和 SonarQube 服务都成功启动,就算完成。

默认服务端口是 9000,启动浏览器访问 localhost:9000,此时应该可以看到下图所示的登录界面。

默认用户名密码是 admin/admin,登录后会被要求修改。如果要对公网提供服务,建议尽量改得复杂一些。

如果刚才启动 SonarQube 的时候只用了 up,建议先停掉,改为 up -d 再次启动为后台服务。

2.0 配置 nginx 反向代理

Docker 启动的服务只能内部访问,所以我们通常需要把它开放到外面,这里还是用 nginx 做反向代理:

server {
    server_name sonarqube.meathill.com;
    root /var/www/sonarqube;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:9000;

    }
}

3.0 安装中文包

SonarQube 所有语言包都是以插件形式存在的,所以要汉化 SonarQube 只需要安装中文插件即可。

登录 SonarQube 后,进入 Administration > Marketplace,搜索 Chinese,安装简体中文包即可。安装之后需要重启,请耐心等待界面变化,按照提示一步一步走。

SonarQube 会根据浏览器语言配置自动选择语言,没有留出显式选项。

4.0 分析 FastTest

全部配置完成之后,可以开始第一次分析了。

首先我们新增项目(Add Project),选择手动(Manually)即可。当然,如果你是团队内部使用,也可以配置为以 GitHub、GitLab 为代码仓库。

输入 token 名称,创建一个 token,将 token 复制下来。

接下来你可以选择使用打包好的二进制文件,那就点击“继续”(Continue)然后按照提示下载二进制文件,再输入命令执行即可。我比较喜欢用 Docker,所以推荐用下面的命令,会自动拉取镜像并且执行:

docker run \
    --rm \
    -e SONAR_HOST_URL="http://${SONARQUBE_URL}" \
    -e SONAR_LOGIN="${myAuthenticationToken}" \
    -v "/path/to/repo:/usr/src" \
    sonarsource/sonar-scanner-cli

上面的参数中:

  • SONAR_HOST_URL 即部署后提供服务的地址
  • SONAR_LOGIN 即上一步中复制的 token
  • /path/to/repo 需要是绝对路径,如果是 Windows 系统,正常使用 \ 即可,比如 C:\works\fasttest,不需要转义
  • 如果你的 SonarQube 跑在 docker 里,又使用 docker 启动分析,那么需要增加 --network=host 参数,不然两个 container 之间无法互相访问

最终得到的分析结果大约是这样:

报告内容我这次就不详细解析了,回头慢慢分享。

总结

好,SonarQube 的部署和分析过程就分享完了,建议大家都学习使用一下,能学到很多安全性、可维护性的知识,能很好的提升团队的代码质量。如果绑定到 GitHub、GitLab 等代码仓库,还可以自动分析 PR、MR,成为工作流程的一部分。

感兴趣又不想自己动手(或没条件)的同学可以联系我,我可以在我的服务器上给你开通账号试用。

注意:不要上传公司的业务代码!

注意:不要上传公司的业务代码!

注意:不要上传公司的业务代码!

分类
chrome

Chrome Extension Manifest V3 升级笔记

关于 MV3 的知识,可以看这篇前文:

《Chrome 扩展大升级 Manifest V3:变化》

近期尝试把一个浏览器扩展升级到 MV3,然后发现了很多问题,写篇博客记录一下。

与文档不符

  • 很多号称返回 Promise 实例的方法实际上不会返回结果,或者在没有 callback 的时候会报错

文档未说明

  • service worker 里无法使用 chrome.i18n.getMessage 或者 chrome.i18n.getUILanguage。于是目前右键菜单都无法 i18n 了。
  • chrome.contextMenus.create 无法向 browser_action 添加菜单项

未实现 Promise 或者有 bug

  • chrome.storage.local.get(keys) Promise 结果固定返回 undefined,不可用,需用回 callback
  • chrome.permissions.request 未 Promise 化,无返回值
  • 必须传入 callback,不然报错
    • chrome.permissions.contains
    • chrome.contextMenus.remove

文档写了,但我没看见(>_<!)

  • 在 service worker 里使用 chrome.contextMenus.create 时,必须传入 id,且不能使用 onclick 函数。只能侦听 chrome.contextMenus.onClicked.addListener,然后做进一步判断。

MV2 存在的功能,但无法迁移至 MV3

(有些可能是暂时的)

因:不允许使用 remote JS

MV3 不支持使用外部 JS,所以 Google Analytics 和 Facebook 分享按钮之类的 SDK 都无法使用。也即是说,现在扩展不能使用 GA,也不能嵌入 Facebook Like,也不能使用其它需要引用外部 SDK 才能实现的功能。

因:Service worker 取代 background page/JS

无法获取系统 light/dark mode

Service worker 没有 window 对象,也就没有那些 window 对象上才有的方法,比如 matchMedia()。所以我们就无法在 service worker 里检查系统的 dark mode,实现动态修改 icon 的功能。

无法使用 Image 解析图像

Service worker 也没有 Image 对象,只能使用 createImageBitmap 获取 ImageBitmap,无法转换成 ImageData,也就无法使用一些依赖 ImageData 的仓库,比如没法解析二维码。(可以通过手动解析二进制的方式绕过)

因为 createImageBitmap 无法解析 SVG blob,所以原则上,所有 SVG 的操作均无法执行。

只能通过 chrome.tabs.create() 的方式传出内容

自然,alert()prompt() 都无法使用,也无法手动弹出 popup。所以如果我们想从 service worker 里向外传出内容,只有两个选择:

  1. 使用通知 notification API
  2. 直接打开页面

而 notification API 需要权限,所以可能只好选后者。

分类
前端

使用 SVG 制作扇形

有时候我们需要制作扇形,比如图形化数据生成饼图的时候。使用 HTML + CSS 做不到,必须借助 SVG 帮助。经过一些摸索,大概方式如下:

0. 创建 SVG

我们需要一个 SVG,然后在里面画一个园:

<svg xmlns="http://www.w3.org/2000/svg" height="600" width="600" viewBox="0 0 20 20">
  <circle r="5" cx="10" cy="10" />
</svg>

这里,我创建了一个 SVG,并且以 10,10 位圆心,画了一个半径为 5 的圆。SVG 的视窗只需要显示这个圆,所以是 0 0 20 20 的正方形。widthheight 用来定义网页中 SVG 的尺寸,SVG 是矢量图形,可以实现内容的无损缩放,所以即使显示尺寸比图形尺寸大很多,也不用担心出现锯齿。

1. 用边框画圆形

接下来,我们用给圆加边框的方式来做圆形。

<circle
  r="5"
  cx="10"
  cy="10"
  fill="transparent"
  stroke="tomato"
  stroke-linecap="butt"
  stroke-width="10"
></circle>

首先,我们用 fill="transparent" 清理掉圆形内部的颜色,然后用 stroke="tomato" 给边框加上橙色。接下来,我们通过 stroke-width="10" 设置边框宽度为 10,这也是矩形半径。

此时,屏幕上会出现一个橙色的正圆。

2. 画扇形

画扇形的方式有很多,比如画两条半径然后画弧形再填充颜色。但是利用边框画扇形最简单。

用边框画扇形说白了,其实是结合圆环和虚线,需要有扇形的地方,就填充颜色;不需要扇形的地方,就用虚线的空白。这里要用到 stroke-dasharray 属性,它的规则很简单,奇数为实偶数为虚,所以我们只要计算扇形所需的弧形长度,然后剩下的填充周长即可。

在我们这里,就是 stroke-dasharray=”calc(10 * 3.1415926 * 1/6) 31.415926",即取绘制一个 1/6 大小的扇形。

3. 修改位置

修改位置需要使用 stroke-dashoffset 属性,它会把图形从原来的位置移动若干距离,正的就往起点移动,负的就往终点移动。

在我们这里,就是 stroke-dashoffset="calc(-10 * 3.1415926 * 1/6)",将第二个扇形移到第一个扇形的旁边。

4. 其它+已知问题+扩展阅读

最终效果:https://codepen.io/meathill/pen/yLMQqBQ?editors=1000

这些属性,也可以使用 CSS 样式替换,效果一样。

Safari 问题比较多,首先半圆就不是半圆,其次偏移也不对,不知道是否只支持 CSS。

分类
css

正确使用 height: 100% 和 flex: 1

HTML+CSS 实现页面布局的时候,盒模型是很重要的概念。早期没有明确布局概念的时候,HTML 元素主要分两大类:行(inline)元素与块(block)元素。默认情况下,块元素会占据父元素的一整行矩形区域,宽度是 100%,高度由内容决定。如果我们希望子元素跟父元素一样高,可以设置 height:100%

接下来我们有了弹性盒模型, display: flex。弹性盒模型是一种主动布局,即我们先决定怎么布局,浏览器则负责填充内容和渲染。所以直觉上,我以为:

  1. 父元素 height: 100%; display: flex; flex-direction: column
  2. 某个子元素 flex: 1
  3. 子元素的子元素 height: 100%,应该能自动填充剩下的高度
  4. 如果子子元素同时 overflow: auto,那么应该可以自动出滚动条。

结果不行。

问题一

如上图,我厂的 Showman 产品。它的高度自适应屏幕高度,顶部通栏、导航、动作按钮栏高度固定,编辑器和日志输出窗口填满剩下的空间。我希望用户可以调节编辑器和日志输出窗口的比例,以适应开发与调试不同的场景。于是保存日志输出窗口的高度,编辑器的高度自适应(flex:1)。

因为 Vue2 组件要求有唯一的根元素,且整个应用有多个不同的路由,所以编辑器和日志输出窗口只能作为子子元素存在,大概的结构是这样的:

<div id="app">
  <header id="main-nav">
  <!-- <router-view> -->
  <div id="main-body">
    <header id="second-nav">....</header>
    <div id="action-bar">....</div>
    <div class="editor-output">
      <div id="editor">....</div>
      <div class="drag-splitter"></div>
      <div id="output">....</div>
    </div>
  </div>
</div>

CSS 大概如此:

html, body, #app {
  height: 100%;
}
#app {
  display: flex;
  flex-direction: column;
}
#main-nav, #second-nav, #action-bar {
  height: 40px;
}
#main-body, .editor-output {
  flex: 1;
  display: flex;
  flex-direction: column;
}
#editor {
  flex: 1;
}
#output {
  height: 100px;
}

这样的结果是,执行时,大量日志输出,就把界面顶开了。而不是预期中那样,出现滚动条,多余的部分被隐藏。

经过一番 google,发现问题在高度计算。虽然定义了 height: 100%display: flex,但是浏览器在计算高度的时候,并不会从外往里一层一层算,而是按照规范:

百分比
指定一个百分比的高度。这个百分比是相对于父元素的盒子的高度计算的。如果父元素没有明确指定高度,并且该元素不是绝对定位的,该值将计算为 “自动”。

auto
由其它属性决定。

于是因为上图中的界面存在嵌套关系,所以在需要计算高度的时候,子元素的高度虽然应该是 100%,但是父元素并没有被明确指定,所以就变成了 auto,继而被子元素撑开。解决方案就是沿着你需要 height: 100% 的元素往上,添加明确的 height,可以是百分比,也可以是绝对数值。

于是,我在 #main-body 上添加 height: calc(100% - 49px),问题解决。

😓 等下,不是每级都要加么?为啥只加一个具体高度就可以了?这个问题,我还要再研究一下。目前猜测,因为这个元素是竖直方向排列的(flex-direction: column)。

问题二

后来,在编辑器和日志输出窗口的右侧,增加了资源缩略图侧边栏。于是又遇到第二个问题:我以为 #main-nav 的高度确定,那么作为 display:flex,默认 align-items: stretch,它的子元素的高度应该都等于它的高度。所以给子元素设置 overflow: auto 就应该可以限制高度,出现滚动条。结果又失败了。

然后我想起来 BFC。虽然直觉上 BFC 应该跟 display:flex 应该没什么关系,不过因为测试起来比较简单,可以先试试。

于是我就在 div.d-flex 上添加了 .overflow-hidden 样式,果然问题就解决了。因为没找到明确的文档解释,所以我只能猜测:

  1. 类似 BFC 的逻辑在 display:flex 元素上依然存在。
  2. 父元素 display:flex;flex:N,根据上下文它应该有个确定的高度
  3. 但如果子元素高度超过它的高度,默认会撑开
  4. 如果父元素 overflow: hidden,会触发某个 xFC,于是整体高度就被限制了
  5. 于是子元素的滚动条就出来了

总结

行文至此,其实两个问题我都没找到具体的文档或者规范,只能说是摸索着解决了,然后再自己猜测原理。希望日后能找到具体的解释和规范吧。(要不要去翻翻张鑫旭的《CSS 世界》呢,都送完了,还得再买……)


参考阅读:

分类
服务器端

nginx 笔记

基础配置

# daemon on;
# worker_processes 1;
error_log logs/travis.error.log error;
pid logs/travis.nginx.pid;

events {
    accept_mutex off;
}

http {

    server {
        listen 9000;

        include mime.types;

        location / {
            rewrite ^ /static/edge/index.html last;
        }

        location /admin-api/ {
            proxy_pass https://admin-dev.openresty.com.cn;
            proxy_set_header Host admin-dev.openresty.com.cn;
            proxy_ssl_name "admin-dev.openresty.com.cn";
            proxy_ssl_server_name on;
        }

        location /static/ {
            alias fe/dist/static/;
        }
    }
}

启动 nginx

nginx -p $PWD -c conf/travis.conf

其中,-p $PWD 指定当前目录为工作目录。-c 指定配置文件。

reload

找到配置中的 pid 文件,从里面找到 pid

kill -s HUP ${pid}
分类
技术

使用 Node.js 驱动 FFmpeg 在 Linux + vncserver 下完成视频录制

自动化录制屏幕有很多用途,比如生成教学视频、生成产品文档,等等。对比人工,自动化有很多好处:

  1. 避免创作者的设备和环境问题(比如邻居装修、麦克风不好等)
  2. 避免创作者的语言、发音问题(比如普通话不标准、不会说某种语言)
  3. 录制环境出现变化,可以方便的重录(比如换个背景图,界面有升级)
  4. 就像写博客一样,任何时候,拿出电脑或者手机都能编辑一段

所以目前研究这方面应用的很多,我厂也是。我近期就投入大量时间在这项工作上面,现在终于有所成果,写篇博客分享一下。

0. 准备环境

首先推荐大家使用 Linux。Linux 开源,有很多开源免费的工具可以完成各种操作,不仅可以录屏,还可以很容易地模拟各种用户操作,给我们留下大量开发空间。

建议选择有图形界面的 Linux 发行版,我尝试过 fedora 33 和 Debian 10 树莓派版,都很容易配置。如果使用纯命令行版本,然后自己完成安装图形界面,比如 gnome,再完成剩下来的配置,会很麻烦。

然后记得把系统更新到最新版,以规避可能遇到的问题。

1. 配置 vncserver

如果只能在主屏录制,这个产品的实用性就会大打折扣。所以我们选择用 vncserver 创建虚拟屏幕,然后在虚拟屏幕上完成录制。如果需要的话,也可以随时用 vnc viewer 之类的软件连上 VNC 实时查看效果,非常方便。

有些系统自带 vncserver,比如 Debian 10 树莓派,那就不用安装。我们选用 fedora 33,需要手动安装,这里推荐 TigerVNC,安装使用都很方便:

sudo dnf install tigervnc

安装完成后,使用:

vncserver :5 -geometry 1280x720

就可以创建虚拟显示器了。其中,:5 是显示器 id,可以顺延,比如 :6:7、甚至 :99,至于上限在哪里我暂时不知道。-geometry 1280x720 是设定显示器分辨率为 1280×720。

另外,还可以使用下面的命令查看和关闭显示器:

 vncserver -list
 vncserver -kill :5 

1.1 测试

配置完成之后,可以用 Firefox 测试一下效果。

# 安装 firefox
sudo dnf install firefox

# 在指定虚拟显示器打开 firefox
DISPLAY=:5 firefox https://cn.bing.com

# 截图,可能需要安装 xwd
xwd -root -display :5 > screen.xwd

# 转换成 png,可能需要安装 ImageMagick
convert screen.xwd screen.png

然后把图片下载到本地,或者启动一个 http 服务器就能看到了。

1.2 关闭桌面

默认的 VNC server 会启动桌面,此时可能会要求我们登录什么的。我们在这套系统当中并不需要桌面,只要有显示器即可,所以可以修改 ~/.vnc/xstartup 禁用:

#!/bin/sh
 

unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
# 把下面这行注释掉 
# exec /etc/X11/xinit/xinitr

2. 使用 FFmpeg 捕获屏幕内容

使用 FFmpeg 录屏比较简单,将输入源设置为指定显示器即可,formatx11grab,命令大体如下:

ffmpeg -y -f x11grab -video_size 1280x720 -framerate 30 -i :5.0+0,0 -c:v h264 a.mp4

其中,

  • -y 意思是自动覆盖前面生成的视频
  • :5.0+0,0 是使用刚才创建的 :5 显示器,用它的 0 号桌面,启动位置是 0,0 即左上角
  • -f x11grab 是使用 X server 抓取格式,Linux 下的图形界面一版是基于这个系统

注意,上面这条命令里参数的顺序很重要,否则可能遇到 Protocol not found 等错误。

3. 使用 node.js 驱动

最后只要用 node.js 的 child_process.spawn() 功能调用上面的命令即可。这段代码属于公司,我就不贴了,主要分享几点经验教训:

  1. 要用 spawn,因为录制过程中我们需要用输出来判断录制状态,exec 这种只能在结束时提供输出的没法用
  2. FFmpeg 会把输出输出到 stderr,理由不明,不过记得要用 stderr 来检查
  3. 录像完成,如果在命令行,按 q 或者 ctrl+C 都可以停止录像,并开始封装视频文件。在 node.js 里,我们可以调用 cp.kill('SIGINT') 。注意,调用之后,FFmpeg 子进程并没有立刻结束,它要把前面的录像进行封装,这个过程也是需要时间的,所以如果你接下来还要对视频文件进行操作,应该等待子进程彻底结束
  4. 判断录像开始的依据,我目前用的是:输出里包含 Output #0, mp4, to 'a.mp4'
  5. 判断录像结束,视频已经生成的依据,我用的是 cp.on('exit', onExit),然后在 onExit 里处理。注意,其它情况导致 ffpmeg 子进程退出时也会触发这个函数,所以我们必须检查 code。此时,在我的机器上,code 是 255,表示它是用户手动中止的,可以当作判断依据。

总结

剩下来的内容基本就是怎么驱动图形界面程序运行了。一般来说用 puppeteer 比较好,可以很容易的跟 node.js 联动,我厂的 showman 也是基于这个方案来实现的,最后贴一段视频,大家看下效果:

一段用上述技术生成的视频

参考阅读

分类
css

重发老视频:使用 CSS 制作工序流程图

整理之前录的视频,发现一个漏掉没有上传的:

这个视频里,我演示了如何使用纯 HTML + CSS 制作工序流程图。涉及到的技术包括:

  1. display:flex Flex 布局
  2. 使用 order: N 调整显示顺序,以实现响应式
  3. 使用 position:XX 调整定位

虽然项目不大,不过大部分布局相关的技术都有所涉及,很适合刚入门和初级同学学习。


今年想继续在直播、视频方面发力,希望大家支持。如果有什么想听的想看的想学的,也欢迎点菜。

分类
技术

欢迎欢迎

欢迎来到我的博客,我是 Meathill,想了解我可以点 关于我

近期作品

欢迎留言点播各种内容。

分类
chrome

Chrome 扩展里实现 SSO

周五打算给客户发版,结果在这里卡了大半天,写篇博客记录下。

0. SSO 的实现

SSO,Single sign-on,单点登录,即统一处理用户登录、提供用户身份凭据的功能。使用 SSO,可以只维护一套用户体系,容易开发维护;对用户来说,只需要登录一次就能使用该开发商的全部产品,也很轻松方便。

一般来说,SSO 的流程是:

  1. 用户使用 A产品,域名是 pa.mydomain.com,登录服务(S)位于 login.mydomain.com
  2. 用户使用提供服务的 A产品,A产品需要登录,用户选择登录
  3. 来到登录服务,完成登录
  4. S服务将用户指回 A产品,返回的 URL 里包含一个 token
  5. A产品拿到 token,请求 S服务,验证 token,获取部分用户信息(比如邮箱,一般只用来展示
  6. A产品生成自己所需的身份凭据,并以此验证用户身份

我厂的产品也是这么实现的。

1. Chrome 扩展遇到的问题

本地调试一切正常,但是加载成扩展之后,从登录服务跳回扩展会遇到 ERR_BLOCKED_BY_CLIENT 错误,URL 也被重定向到 chrome://invalid/。我在这里卡了很久,主要是不知道该怎么定位问题和搜索答案。

后来经过反复尝试,我终于发现,只有从登录页面跳转回去插件页面的时候,即 location.href='chrome-extension://{id}/ui/index.html 的时候,才会报错,所以立刻换用 chrome extension href ERR_BLOCKED_BY_CLIENT 作为关键词,立刻找到了答案:redirect to chrome-extension:// results in ERR_BLOCKED_BY_CLIENT

然后阅读文档:Manifest – Web Accessible Resources(可由 Web 访问的资源),得知需要在 manifest.json 里添加对应的配置:

{
  ...  "web_accessible_resources": [
    "ui/index.html"
  ],
  ...
}

添加后 SSO 就正常了。

2. 后记

不过我没想明白的是,这个配置意义何在?配置写在扩展里,防止 web 访问扩展里的文件,似乎并没有什么帮助,也没什么安全性的顾虑。也许是我还没遇到吧。

分类
chrome

让 Chrome API 支持 Promise

Chrome API 都是回调型,连续使用非常不方便,希望能改成 Promise 型。Chrome 本身不提供 promisify,不过可以自己写一个:

export default function promisify(original) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      original(...args, (...results) => {
        if (chrome.runtime.lastError) {
          reject(chrome.runtime.lastError.message);
        } else {
          resolve(...results);
        }
      });
    });
  }
}

这里有几个注意事项:

  1. 参数使用 ...args 进行拆解和合并,方便调用
  2. 需要检查 runtime.lastError,不然出错的时候浏览器会报错,影响体验

使用的时候,我比较喜欢只修改需要使用的函数,不打算 promisify 全部函数,大概是这样:

import promisify from './index';

/* global chrome */

export const update = promisify(chrome.tabs.update);
export const remove = promisify(chrome.tabs.remove);
export const get = promisify(chrome.tabs.get);
// 我觉得 `close tab` 看起来更合理
export const close = remove;
// 封装一个 `goto` 的快捷方式
export const goto = function (tabId, url) {
  return update(tabId, {url});
}
// 全部导入,好处是简单,坏处是不方便 tree-shaking
import * as ChromeTabs from '../chrome-promisify/chrome.tabs';

ChromeTabs.goto(tabId, 'https://blog.meathill.com');

// 需要哪个导入哪个
import {goto} from '../chrome-promisify/chrome.tabs';

goto(tabId, 'https://github.com/meathill');