使用 react-native-quick-crypto 的项目遇到 “global.base64ToArrayBuffer is not a function” 问题

我们有个项目里面用 react-native-quick-crypto 代替 crypto-js 来进行 AES 加解密以及 pbkdf2 运算。最开始是仅仅做 pbkdf2 运算,用于将客户密码生成一个 encryption key,以此来加密读写数据库。运行良好没有报错,效率提升显著,原来的 crypto-js 需要二三十秒的时间,现在一两秒就完成了。

后来又添加了进行 AES 加解密的功能,结果涉及 base64转码就报错了。报错信息如下:

TypeError: global.base64ToArrayBuffer is not a function (it is undefined)

当时在用 AI 编程,gemini 帮我在 index.js 中引入各种 polyfill 均没有解决。后来自己研究了一下,想明白了,这个 base64ToArrayBuffer 函数在我们代码中并没有直接使用,是 react-native-quick-crypto 间接使用的,而其依赖的该函数应该是在 react-native-quick-base64 包中。

查找 package-lock.json,却发现 react-native-quick-base64 已经安装了。正在无计可施之际,进一步在网上搜索答案,找到了这个 issue,里面有人提到他通过安装 react-native-quick-base64 解决。于是抱着试试看的态度,运行:

npm install react-native-quick-base64

问题果然得到解决。由于不是很了解背后机制,又质问了一下 gemini,这回这货好像反应过来了,给了我如下的解释:

这是一个非常好的问题,也是 React Native 和 Node.js 生态系统中一个常见的、令人困惑的现象。

  您观察得非常准确:react-native-quick-base64 确实是 react-native-quick-crypto 的一个传递性依赖 (transitive dependency),这意味着当您安装 react-native-quick-crypto 时,npm 或 yarn 也会自动安装 react-native-quick-base64 到 node_modules 文件夹中,并且 package-lock.json 会记录下来。

  为什么直接安装它就解决了问题?

  这通常是由于以下一个或多个原因造成的:

   1. 模块解析顺序和全局 Polyfill 的时机:
       * react-native-quick-base64 库很可能在被导入时,会执行一些全局的 polyfill 操作(例如,它可能提供了 global.atob 或 global.base64ToArrayBuffer)。
       * 当它只是一个传递性依赖时,打包工具(如 Metro)在解析模块时,可能不会保证 react-native-quick-base64 的初始化代码在 react-native-quick-crypto 尝试使用它之前运行。
       * 当您将其作为直接依赖安装时,它在 node_modules 中的位置(通常是顶层)以及打包工具的处理方式可能会发生变化,从而确保它的 polyfill 代码在更早、更可靠的时机被执行,使得 global.base64ToArrayBuffer 在 react-native-quick-crypto 需要它时已经可用。

   2. 原生模块的链接问题 (如果它有原生部分):
       * 虽然 react-native-quick-base64 听起来像是一个纯 JavaScript 库,但如果它内部包含任何原生代码(即使是很小的部分),那么当它只是一个传递性依赖时,原生构建系统(如 iOS 的 CocoaPods 或 Android 的 Gradle)可能不会正确地发现并链接它。
       * 当您将其添加到 package.json 作为直接依赖时,它会被明确地包含在原生构建过程中,确保所有必要的原生组件都被正确链接。

   3. 打包工具的优化行为:
       * 打包工具(如 Metro)会进行各种优化,例如“hoisting”(提升)依赖项。有时,这些优化可能会导致某些模块的初始化顺序或作用域出现意想不到的问题。直接声明为依赖可以避免这些不确定性。

  总结

  这并不是 react-native-quick-crypto 官方文档的疏漏,而更像是JavaScript 模块系统、打包工具和原生模块链接之间复杂交互的一个副作用。

  最佳实践:
  既然您已经发现直接安装 react-native-quick-base64 解决了问题,那么请务必将其保留在 `package.json` 中作为直接依赖。这能确保您的项目在未来构建时,这个关键的 polyfill 能够稳定可靠地被加载。

补充版本信息:

react-native 0.79.6
react-native-quick-crypto 0.7.17

MacOS 下 Gemini Cli 无法通过浏览器授权的解决办法

