@crvouga/mockingbird-service-whoop

中文文档
1.12.0发布于 2026/11/13MITTypeScriptES Module构建来源已验证近 30 天下载 2139

Stateful WHOOP v2 emulator: collections, related-sleep recovery windows, and OAuth refresh.

原始使用文档
文档目录7 节
  1. Install
  2. Usage
  3. Routes and state
  4. Test controls
  5. Oracle and tests
  6. Deliberately not modelled
  7. API

@crvouga/mockingbird-service-whoop

Local emulators. Real API contracts. Part of Mockingbird.

WIP WHOOP v2 emulator for synthetic workout, sleep, recovery and cycle synchronization.

Install

bun add @crvouga/mockingbird-service-whoop

Usage

import { createServer } from "@crvouga/mockingbird-service-whoop/server"

// Test setup: a local WHOOP. The application's API base is its URL plus /developer.
const server = await createServer()
const baseUrl = `${server.url}/developer`

// Application code: the request it sends to WHOOP.
const response = await fetch(`${baseUrl}/v2/activity/workout`, {
  headers: { authorization: "Bearer mock_whoop_token" },
})
const { records } = await response.json()

await server.close()

Run mockingbird-whoop serve --port 12129. Inject the local origin plus /developer as the consumer API base, and the origin alone as OAuth base. Consumer endpoint wiring is separate from this emulator.

Routes and state

POST /oauth/oauth2/token accepts form-encoded authorization-code or refresh-token grants with client credentials. Seed clients, codes, and grants via options or admin state. Default emulator client: mock_client / mock_client_secret; default bearer mock_whoop_token and refresh mock_whoop_refresh. The default grant has all four read scopes and offline. Codes bind client, optional redirect and optional S256 PKCE challenge. Codes are single-use, including concurrent exchanges. New tokens expire after 3600 seconds (tokenTtlSeconds is configurable). Refresh invalidates both old access and refresh tokens, keeps the same user/scopes, and issues a refresh token only for an offline grant.

