@crvouga/mockingbird-service-google-calendar

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

Stateful emulator of the Google Calendar v3 API and Google OAuth our EMR calls: events list/insert/update/delete/watch, channels.stop, calendarList/calendars, token exchange/refresh/revoke and userinfo, with signed-by-header push notifications.

原始使用文档
文档目录9 节
  1. Install
  2. Usage
  3. Routes
  4. Push notifications
  5. Admin (beyond the standard contract)
  6. Namespaces
  7. SDK
  8. Deliberately not modelled
  9. API

@crvouga/mockingbird-service-google-calendar

Local emulators. Real API contracts. Part of Mockingbird.

Stateful emulator of Google Calendar v3 and Google OAuth 2.0 for test suites, covering what our EMR backend calls: calendarList.list, calendars.insert, events.list (time windows, q, orderBy, paging, incremental sync with 410 fullSyncRequired), events.get/insert/ update/delete, events.watch with real push notifications to the channel's address, and channels.stop; plus the token endpoint (authorization code and refresh grants), revoke, and OpenID userinfo. Practitioners connect, sync EMR appointments and receive calendar pushes without a Google account.

  • Operation coverage: SUPPORT.md
  • The contract (openapi.yaml) is trimmed from Google's Calendar v3 discovery document and OAuth docs to what google-calendar-service.ts, authorize.business.ts and the webhook controller use.

Install

npm install -D @crvouga/mockingbird-service-google-calendar

ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with npx mockingbird-google-calendar serve, createServer from ./server (Node), or createRuntime with any Fetch server.

Usage

Seams (the app hardcodes Google today): give the calendar client a rootUrl (google.calendar({version: "v3", auth, rootUrl: "<emulator>/"})), give OAuth2Client endpoints: {oauth2TokenUrl: "<emulator>/token", oauth2RevokeUrl: "<emulator>/revoke"}, and point the userinfo fetch at <emulator>/oauth2/v3/userinfo. GOOGLE_WEBHOOK_URL can stay as it is: the emulator posts pushes to whatever address the app registers (http is allowed unless --require-https-webhooks).

npx mockingbird-google-calendar serve --port 8820 --client "$GOOGLE_CLIENT_ID:$GOOGLE_CLIENT_SECRET"
import { auth, calendar } from "@googleapis/calendar"
import { OAuth2Client } from "google-auth-library"
import { createServer } from "@crvouga/mockingbird-service-google-calendar/server"

const google = await createServer()

// The application's OAuth and Calendar clients. Only their URLs change.
const oauth = new OAuth2Client({
  clientId: "c",
  clientSecret: "s",
  redirectUri: "postmessage",
  endpoints: { oauth2TokenUrl: `${google.url}/token`, oauth2RevokeUrl: `${google.url}/revoke` },
})
// Authorization code "4/mock-<name>" signs in <name>@example.com ("4/mock-<email>" for others).
const { tokens } = await oauth.getToken("4/mock-dr.house")
const client = new auth.OAuth2()
client.setCredentials({ access_token: tokens.access_token ?? null })
const cal = calendar({ version: "v3", auth: client, rootUrl: `${google.url}/` })
await cal.events.insert({
  calendarId: "primary",
  requestBody: { summary: "Rounds", start: { dateTime: "2026-10-01T15:00:00Z" }, end: { dateTime: "2026-10-01T16:00:00Z" } },
})

await google.close()

An event created "in Google's UI" (admin route) is what the app then lists and syncs:

import { auth, calendar } from "@googleapis/calendar"
import { OAuth2Client } from "google-auth-library"
import { createServer } from "@crvouga/mockingbird-service-google-calendar/server"

const google = await createServer()

// Test control: the practitioner adds an event in Google's UI.
await fetch(`${google.url}/__admin/events`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    email: "[email protected]",
    event: { summary: "Rounds", start: { dateTime: "2026-10-01T15:00:00Z" }, end: { dateTime: "2026-10-01T16:00:00Z" } },
  }),
})