最近在用 Claude Code 的同时,也在用 Gemini Cli 做一些事。今天换回刚返修回来的 MacBook Pro,在机器里面安装了 Gemini Cli 之后,发现使用 Chrome 浏览器登录会卡在点击完同意授权按钮之后,页面没有刷新,而命令行过一会儿后则报错,报错如下:

Failed to login. Message: request to https://oauth2.googleapis.com/token failed, reason: connect ETIMEDOUT xx.xxx.xxx.xx:443

一开始以为是缓存等问题,清理删除了 ~/.gemini目录,重新安装了 Gemini Cli,但还是不行。后来想到也许是网络问题,因为最近是在国内,所以是在必要的时候用了科学上网的,也许配置不到位。后来一番查找资料,果然通过网络设置解决了。

我是用 Clash,配了一个自己搭的 https 转发服务。平时只是用系统代理,没有带开 TUN 模式。当打开 TUN 模式之后,授权就可以了。

Angular 项目升级后,ng-bootstrap 的pagination 组件显示多余的 “(current)”

最近有个项目从 Angular 11 升级到了 Angular 13 (升级过程可以参考这篇文章),其他部分都显示良好,不过在很多数据列表页面出现了页码显示的问题,翻页器 pagination 的当前页码后面跟着个扎眼的 “(current)”,如下图:

通过查看代码,对比 bootstrap 和 ng-bootstrap 源代码,发现原来是版本对应兼容的问题。

项目升级后, bootstrap 版本升为了 4.6.2,而 ng-bootstrap 升为了 12.0.0。而根据 ng-bootstrap 官方文档,版本的对应关系应该是如下:

因此通过将 ng-bootstrap 降级到 11.0.1 问题得到解决。

这个问题的根本原因,在于 bootstrap 新版本中去除了一段代码,见这个 issue:

https://github.com/ng-bootstrap/ng-bootstrap/issues/3870

新版本 ngb 也跟着去掉了包含 (current) 的这段代码,但若依赖项是老版本 bootstrap,里面还有这段代码,那就显示出来了。

用 JHipster 生成的应用的升级小技巧

JHipster的基本介绍就不说了,官方网址是: https://www.jhipster.tech

关于用它生产的应用程序如何进行升级,看这里:https://www.jhipster.tech/upgrading-an-application/

由于它是一个代码生成器,因此每次升级会覆盖原有代码,如果对生产的代码修改得多,那么升级要解决的冲突就多。

因此,第一个要点,就是尽量把自己新增加的业务逻辑写在另外的文件中,尽量少修改生成的那部分代码。所采用的技巧,是 side by side 的方式,具体可以参考:https://www.jhipster.tech/tips/035_tip_combine_generation_and_custom_code.html#pattern-3—side-by-side

第二个要点,具体升级的时候,如果你经常修改 entity 定义(譬如因为字段删改、重命名等导致 .jhipster文件夹下的 json 文件修改)等文件,那么建议升级之前先删除 jhipster_upgrade 分支。换句话说,每次升级都是以当前版本的 entity 定义为基础,如此可以避免很多不必要的 entity 文件相关的冲突。具体解释理由:如果以比较早以前就生成了的 jhipster_upgrade 分支为基础来升级,而这个分支里面留存的 entity 定义又是很早的版本,那么就会生成不一样的 entity 文件,导致需要进行冲突解决。

在 macOS 上运行 bitcoin client 项目

项目位于 https://github.com/bitcoin/bitcoin 。本文针对 c++ 经验较少的读者。

第一步要点是搭建 c++ 开发环境, macOS 默认的安装是不全的,基本上,常见需要添加的是 automake 和 boost,安装方式: brew install automake boost libevent

然后运行如下:

  1. 运行 ./autogen,这会生成下一步运行 configure 所必需的脚本和配置文件,运行完毕后文件夹下会多出一些文件,如 configure,build-aux 等
  2. 运行 ./configure,这一步会需要用到 c++ 的 boost , libevent 库等,这也是为什么一开始需要安装它们的原因
  3. 运行 make
  4. 运行 make install

如何将 Angular 应用部署在虚拟目录/子目录

假设我们开发了一个 Angular 应用,我们希望将它部署在 https://www.iconben.com/app1,我们该怎么做呢?

