Jesssullivan/scheduling-bridge
GitHub: Jesssullivan/scheduling-bridge
基于 Playwright 浏览器自动化的 HTTP 中间件服务器,通过封装 Acuity Scheduling 的预约向导 UI 提供标准 REST API,实现无需官方 API 即可批量迁移和程序化预约。
Stars: 0 | Forks: 0
# scheduling-bridge
与后端无关的调度适配器中心。目前通过 Playwright 浏览器自动化桥接 Acuity Scheduling,其架构设计旨在支持额外的调度后端。
## 架构
一个封装了 Playwright 向导流程的 HTTP 服务器,可自动化 Acuity 预订 UI。该桥接器使用 Effect TS 进行资源生命周期管理(浏览器/页面的获取与释放)。
```
HTTP Request
-> server/handler.ts (route matching, auth, JSON serialization)
-> acuity-service-catalog.ts (static env catalog -> BUSINESS -> scraper fallback)
-> steps/ (Effect TS programs for each wizard stage)
-> browser-service.ts (Playwright lifecycle via Effect Layer)
-> selectors.ts (CSS selector registry with fallback chains)
```
### 关键组件
- **server/handler.ts** -- 带有 Bearer token 认证的独立 Node.js HTTP 服务器
- **acuity-service-catalog.ts** -- 共享服务源顺序和静态配置缓存,BUSINESS 提取和抓取器回退
- **browser-service.ts** -- Effect TS Layers,用于常驻共享浏览器进程以及请求范围的页面会话
- **acuity-wizard.ts** -- 完整的 `SchedulingAdapter` 实现(本地 Playwright 或远程 HTTP 代理)
- **remote-adapter.ts** -- 用于代理到远程中间件实例的 HTTP 客户端适配器
- **selectors.ts** -- 所有 Acuity DOM 选择器的唯一事实来源
- **steps/** -- 单独的向导步骤程序以及 BUSINESS 提取辅助工具
- **acuity-scraper.ts** -- 已弃用的服务、日期和时间段读取回退方案
## 端点
| 方法 | 路径 | 描述 |
|--------|------|-------------|
| GET | `/health` | 健康检查(无需认证) |
| GET | `/services` | 通过 `SERVICES_JSON` -> BUSINESS -> 抓取器回退列出预约类型 |
| GET | `/services/:id` | 获取特定服务 |
| POST | `/availability/dates` | 服务的可用日期 |
| POST | `/availability/slots` | 特定日期的时间段 |
| POST | `/availability/check` | 检查某个时间段是否可用 |
| POST | `/booking/create` | 创建预约(标准) |
| POST | `/booking/create-with-payment` | 创建预约并绕过支付(使用优惠券) |
### 健康检查契约
`GET /health` 是稳定的下游运行时真实状态表面。
除了基本的运行时数据外,它现在还发布:
- 发布元组:
- `releaseSha`
- `releaseRef`
- `releaseVersion`
- `releaseBuiltAt`
- 嵌套的 `release.{ sha, ref, version, builtAt, modalEnvironment }`
- 协议元组:
- `protocolVersion`
- 嵌套的 `protocol.version`
- `protocol.flowOwner = "scheduling-bridge"`
- `protocol.backend = "acuity"`
- `protocol.transport = "http-json"`
- `protocol.endpoints`
- `protocol.capabilities`
下游应用应使用此元组来断言在 beta 验证和上线声明期间,
它们正在与哪个桥接器发布版本和协议接口进行通信。
此元组是供采用者使用的受支持的运行时真实状态表面。当 `/health` 可用时,
下游应用不应从包元数据、分支名称或 Modal 控制台状态推断桥接器所有权。
## 环境变量
| 变量 | 是否必填 | 默认值 | 描述 |
|----------|----------|---------|-------------|
| `PORT` | 否 | `3001` | 服务器端口 |
| `ACUITY_BASE_URL` | 否 | `https://MassageIthaca.as.me` | Acuity 调度页面 URL |
| `AUTH_TOKEN` | 推荐 | -- | 用于所有端点(/health 除外)的 Bearer token |
| `ACUITY_BYPASS_COUPON` | 用于绕过支付 | -- | 100% 礼品券代码 |
| `PLAYWRIGHT_HEADLESS` | 否 | `true` | 以无头模式运行浏览器 |
| `PLAYWRIGHT_TIMEOUT` | 否 | `30000` | 页面操作超时时间(毫秒) |
| `CHROMIUM_EXECUTABLE_PATH` | 否 | -- | 自定义 Chromium 路径(适用于 Lambda/无服务器) |
| `CHROMIUM_LAUNCH_ARGS` | 否 | -- | 以逗号分隔的 Chromium 参数 |
| `SERVICES_JSON` | 否 | -- | 用于绕过实时 Acuity 读取的可选静态服务目录 |
| `ACUITY_SERVICE_CACHE_TTL_MS` | 否 | `300000` | 缓存的实时服务目录在 BUSINESS/抓取器刷新之前的 TTL |
| `SCHEDULING_BRIDGE_SLOT_PROFILE_THRESHOLD_MS` | 否 | `1500` | 记录长尾时间段读取分析事件的阈值(毫秒) |
| `SCHEDULING_BRIDGE_PROFILE_SLOT_READS` | 否 | `false` | 强制记录时间段读取分析事件,即使未达到阈值 |
| `MIDDLEWARE_RELEASE_SHA` | 否 | -- | 通过 `/health` 暴露的发布 commit SHA |
| `MIDDLEWARE_RELEASE_REF` | 否 | -- | 通过 `/health` 暴露的发布 ref/tag |
| `MIDDLEWARE_RELEASE_VERSION` | 否 | -- | 通过 `/health` 暴露的发布版本 |
| `MIDDLEWARE_RELEASE_BUILT_AT` | 否 | -- | 通过 `/health` 暴露的构建时间戳 |
| `MIDDLEWARE_BUILD_TIMESTAMP` | 否 | -- | 用于 `/health` 的旧版回退构建时间戳 |
### 可观测性
桥接器向 stdout/stderr 发出 NDJSON 日志以进行运行时分析。
- `/health` 仍是下游应用的权威运行时真实状态表面
- 请求处理程序发出请求范围的结构化事件,包括 `requestId`
- 长尾时间段读取发出带有阶段耗时的 `slot_read_profile` 事件
- `SCHEDULING_BRIDGE_PROFILE_SLOT_READS=1` 强制为所有时间段读取发出分析事件
## 部署
### 独立 Node.js
```
pnpm install
pnpm dev # Development with tsx against src/server/handler.ts
# 或
pnpm build && pnpm start # Production via dist/server/handler.js
```
### Docker
```
docker build -t scheduling-bridge .
docker run -p 3001:3001 \
-e AUTH_TOKEN=your-secret-token \
-e ACUITY_BASE_URL=https://YourBusiness.as.me \
-e ACUITY_BYPASS_COUPON=your-coupon-code \
scheduling-bridge
```
### Modal Labs
```
# 首先在 Modal 控制台中设置 secrets:
# AUTH_TOKEN, ACUITY_BASE_URL, ACUITY_BYPASS_COUPON
# Modal 镜像构建了与 pnpm start 所使用的相同的 dist/server/handler.js artifact。
modal deploy modal-app.py
```
#### 支持的部署路径
实时 Acuity 桥接器支持的部署路径为:
1. 合并到 `main`
2. 让 `.github/workflows/deploy-modal.yml` 部署 `modal-app.py`
3. 注入 `MIDDLEWARE_RELEASE_SHA`、`MIDDLEWARE_RELEASE_REF`、
`MIDDLEWARE_RELEASE_VERSION` 和 `MIDDLEWARE_RELEASE_BUILT_AT`
4. 通过 `GET /health` 验证生成的桥接器元组
在操作上,这意味着:
- Modal 部署是发布真实状态的一部分,而不是旁路通道
- 实时桥接器应通过 `/health` 发布和协议元组来识别
- 下游应用在进行上线声明之前应验证它们所期望的元组
### Nix
```
nix develop # Enter dev shell with Node.js + Playwright
pnpm install
pnpm dev
```
## 发布权限
当前发布权限:
- 规范仓库:`Jesssullivan/scheduling-bridge`
- npm 包:`@tummycrypt/scheduling-bridge`
- GitHub Packages 镜像:`@jesssullivan/scheduling-bridge`
当前的发布 + 部署形式为:
1. 发布元数据声明一次
2. Bazel 验证/构建可发布构件
3. CI 在发布前对提取的 Bazel 包表面进行演练
4. GitHub Actions 发布该提取的构件
5. GitHub Actions 从 `main` 部署 Modal 运行时
6. 下游应用使用已发布的包,并通过 `/health` 验证实时运行时元组
此仓库是 Acuity 自动化关注点的唯一所有者。应用仓库和共享包可以使用桥接器并断言其运行时元组,但它们不应重复桥接器运行时所有权或发布真实逻辑。
## Runner 权限
包的 CI 和发布当前使用带有
`runner_mode: shared` 和 `publish_mode: same_runner` 的共享 `js-bazel-package` 工作流。
具体的共享 runner 标签来自仓库 Actions 变量,
并且必须通过成功的工作流运行来证明,然后才能将其视为操作真实状态。请将私有 runner 拓扑和应用细节保留在此公共仓库之外。
## 开发
```
pnpm install # Install dependencies
pnpm dev # Start dev server with tsx
pnpm typecheck # Run TypeScript type checking
pnpm build # Compile TypeScript to dist/
pnpm test # Run tests
```
## 许可证
MIT
标签:Acuity Scheduling, Bearer认证, DOM选择器, Effect TS, GNU通用公共许可证, HTTP服务器, MITM代理, Node.js, Playwright, 中间件服务器, 容器原生, 无头浏览器, 日程安排适配器, 浏览器自动化, 特征检测, 自动化攻击, 请求拦截, 远程代理, 零停机时间, 预约系统迁移