Files
simstudioai--sim/apps/sim/tools/google_calendar/utils.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

89 lines
3.4 KiB
TypeScript

import { generateId } from '@sim/utils/id'
import type { GoogleCalendarEventRequestBody } from '@/tools/google_calendar/types'
type EventDateTime = GoogleCalendarEventRequestBody['start']
const DEFAULT_TIME_ZONE = 'America/Los_Angeles'
const TZ_OFFSET_PATTERN = /([+-]\d{2}:?\d{2}|Z)$/
/**
* Build a Google Calendar event date/time object from a user-supplied value.
*
* A date-only value (e.g. `2025-06-03`) produces an all-day `{ date }` object.
* A datetime value produces `{ dateTime, timeZone? }`. An explicitly provided
* timezone always wins. Otherwise a default zone is attached only for "naive"
* datetimes that carry no UTC offset — when an offset is present it is authoritative
* and is never overridden with a guessed zone, which would misalign the time.
*
* For recurring events the Calendar API requires a named `timeZone` on start/end;
* callers should pass the user's timezone explicitly (an RFC3339 offset alone is
* insufficient to expand a recurrence across DST).
*/
export function buildEventDateTime(value: string, timeZone: string | undefined): EventDateTime {
const isDateOnly = !value.includes('T')
if (isDateOnly) {
return { date: value }
}
const hasOffset = TZ_OFFSET_PATTERN.test(value)
const result: EventDateTime = { dateTime: value }
if (timeZone) {
result.timeZone = timeZone
} else if (!hasOffset) {
result.timeZone = DEFAULT_TIME_ZONE
}
return result
}
/** Normalize a comma/newline-separated string or array of attendee emails into `[{ email }]`. */
export function normalizeAttendees(
attendees: string | string[] | undefined
): Array<{ email: string }> {
if (!attendees) return []
const list = Array.isArray(attendees)
? attendees
: attendees.split(',').map((email) => email.trim())
return list.filter((email) => email.length > 0).map((email) => ({ email }))
}
/**
* Recurring events require a named `timeZone` on their timed start/end — the Calendar API
* rejects them otherwise, and an RFC3339 offset is not a substitute (an IANA zone cannot be
* derived from a fixed offset). Throws a clear error so we fail fast with guidance instead of
* silently guessing a zone (which would misalign the recurrence) or sending an invalid request.
* All-day recurring events (date-only values) do not need a timezone and are allowed.
*/
export function assertRecurringTimeZone(
dateTimes: Array<string | undefined>,
timeZone: string | undefined
): void {
if (timeZone) return
const hasTimedValue = dateTimes.some((value) => value?.includes('T'))
if (hasTimedValue) {
throw new Error(
'Recurring events require a time zone. Provide the timeZone parameter (an IANA name, e.g. America/New_York).'
)
}
}
/** Normalize recurrence rules (single string, newline-separated string, or array) into an array. */
export function normalizeRecurrence(recurrence: string | string[] | undefined): string[] {
if (!recurrence) return []
const list = Array.isArray(recurrence) ? recurrence : recurrence.split('\n')
return list.map((rule) => rule.trim()).filter((rule) => rule.length > 0)
}
/** Build a `conferenceData.createRequest` payload that asks Google to attach a Meet link. */
export function buildGoogleMeetConferenceData(): GoogleCalendarEventRequestBody['conferenceData'] {
return {
createRequest: {
requestId: generateId(),
conferenceSolutionKey: { type: 'hangoutsMeet' },
},
}
}