这涉及两方面的配置,一方面是 Nginx 配置文件中,配置 server 下面的子目录;另一方面,因为 Angular 应用中的所有请求都会经过 url 拼装,需要让 Angular 知道自己处于子目录的环境下。

前者通过 nginx 配置文件内 server 中配置 location 可以实现(有 root 和 alias 两种用法,若子目录与angular 应用的文件夹不同名,则用 alias 配置):

server {
        server_name   www.iconben.com;
        listen 80;

        location /app1 {
            alias  /srv/www/angular-app1;
            index  index.html  index.htm login.html;
            client_max_body_size  1024m;
        }

        location / {
            root  /srv/www/www.iconben.com;
            try_files $uri $uri/ /index.html;
            index  index.html  index.htm login.html;
            client_max_body_size  1024m;
        }
}

后者通过修改 Angular 应用的 base href 可以实现。即修改index.html 中的 base 标签为 <base href=”app1″>(默认为<base href=””>,表示配置在站点根目录)。

考虑到开发环境多数并非子目录环境,以上这一步要么在打包的时候才实现(如果是 CI/CD 的环境,配置好相关脚本就好);要么则是不修改打包结果,而在 nginx 中用 sub_filter 配置 index.html 被请求的时候动态替换base 标签的 href 值。

以下为完整配置:

server {
        server_name   www.iconben.com;
        listen 80;

        location /app1 {
            alias  /srv/www/angular-app1;
            sub_filter '<base href="/">' '<base href="/app1/">';
            sub_filter_once on;
            try_files $uri $uri/ /app1/index.html;
            index  index.html  index.htm login.html;
            client_max_body_size  1024m;
        }

        location / {
            root  /srv/www/www.iconben.com;
            try_files $uri $uri/ /index.html;
            index  index.html  index.htm login.html;
            client_max_body_size  1024m;
        }
}

网上有很些相关文章,但很多没讲到点子上。

一般官方文档是很好的参考来源。以上配置涉及 angular 部分参考这里

使用容器化的 php-fpm 的文件读写权限问题

手头有两个站点,在服务器上通过 nginx 对外服务,nginx 转发 php 请求给基于 php-fpm 容器。在如下架构下部署了 wordpress 站点。站点能运行,但涉及写文件的时候(譬如上传图片)则会报错。

一开始以为是 nginx 权限问题,查了 nginx 运行在用户 nginx 之下,因此尝试对网站文件夹给予 nginx 用户读写权限,但没解决问题。

后来知道了是 php-fpm 容器使用了 www-data 用户,因此对网站文件给予 www-data 用户读写权限就行了,譬如运行: chown -R www-data:www-data ./wordpress-website/

此操作有一个注意事项,即是多半在 host 系统中没有 www-data 用户,只在 php-fpm 容器中有,因此需要先进入 php-fpm 容器命令行,然后做以上操作。

使用 certbot 生成Let’s Encrypt通配符SSL证书

当我们有多个二级域名都需要启用 SSL 证书的时候,如果一个一个的进行证书的设置与管理是比较费时间精力的事情,还好有通配符证书。通配符证书能支持譬如 *.iconben.com 这样的多个二级域名,十分方便。

Let’s Encrypt 从2018-03-13起开始支持通配符证书。因此这给了我们免费实现系列站二级域名共用同一个证书的机会。因我们的域名解析托管在 DNSPod,以下简要说明如何使用客户端 certbot 工具,配合 certbot-dns-dnspod 插件,对于托管在 DNSPod 的域名生成通配符证书。

生成证书

安装 Certbot,选择相应的 Web Server 和操作系统,按照说明安装 Certbot。

然后通过 DNS 验证的方式生成证书:可以安装 Certbot’s DNS plugins。手动和自动都可以生成 SSL 证书,因通配符的证书有效期也是3个月,使用插件自动生成的话续期十分方便。

因本站域名解析使用的是DNSPod,可以使用 certbot-dns-dnspod,使用 pip 安装,如 pip3 install certbot-dns-dnspod。

针对那些没有在列表中DNS服务商,可以去自行搜索相关插件。

