Monitor websites uptime using Cloudflare Workers
Something went wrong. Try again.
TypeScript
at optimization
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318import type { ApiError, Badge, BadgeCreate, DnsDiagnosticResult, Monitor, MonitorBulkFrequencyUpdate, MonitorBulkBadgeUpdate, MonitorCreate, MonitorSummary, NotificationService, NotificationServiceCreate, NotificationServiceUpdate, Observation, PublicMonitorSummary, StatusPageSave,} from '@uptime/contracts';import type { RegionId } from '@uptime/regions';import type { RegionDefinition } from '@uptime/regions';
export interface LatencyPoint { observedAt: string; regionId: RegionId; responseMs: number | null; success: boolean;}export interface LatencyStats { regionId: LatencyPoint['regionId']; sampleCount: number; successCount: number; p50Ms: number | null; p95Ms: number | null; p99Ms: number | null;}export interface AggregateLatencyPoint { observedAt: string; responseMs: number | null; success: boolean;}export interface AggregateLatencyStats { averageResponseMs: number | null; maximumResponseMs: number | null; maximumResponseRegionId: RegionId | null; minimumResponseMs: number | null;}
export interface MonitorLatencyData { latency: { points: LatencyPoint[]; stats: LatencyStats[]; aggregatePoints?: AggregateLatencyPoint[]; aggregateStats?: AggregateLatencyStats; };}
export interface UptimeDay { date: string; uptimePercentage: number | null; averageResponseMs: number | null;}
export interface MonitorUptimeData { uptimePercentage: number | null; status: 'up' | 'down' | 'unknown'; recoveryStatus?: 'up' | 'down' | 'recovering' | null; days: UptimeDay[];}
export interface MonitorDetailResponse extends MonitorLatencyData { summary: MonitorSummary; uptime: MonitorUptimeData;}
export interface PublicMonitorDetailResponse extends MonitorLatencyData { summary: PublicMonitorSummary; uptime: MonitorUptimeData;}
export interface ObservationPage { items: Observation[]; nextCursor: string | null;}
export interface DnsDiagnosticRecord { id: string; monitorId: string; checkRunId: string | null; observationId: string | null; regionId: RegionId; kind: 'dns_candidates'; windowStartedAt: string; lifecycle: 'pending' | 'complete' | 'unavailable'; finalHostname: string | null; result: DnsDiagnosticResult | null; failureCode: string | null; requestedAt: string; startedAt: string | null; completedAt: string | null; createdAt: string;}
export interface DnsDiagnosticPage { items: DnsDiagnosticRecord[]; nextCursor: string | null;}
export interface StatusPageSummary { id: string; title: string; publicSlug: string | null; monitorCount: number; createdAt: string; updatedAt: string;}export interface StatusPageDetail extends StatusPageSummary { groups: Array<{ id: string; title: string; position: number; width: 'full' | 'half'; showBadges: boolean; monitors: Array<{ id: string; name: string | null; url: string; publicSlug: string | null; position: number; badge?: Badge | null; }>; }>;}export interface PublicStatusPage { id: string; title: string; publicSlug: string | null; groups: Array<{ id: string; title: string; width: 'full' | 'half'; showBadges: boolean; monitors: Array<{ id: string; name: string | null; url: string; publicSlug: string | null; badge?: Badge | null; uptimeThresholds?: { green: number; lightGreen: number; orange: number; }; uptimePercentage: number | null; status: 'up' | 'down' | 'unknown'; configuredRegionCount: number; affectedRegionIds: RegionId[]; recoveryStatus: 'up' | 'down' | 'recovering' | null; days: Array<{ date: string; uptimePercentage: number | null; averageResponseMs: number | null; }>; }>; }>;}
export class RequestError extends Error { readonly fieldErrors: Record<string, string[]> | undefined; constructor( message: string, readonly status: number, error?: ApiError, ) { super(message); this.fieldErrors = error?.error.fieldErrors; }}
async function request<T>( path: string, init?: RequestInit, credentials: RequestCredentials = 'include',): Promise<T> { const headers = init?.headers !== undefined || init?.body !== undefined ? new Headers(init.headers) : undefined; if (init?.body !== undefined && headers && !headers.has('content-type')) { headers.set('content-type', 'application/json'); } const response = await fetch(`/api${path}`, { ...init, credentials, ...(headers ? { headers } : {}), }); if (!response.ok) { const error = (await response.json().catch(() => undefined)) as ApiError | undefined; throw new RequestError( error?.error.message ?? `Request failed (${response.status})`, response.status, error, ); } if (response.status === 204) return undefined as T; return (await response.json()) as T;}
async function publicRequest<T>(path: string): Promise<T> { return request<T>(path, undefined, 'omit');}
export const api = { session: () => request<{ admin: { email: string } }>('/auth/session'), signIn: (password: string) => request<{ admin: { email: string } }>('/auth/login', { method: 'POST', body: JSON.stringify({ password }), }), signOut: () => request<void>('/auth/logout', { method: 'POST' }), regions: () => request<{ regions: RegionDefinition[] }>('/regions'), monitors: () => request<{ monitors: MonitorSummary[] }>('/monitors'), badges: () => request<{ badges: Badge[] }>('/badges'), createBadge: (input: BadgeCreate) => request<{ badge: Badge }>('/badges', { method: 'POST', body: JSON.stringify(input), }).then((result) => result.badge), monitor: async (id: string, range: string): Promise<MonitorDetailResponse> => { const [monitor, latency, uptime] = await Promise.all([ request<{ summary: MonitorSummary }>(`/monitors/${id}`), request<MonitorLatencyData['latency']>(`/monitors/${id}/latency?range=${range}`), request<{ uptime: MonitorUptimeData }>(`/monitors/${id}/uptime`), ]); return { summary: monitor.summary, latency, uptime: uptime.uptime }; }, publicMonitor: async (id: string, range: string): Promise<PublicMonitorDetailResponse> => { const [monitor, latency, uptime] = await Promise.all([ publicRequest<{ summary: PublicMonitorSummary }>(`/monitors/public/${id}`), publicRequest<MonitorLatencyData['latency']>(`/monitors/public/${id}/latency?range=${range}`), publicRequest<{ uptime: MonitorUptimeData }>(`/monitors/public/${id}/uptime`), ]); return { summary: monitor.summary, latency, uptime: uptime.uptime }; }, createMonitor: (input: MonitorCreate) => request<{ summary: MonitorSummary }>('/monitors', { method: 'POST', body: JSON.stringify(input), }), updateMonitor: (id: string, input: Partial<MonitorCreate>) => request<{ summary: MonitorSummary }>(`/monitors/${id}`, { method: 'PATCH', body: JSON.stringify(input), }), updateMonitorFrequencies: (input: MonitorBulkFrequencyUpdate) => request<{ updatedCount: number }>('/monitors/bulk-frequency', { method: 'PATCH', body: JSON.stringify(input), }), updateMonitorBadges: (input: MonitorBulkBadgeUpdate) => request<{ updatedCount: number }>('/monitors/bulk-badge', { method: 'PATCH', body: JSON.stringify(input), }), deleteMonitor: (id: string) => request<void>(`/monitors/${id}`, { method: 'DELETE' }), deleteMonitorHistory: (id: string) => request<void>(`/monitors/${id}/history`, { method: 'DELETE' }), notificationServices: () => request<{ services: NotificationService[] }>('/notification-services'), createNotificationService: (input: NotificationServiceCreate) => request<{ service: NotificationService }>('/notification-services', { method: 'POST', body: JSON.stringify(input), }), updateNotificationService: (id: string, input: NotificationServiceUpdate) => request<{ service: NotificationService }>(`/notification-services/${id}`, { method: 'PATCH', body: JSON.stringify(input), }), deleteNotificationService: (id: string) => request<void>(`/notification-services/${id}`, { method: 'DELETE' }), testNotificationService: (id: string) => request<{ success: true }>(`/notification-services/${id}/test`, { method: 'POST' }), statusPages: () => request<{ statusPages: StatusPageSummary[] }>('/status-pages'), statusPage: (id: string) => request<{ statusPage: StatusPageDetail }>(`/status-pages/${id}`).then( (result) => result.statusPage, ), createStatusPage: (input: StatusPageSave) => request<{ statusPage: StatusPageDetail }>('/status-pages', { method: 'POST', body: JSON.stringify(input), }).then((result) => result.statusPage), updateStatusPage: (id: string, input: StatusPageSave) => request<{ statusPage: StatusPageDetail }>(`/status-pages/${id}`, { method: 'PUT', body: JSON.stringify(input), }).then((result) => result.statusPage), deleteStatusPage: (id: string) => request<void>(`/status-pages/${id}`, { method: 'DELETE' }), publicStatusPage: (id: string) => publicRequest<{ statusPage: PublicStatusPage }>(`/status-pages/public/${id}`).then( (result) => result.statusPage, ), observations: (id: string, range: string, cursor?: string) => request<{ observations: Observation[]; nextCursor: string | null }>( `/monitors/${id}/observations?range=${range}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`, ).then((result) => ({ items: result.observations, nextCursor: result.nextCursor })), dnsDiagnostics: ( id: string, range: '7d' | '30d', regionId?: RegionId, cursor?: string, ): Promise<DnsDiagnosticPage> => { const params = new URLSearchParams({ range }); if (regionId) params.set('regionId', regionId); if (cursor) params.set('cursor', cursor); return request<{ diagnostics: DnsDiagnosticRecord[]; nextCursor: string | null }>( `/monitors/${id}/dns-diagnostics?${params.toString()}`, ).then((result) => ({ items: result.diagnostics, nextCursor: result.nextCursor })); },};