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, 中间件服务器, 容器原生, 无头浏览器, 日程安排适配器, 浏览器自动化, 特征检测, 自动化攻击, 请求拦截, 远程代理, 零停机时间, 预约系统迁移