GET /oauth/oauth2/auth is the authorization endpoint a browser visits to connect a member. There is no consent page: the request is validated, then the consents collection answers for the user and the browser is redirected (302) to redirect_uri with code and state, or error, error_description and state. The code is stored in codes bound to the client, the exact redirect_uri, the granted scopes (so the token's scope is what was requested, and refresh_token appears only with offline) and an optional S256 code_challenge; POST /oauth/oauth2/token redeems it once. An unknown client (401 invalid_client), a missing client_id or redirect_uri, or a redirect_uri the client did not register (400 invalid_request, JSON {error,error_description}) is answered in place and never redirected to. Clients take optional redirectUris (exact match; omitted accepts any absolute http(s) URI so the default mock_client works as is) and scopes (omitted allows the documented WHOOP scopes). Redirected errors: invalid_request (no response_type, or a code_challenge_method other than S256), unsupported_response_type, invalid_scope, invalid_state (missing or under 8 characters). A longer state is echoed unchanged.

Consent controls: rows {id, decision: "approve"|"deny"|"error", userId?, codeTtlSeconds?, error?, errorDescription?} where id is a client id or * (every client without its own row). Seed with the consents option or POST /__admin/state/consents {"id":"*","value":{"id":"*","decision":"deny"}}. No row means approve for synthetic-user with a 600 second code. deny redirects access_denied and error redirects server_error (override with error/errorDescription), both with the unchanged state and no code. Issued codes are listed at GET /__admin/state/codes.

GET /developer/v2/user/profile/basic returns the token user's {user_id,email,first_name,last_name}: seed a records row with collection: "profile", userId the token's user and data the profile (fake values only). Scope read:profile is required (403 {detail:"Access forbidden."} like the collections, 401 for an unknown or expired token, 404 {detail:"Not Found"} when the user has no profile row). The default grant does not carry read:profile.

GET /developer/v2/activity/workout, /developer/v2/activity/sleep, /developer/v2/recovery, /developer/v2/cycle return {records,next_token}. limit defaults to 10, maximum 25; nextToken continues pages. Results sort newest start first; start includes equal timestamps, end excludes equal starts and defaults to emulate now. The collection time-window implementation uses start timestamps, including activities spanning the end instant. Scope names are read:workout, read:sleep, read:recovery, read:cycles.

Seed records: [{key,userId,collection,data}]; data is a synthetic provider-shaped record. Recovery has cycle_id and sleep_id, not id, start, or end: seed a matching sleep record to supply its time window. This corrects the issue's generic recovery field list using the official schema. Missing related-sleep fixtures are excluded. Cycle ids remain numbers; workout/sleep ids remain UUIDs. Metrics and score states are passed through, preserving null or absent metrics. Distinct storage keys can model duplicate vendor records. Cursor internals are local stand-ins; consumers must retain the same query while paginating.

Test controls

Standard relocatable /__admin health/state/reset/clock/journal/Timeline/fault routes. State collections contain only synthetic fixtures. Header namespaces, /__admin/ns/<name> paths and bearer-credential mappings isolate state. Journals record metadata, not OAuth or health bodies. Presets unauthorized, rate_limited, server_error, connection_drop support deterministic failures; generic faults add latency. Preset error bodies are scripted local fixtures, not claims of exact quota/error wording. No webhooks are required.

Oracle and tests

Derived from the official OpenAPI, OAuth reference, and refresh example. The contract retains the official collection response schemas. Authorized live data and OAuth success were not probed; no live credentials used.

Best guess, not oracle-verified: the authorization endpoint and the profile route are not observed live. /oauth/oauth2/auth and /oauth/oauth2/token are Ory Hydra paths, so redirect status (302), the error/error_description/state redirect shape, the 8 character state minimum, and the error codes follow Hydra and its fosite library plus the WHOOP OAuth page. The statuses and JSON bodies for an unknown client or unregistered redirect_uri (Hydra renders an error page on the live service), the invalid_scope allow-list, treating a missing scope as an empty grant, S256-only PKCE, access_denied/server_error for consent controls, and the 403 for a missing read:profile scope are the emulator's choices, not observed vendor responses. The profile shape is the official UserBasicProfile schema.

bun test runs acceptance, all-operation self-parity and divergence detection. bun scripts/python-smoke.ts uses uv to run actual requests 2.32.5 and httpx 0.28.1 against local HTTP. It is not the private consumer itself. bun run parity requires WHOOP_ACCESS_TOKEN and performs a safe collection-envelope probe without printing health data.

Deliberately not modelled

No real users, device pairing, sensors, a consent page (the consents rows stand in for the user's answer), hardware SDK, webhook delivery, body measurements, partner APIs or scoring computation. No cursor-format parity, real quota enforcement, concurrent refresh grace period or private-client integration claim. Partial synthetic records can be seeded deliberately; validate production fixtures against the supplied official schemas. Recovery relies on matching synthetic sleep fixtures.

API

Root runtime exports: WhoopAPI, WHOOP_NAMESPACE, COLLECTION_SCOPES, KNOWN_SCOPES, MIN_STATE_LENGTH, createRuntime, WHOOP_PRESETS, document, operationIds, supportedOperationIds. Types: WhoopAPIOptions, Grant, OAuthCode, Client, Consent, DataRecord, CollectionName, WhoopRuntimeOptions, WhoopRuntime, OperationId, SupportedOperationId.

/server: createServer, serveTarget, DEFAULT_PORT; types WhoopServerOptions, WhoopServer.

版本与发布记录

@crvouga/mockingbird-service-whoop 共发布 19 个版本,首次发布于 2026/10/05;过去 12 个月发布了 19 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.12.0latest2026/11/13今天
1.11.12026/11/121 天前
1.11.02026/11/121 天前
1.10.02026/11/112 天前
1.9.42026/11/0310 天前
1.9.32026/11/0310 天前

关于 @crvouga/mockingbird-service-whoop 的常见问题

@crvouga/mockingbird-service-whoop 是什么?

Stateful WHOOP v2 emulator: collections, related-sleep recovery windows, and OAuth refresh.

如何安装 @crvouga/mockingbird-service-whoop?

在项目目录运行 npm install @crvouga/mockingbird-service-whoop,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 mockingbird-whoop,可以用 npx mockingbird-whoop 直接运行。

@crvouga/mockingbird-service-whoop 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@crvouga/mockingbird-service-whoop 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@crvouga/mockingbird-service-whoop 可以商用吗?

@crvouga/mockingbird-service-whoop 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@crvouga/mockingbird-service-whoop 的下载量有多少?

根据 npm 官方下载统计,@crvouga/mockingbird-service-whoop 在 2026/10/11 – 2026/11/09 这 30 天内被下载 2,139 次,日均约 71 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@crvouga/mockingbird-service-whoop 还在维护吗?

最新版本 1.12.0 发布于 2026/11/13(今天);至今共发布 19 个版本,其中过去 12 个月发布了 19 个;首次发布于 2026/10/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。