// The application lists it through the Calendar client.
const oauth = new OAuth2Client({
  clientId: "c",
  clientSecret: "s",
  redirectUri: "postmessage",
  endpoints: { oauth2TokenUrl: `${google.url}/token` },
})
const { tokens } = await oauth.getToken("4/mock-dr.house")
const client = new auth.OAuth2()
client.setCredentials({ access_token: tokens.access_token ?? null })
const cal = calendar({ version: "v3", auth: client, rootUrl: `${google.url}/` })
const { data } = await cal.events.list({ calendarId: "primary" })
console.log(data.items?.map((event) => event.summary)) // ["Rounds"]

await google.close()

Routes

RouteBehaviour
POST /tokenForm-encoded (client_secret_post or Basic). authorization_code: 4/mock-<name or email> → {access_token, expires_in: 3599, refresh_token, scope, token_type, id_token}; codes are single-use; anything else is 400 invalid_grant. refresh_token → a new access token (+ id_token); revoked or foreign tokens are invalid_grant. Unknown client (with clients set) 401 invalid_client.
POST /revoke?token=Revokes an access or refresh token; unknown / already revoked 400 invalid_token.
GET /oauth2/v3/userinfo{sub, email, email_verified, name, given_name, family_name, picture} (derived from the email, or set with PUT /__admin/users/:email).
GET /calendar/v3/users/me/calendarListThe account's primary calendar (id = email) and its secondary calendars.
POST /calendar/v3/calendarsCreate a secondary calendar (…@group.calendar.google.com).
GET /calendar/v3/calendars/{id}/eventstimeMin/timeMax (overlap), q, showDeleted, singleEvents, orderBy (startTime needs singleEvents), updatedMin, maxResults (≤ 2500) with nextPageToken; the last page carries nextSyncToken (not with q/orderBy/updatedMin). syncToken returns only changes since, deletions as {id, status: "cancelled"}; combined with a window or q it is 400; invalid or invalidated it is 410 fullSyncRequired.
POST …/events, GET/PUT/DELETE …/events/{eventId}Base32hex ids (client ids allowed; duplicates 409). Missing start/end 400, an empty range 400 timeRangeEmpty. PUT replaces (sequence +1 when the time moves). DELETE 204, again 410 deleted.
POST …/events/watch{id, type: "web_hook", address, token?, expiration?} → {kind: "api#channel", id, resourceId, resourceUri, expiration} (capped at 30 days). A sync push follows immediately. Duplicate live channel ids are 400.
POST /calendar/v3/channels/stop{id, resourceId} → 204; unknown 404.

Errors use Google's envelope {error: {errors: [{domain, reason, message}], code, message, status?}}. Invalid or expired access tokens are 401 authError (userinfo: invalid_request).

Push notifications

Every change to a calendar's events (through the API or the admin plane) posts to each live channel on that calendar: an empty body with X-Goog-Channel-ID, X-Goog-Channel-Token (when set), X-Goog-Channel-Expiration (RFC 1123), X-Goog-Message-Number (1 for sync, then +1), X-Goog-Resource-ID, X-Goog-Resource-URI and X-Goog-Resource-State (sync, then exists). Stopped or expired (emulator clock) channels get nothing. Non-2xx answers are retried (immediately, 1 s, 10 s, 1 min, 10 min). GET /__admin/webhooks lists deliveries, and /__admin/webhooks/events, …/replay, …/flush work as usual.

Admin (beyond the standard contract)

RouteEffect
POST /__admin/events{email, calendarId?, event}: create an event as the account would in Google's UI (pushes).
PUT /__admin/events/:id{email, calendarId?, event}: replace it (pushes).
DELETE /__admin/events/:id?email=&calendarId=Delete it (pushes).
GET /__admin/events?email=Stored events (with change sequences).
PUT /__admin/users/:emailSet the userinfo profile.
GET /__admin/channelsEvery channel with its message number, expiry and stopped flag.
POST /__admin/sync-tokens/invalidateEvery sync token issued so far answers 410.
GET/PUT /__admin/settings{clients?, tokenTtlSeconds?, requireHttpsWebhooks?, maxChannelTtlSeconds?, scope?}.