安装完成后,还需要登录到 DNSPod控制台 生成API Token,并保存在配置文件/path/credential.ini中。注意 DNSPod 已经启用了 V3 版 API,但我们这里使用的是 V2 版。配置文件中需要有 email 参数,必须是 DNSPod 账号的登录邮箱,api_token 参数,必须是双引号括起来的 “ID,Token”的形式,例子如下:

dns_dnspod_email = someone@example.com
dns_dnspod_api_token = "12345,c54cd52eae137f765af143d1dfader3434"

接下来运行命令行:

sudo certbot certonly -a dns-dnspod --dns-dnspod-credentials /path/credentials.ini -d example.com -d "*.example.com" 

# -a:指定使用的插件
# --dns-dnspod-credentials :指定 dnspod 使用的 含 email 和 api_token 的配置文件
# -d:指定要生成证书的域名

其他DNS plugins的使用也是类似的。

运行命令后,根据向导输入邮箱,同意条款,选择是否与 EFF 共享个人邮箱信息,然后 DNS plugin 创建 DNS TXT 记录,进行域名验证,然后生成 SSL 证书,然后删除 DNS TXT记录。最后提示证书生成成功!

CORS 的 preflight 机制

昨天帮某客户配置 wordpress 的 SSL,本以为是个很简单的活儿,结果遭遇问题,弄了很久才解决。在此过程中对跨域访问的预检机制有了较多的了解,在此记录。

需求:
之所以要将该 wordpress 网站升级为整站 https,除了网站本身的安全性和对外形象之外,最主要是该网站的文章是另外一个 web 应用的数据来源。具体来说,是客户另外那个 web 应用通过 rest api 获取该网站的文章列表并显示。而 web 应用已经率先整站 https,若其通过基于未加密的 http 协议来访问该 wordpress 网站的 rest api,浏览器中会有安全提醒。

第一步:
客户 wordpress 网站基于 centos7,apache2.4.6,php7.0 等。帮客户升级至 php7.4,安装 certbot 配置 let‘s encrypt 证书并配置自动续期,配置安全规则允许 443 端口访问。https 成功可以访问,初步成功。

问题1:
虽然网站在浏览器中可以使用 https 进行访问,但客户的 web 应用上对该网站的 rest api 请求却失败。仔细查看浏览器控制台输出,发现报的错误是:Cross origin resource sharing error: MissingAllowOriginHeader。含义是在 rest api 的 HTTP 响应头中,没有包含 Access-Control-Allow-Origin 的 header。

解决1:
WordPress 虽然是全球用量很大的 CMS 系统,但其文档及社区却不敢恭维,主要是因为其版本众多,各种网络文章针对不同版本不容易分辨,且 PHP 这门世界上最好的语言实在太容易各种 hack,所以对于一个问题的解决方案也是八仙过海,各显神通,良莠不齐。了解到 wordpress 默认不允许跨域访问。经过试验各种配置(包括调整 apache 的默认配置和 VirtualHost 配置,调整 wordpress 的.htaccess 文件内的配置),最后使用的方案是在 wordpress 的 wp-include 文件夹下修改 function.php 实现跨域访问的域名白名单。

问题2:
前一步成功以后,查看浏览器中该 api 访问的预检仍然报错:Cross origin resource sharing error: HeaderDisallowedbyPreflightresponse。这是提示请求头中有不允许的 header,通过排查,发现是请求方应用中有某几个自定义的请求头,通过在服务器端设置 Access-Control-Allow-Headers 而成功。

在 ARM 架构的虚拟机上安装 ImageMagick 的 JPEG 和 PNG 支持

手上在用一台 ARM 架构的虚拟机,在一个项目中需要用到 ImageMagick 库提供图片文件的裁剪服务。因官方软件源不包含 ImageMagick,于是手动下载源代码并编译安装。但发现默认不支持 JPEG 和 PNG 格式的文件,因此需要手动安装这两个格式的支持。

关于安装方法,网上找到一些文章,包括官方文档似乎也语焉不详(官方文档不算友好,起码从搜索结果来看是如此)。经过一番探索才搞定。

其实不需要像网上有些地方所说的要从源代码安装之类的。只需要:
1. 安装 openjpeg2 和 libpng15 库:
sudo yum install openjpeg2
sudo yum install libpng15

2. 重新安装 ImageMagick:
./configure –disable-shared –with-openjp2=yes –with-png=yes
make
sudo make install