iOS 真机 JS 注入 · 第 4 课

原理通了,工具却"不肯配合":三个实操坑

第 3 课证明了 js-shell --userspace 原理可行。但我真去跑,被结结实实绊了三跤: 参数把页面导去 404、命令拒绝在管道里跑、多网页时冒出一个方向键菜单。 这三个坑看着杂,其实是同一个道理:这工具是给"坐在终端前、会用眼睛看会用手按键的人"设计的; 你想让它自动化、脚本化,就得反过来把自己伪装成那个人

坑一:--url 是"导航",不是"选页" —— 我把目标页搞成了 404

我想调的是妙记尾窗那个网页。看到有个 --url 参数,想当然以为是"按 URL 帮我挑中那一页"。结果——

现象

它不是"选页",是"把当前页导航到这个 URL"。我一传, 目标网页当场被导去了一个不存在的地址,整页 404。(当时对面同学直接看到"404了"。)

这坑的杀伤力在于它改坏了现场——你要调试的页面被你自己弄没了。教训有两层:

坑二:它拒绝在管道里跑 —— 因为它要一个"真终端"

自动化第一反应是把命令和输入用管道喂进去(echo ... | js-shell)。不行:

现象 & 原因

报错 Inappropriate ioctl for device。因为 js-shell 是个交互式壳, 它要操作一个 TTY 终端设备(去设置回显、行编辑这些"终端才有"的东西)。 管道不是终端,它一上来就问终端要参数,要不到就崩。
雪上加霜:macOS 没有 timeout 命令,想给它套个超时也没趁手工具;盲发 script -q 又会吊死。

解法:用 pty 给它"造"一个假终端

pty 伪终端 pseudo-terminal 就是"用软件伪造的一对终端"。我写了个 Python 驱动:用 pty.openpty() 造一对伪终端, 把 js-shell 的输入输出接上去——在它眼里,对面就是个真人在用终端。然后我在代码里"替人"读输出、发命令。

import pty, subprocess, os
master, slave = pty.openpty()          # 造一对伪终端
p = subprocess.Popen(['pymobiledevice3','webinspector','js-shell','--userspace'],
                     stdin=slave, stdout=slave, stderr=slave)  # 让它以为在真终端里
os.read(master, 8192)                  # 我这头"替人"看屏幕
os.write(master, (expr + '\n').encode())  # 我这头"替人"敲命令

完整脚本(含等待/重试/选页逻辑)是 ios-jsshell-drive2.py,全文在飞书调试脚本文档,团队可直接复用。

坑三:多网页时冒出方向键菜单 —— 单页/多页两种形态都得处理

伪终端接通后,又发现 js-shell 的启动有两种形态,脚本必须都认:

多个可调试网页 → 弹方向键菜单 出现一个带 光标的列表让你选。脚本要"替人"发下箭头(终端里就是 \x1b[B 这串字符),一行行移,直到高亮行的 URL 命中目标,再发回车。
只有一个网页 → 没有菜单,直接进 REPL 这时没有列表,直接就能敲 JS。脚本若还傻等菜单,就会误判"没找到"。两种形态都要写分支。
还有个稳定性坑

js-shell 启动时快时慢webinspectord 被多方争用),有时干脆起不来。 对策:脚本内置 ~50 秒的滚动等待,识别到"菜单出现 / 进了 REPL / 终端警告"任一信号就往下走;起不来(NO_CHOOSER)就重跑。真机调试要默认"会 flaky",把重试写进流程。

三个坑的共同真相

这三个坑都不是"原理错",而是工具的人机接口假设了一个人类操作者: 它以为有人会看 URL 别导错、有人坐在真终端前、有人会用眼睛看菜单用手按方向键。自动化的本质,就是把这些"人类假设"一条条用代码补上——pty 补上"终端",箭头字符补上"按键",备份 URL 补上"人不会手滑"。原理是地图,这些是路况。

接下来

能执行 JS 了,最后一课回到目的本身第 5 课——我们要探的 common.getSettings 是个异步桥调用(发出去要等 native 回话)。可 js-shell 每次执行是独立会话,没法在一次里等一个可能永远不回来的回调。 我怎么用"跨会话读全局变量"的两步走破这个局,并最终拿到"妙记 iOS 容器根本不回话"这个决定性的铁证。

📖 参考来源pymobiledevice3 CLI Recipes ——opened-tabs / js-shell / --bundle-id 过滤等命令的官方用法,写脚本时对照。


第 4 课 / 共 6 课 · 课程:iOS 真机 JS 注入