Fault presets (POST /__admin/faults {"preset": "<name>", "count"?: n}): invalid_grant, token_expired, rate_limited (403 rateLimitExceeded), too_many_requests (429), calendar_api_disabled (403 accessNotConfigured), insufficient_scopes, sync_token_gone, backend_error (503), connection_drop, push_duplicate, push_drop. gaxios retries 429 and 5xx itself, so pass a count above its budget (3 retries) to surface them.

Namespaces

By account: access tokens carry their email, so PUT /__admin/credentials {"credentials": {"[email protected]": "<namespace>"}} routes every calendar and userinfo call. x-mockingbird-namespace works as usual. A /__admin/ns/<name> prefix works for the token, revoke and userinfo URLs (they are full URLs) but not for the calendar client: googleapis resolves paths against rootUrl's origin and drops a path prefix.

SDK

The tests drive the real client stack of googleapis@149: @googleapis/[email protected] (the same generated calendar module over googleapis-common 7 / gaxios 6; the full googleapis package is ~125 MB unpacked, so only the module is installed) and [email protected].

Deliberately not modelled

  • Recurring events (recurrence is stored, never expanded), attendees' responses and email invitations, Meet links, free/busy, ACLs, colors and settings.
  • Real Google identity: id_tokens are JWT-shaped but not signed with Google's keys.
  • X-Goog-Changed (Google omits it for calendar event channels) and not_exists states.

API

ExportKindDescription
GoogleCalendarAPIclassThe in-process emulator: fetch(request), reset(), user(email), ensurePrimary(email), resolveCalendar(email, id), createEvent, updateEvent, deleteEvent, findEvent, notify(calendarId), channels(), events(email?), invalidateSyncTokens(). Options: sqlite, now, namespace, settings, onPush, onChannels.
createRuntimefunctionThe emulator with the full service contract and push delivery. Options: settings, push: {retryDelaysMs?, fetch?}, clock, seed, adminKey, onLog, sqlite.
GOOGLE_CALENDAR_PRESETSobjectEvery named fault preset.
GOOGLE_CALENDAR_NAMESPACEstringThe service name, "google-calendar".
DEFAULT_SCOPEstringThe scope the token endpoint reports.
pushHeadersfunctionThe X-Goog-* headers of a push notification.
issueAccessTokenfunctionMint an access token the emulator accepts for an email.
parseTokenfunctionDecode an emulator access or refresh token.
accessTokenCredentialfunctionThe account an access token belongs to (how credentials map to namespaces).
emailFromCodefunctionThe account a 4/mock-… authorization code signs in.
googleErrorfunctionBuild Google's error envelope.
document, operationIds, supportedOperationIdsvaluesThe OpenAPI contract and its operation ids.
createServer, serveTarget, DEFAULT_PORT (./server)NodeServe over node:http; the serve CLI target (--client, --require-https-webhooks); port 8820.

Part of mockingbird.

版本与发布记录

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

版本发布日期距今
3.11.0latest2026/11/11今天
3.10.92026/11/037 天前
3.10.82026/11/038 天前
3.10.72026/11/038 天前
3.10.62026/11/038 天前
3.10.52026/11/038 天前

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

@crvouga/mockingbird-service-google-calendar 是什么?

Stateful emulator of the Google Calendar v3 API and Google OAuth our EMR calls: events list/insert/update/delete/watch, channels.stop, calendarList/calendars, token exchange/refresh/revoke and userinfo, with signed-by-header push notifications.

如何安装 @crvouga/mockingbird-service-google-calendar?

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

@crvouga/mockingbird-service-google-calendar 支持 TypeScript 吗?

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

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

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

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

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

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

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

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

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