From 4bcf4173ea42f12d300cfdd3e7e4e8b26c331d96 Mon Sep 17 00:00:00 2001 From: lyc8503 Date: Sat, 19 Apr 2025 21:50:43 +0800 Subject: [PATCH] misc: run prettier --- .github/workflows/issue_translate.yml | 6 +- .github/workflows/sync.yaml | 6 +- .prettierrc.yaml | 2 +- README.md | 3 +- README_zh-CN.md | 1 + components/DetailBar.tsx | 33 ++++- components/MonitorDetail.tsx | 6 +- components/MonitorList.tsx | 82 ++++++----- components/OverallStatus.tsx | 30 ++-- env.d.ts | 12 +- middleware.ts | 9 +- next.config.js | 8 +- pages/api/data.ts | 23 ++-- pages/index.tsx | 23 ++-- proxy/api/index.js | 79 ++++++----- proxy/vercel.json | 2 +- uptime.config.ts | 11 +- worker/.prettierrc.yaml | 2 +- worker/src/index.ts | 111 ++++++++------- worker/src/monitor.ts | 34 +++-- worker/src/util.ts | 31 ++++- worker/tsconfig.json | 190 +++++++++++++------------- 22 files changed, 402 insertions(+), 302 deletions(-) diff --git a/.github/workflows/issue_translate.yml b/.github/workflows/issue_translate.yml index 4211857..83c127b 100644 --- a/.github/workflows/issue_translate.yml +++ b/.github/workflows/issue_translate.yml @@ -1,8 +1,8 @@ name: 'issue-translator' -on: - issue_comment: +on: + issue_comment: types: [created] - issues: + issues: types: [opened] jobs: diff --git a/.github/workflows/sync.yaml b/.github/workflows/sync.yaml index d8b1d5b..2a0b3d0 100644 --- a/.github/workflows/sync.yaml +++ b/.github/workflows/sync.yaml @@ -37,7 +37,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - + # Fetch latest code git clone https://github.com/lyc8503/UptimeFlare /tmp/latest rm -rf /tmp/latest/.git @@ -49,8 +49,8 @@ jobs: git add . git commit -m "Sync latest code from upstream" git push - + - name: Trigger deployment uses: benc-uk/workflow-dispatch@v1 with: - workflow: deploy.yml \ No newline at end of file + workflow: deploy.yml diff --git a/.prettierrc.yaml b/.prettierrc.yaml index 04fef45..75d2ffa 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -2,4 +2,4 @@ trailingComma: 'es5' tabWidth: 2 semi: false singleQuote: true -printWidth: 100 \ No newline at end of file +printWidth: 100 diff --git a/README.md b/README.md index c76279e..0738dee 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A more advanced, serverless, and free uptime monitoring & status page solution, powered by Cloudflare Workers, complete with a user-friendly interface. ## ⭐Features + - Open-source, easy to deploy (in under 10 minutes, no local tools required), and free - Monitoring capabilities - Up to 50 checks at 1-minute intervals @@ -52,7 +53,7 @@ Please refer to [Wiki](https://github.com/lyc8503/UptimeFlare/wiki) - [x] ~~Self-host Dockerfile~~ - [x] Incident history - [x] Improve `checkLocationWorkerRoute` and fix possible `proxy failed` -- [x] Groups +- [x] Groups - [x] Remove old incidents - [x] ~~Known issue~~: `fetch` doesn't support non-standard port (resolved after CF update) - [ ] Compatibility date update diff --git a/README_zh-CN.md b/README_zh-CN.md index 3e867de..14a3f26 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -8,6 +8,7 @@ 一个由 Cloudflare Workers 驱动的功能丰富、Serverless 且免费的 Uptime 监控及状态页面。 ## ⭐功能 + - 开源,易于部署(全程无需本地工具,耗时不到 10 分钟),且完全免费 - 监控功能 - 最多支持 50 个 1 分钟精度的检查 diff --git a/components/DetailBar.tsx b/components/DetailBar.tsx index d6c33fc..4c05b93 100644 --- a/components/DetailBar.tsx +++ b/components/DetailBar.tsx @@ -50,13 +50,20 @@ export default function DetailBar({ if (overlap > 0) { for (let i = 0; i < incident.error.length; i++) { let partStart = incident.start[i] - let partEnd = i === incident.error.length - 1 ? (incident.end ?? currentTime) : incident.start[i + 1] + let partEnd = + i === incident.error.length - 1 ? incident.end ?? currentTime : incident.start[i + 1] partStart = Math.max(partStart, dayStart) partEnd = Math.min(partEnd, dayEnd) if (overlapLen(dayStart, dayEnd, partStart, partEnd) > 0) { - const startStr = new Date(partStart * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) - const endStr = new Date(partEnd * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) + const startStr = new Date(partStart * 1000).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + }) + const endStr = new Date(partEnd * 1000).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + }) incidentReasons.push(`[${startStr}-${endStr}] ${incident.error[i]}`) } } @@ -77,7 +84,10 @@ export default function DetailBar({ <>
{dayPercent + '% at ' + new Date(dayStart * 1000).toLocaleDateString()}
{dayDownTime > 0 && ( -
{`Down for ${moment.preciseDiff(moment(0), moment(dayDownTime * 1000))} (click for detail)`}
+
{`Down for ${moment.preciseDiff( + moment(0), + moment(dayDownTime * 1000) + )} (click for detail)`}
)} ) @@ -94,10 +104,14 @@ export default function DetailBar({ }} onClick={() => { if (dayDownTime > 0) { - setModalTitle(`🚨 ${monitor.name} incidents at ${new Date(dayStart * 1000).toLocaleDateString()}`) + setModalTitle( + `🚨 ${monitor.name} incidents at ${new Date(dayStart * 1000).toLocaleDateString()}` + ) setModelContent( <> - {incidentReasons.map((reason, index) => (
{reason}
))} + {incidentReasons.map((reason, index) => ( +
{reason}
+ ))} ) setModalOpened(true) @@ -110,7 +124,12 @@ export default function DetailBar({ return ( <> - setModalOpened(false)} title={modalTitle} size={'40em'}> + setModalOpened(false)} + title={modalTitle} + size={'40em'} + > {modelContent} {monitor.statusPageLink ? ( - + {statusIcon} {monitor.name} ) : ( diff --git a/components/MonitorList.tsx b/components/MonitorList.tsx index ac33ea5..c8530b5 100644 --- a/components/MonitorList.tsx +++ b/components/MonitorList.tsx @@ -1,7 +1,7 @@ import { MonitorState, MonitorTarget } from '@/uptime.types' import { Accordion, Card, Center, Text } from '@mantine/core' import MonitorDetail from './MonitorDetail' -import { pageConfig } from '@/uptime.config'; +import { pageConfig } from '@/uptime.config' function countDownCount(state: MonitorState, ids: string[]) { let downCount = 0 @@ -28,45 +28,61 @@ function getStatusTextColor(state: MonitorState, ids: string[]) { } } -export default function MonitorList({ monitors, state }: { monitors: MonitorTarget[]; state: MonitorState }) { +export default function MonitorList({ + monitors, + state, +}: { + monitors: MonitorTarget[] + state: MonitorState +}) { // @ts-ignore let group: any = pageConfig.group let groupedMonitor = group && Object.keys(group).length > 0 let content - + if (groupedMonitor) { // Grouped monitors content = ( - - { - Object.keys(group).map(groupName => ( - - -
-
{groupName}
- - {group[groupName].length - countDownCount(state, group[groupName])} - /{group[groupName].length} Operational - -
-
- - { - monitors - .filter(monitor => group[groupName].includes(monitor.id)) - .sort((a, b) => group[groupName].indexOf(a.id) - group[groupName].indexOf(b.id)) - .map(monitor => ( -
- - - -
- )) - } -
-
- )) - } + + {Object.keys(group).map((groupName) => ( + + +
+
{groupName}
+ + {group[groupName].length - countDownCount(state, group[groupName])}/ + {group[groupName].length} Operational + +
+
+ + {monitors + .filter((monitor) => group[groupName].includes(monitor.id)) + .sort((a, b) => group[groupName].indexOf(a.id) - group[groupName].indexOf(b.id)) + .map((monitor) => ( +
+ + + +
+ ))} +
+
+ ))}
) } else { diff --git a/components/OverallStatus.tsx b/components/OverallStatus.tsx index 72252d6..fba8a9f 100644 --- a/components/OverallStatus.tsx +++ b/components/OverallStatus.tsx @@ -1,24 +1,24 @@ import { Center, Title } from '@mantine/core' import { IconCircleCheck, IconAlertCircle } from '@tabler/icons-react' -import { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react' function useWindowVisibility() { - const [isVisible, setIsVisible] = useState(true); + const [isVisible, setIsVisible] = useState(true) useEffect(() => { const handleVisibilityChange = () => { - console.log('visibility change', document.visibilityState); - setIsVisible(document.visibilityState === 'visible'); - }; + console.log('visibility change', document.visibilityState) + setIsVisible(document.visibilityState === 'visible') + } - document.addEventListener('visibilitychange', handleVisibilityChange); + document.addEventListener('visibilitychange', handleVisibilityChange) return () => { - document.removeEventListener('visibilitychange', handleVisibilityChange); - }; - }, []); + document.removeEventListener('visibilitychange', handleVisibilityChange) + } + }, []) - return isVisible; + return isVisible } export default function OverallStatus({ @@ -36,12 +36,14 @@ export default function OverallStatus({ statusString = 'All systems operational' icon = } else { - statusString = `Some systems not operational (${state.overallDown} out of ${state.overallUp + state.overallDown})` + statusString = `Some systems not operational (${state.overallDown} out of ${ + state.overallUp + state.overallDown + })` } const [openTime] = useState(Math.round(Date.now() / 1000)) const [currentTime, setCurrentTime] = useState(Math.round(Date.now() / 1000)) - const isWindowVisible = useWindowVisibility(); + const isWindowVisible = useWindowVisibility() useEffect(() => { const interval = setInterval(() => { @@ -66,7 +68,9 @@ export default function OverallStatus({ Last updated on:{' '} - {`${new Date(state.lastUpdate * 1000).toLocaleString()} (${currentTime - state.lastUpdate} sec ago)`} + {`${new Date(state.lastUpdate * 1000).toLocaleString()} (${ + currentTime - state.lastUpdate + } sec ago)`} ) diff --git a/env.d.ts b/env.d.ts index 89aba0b..6536e4f 100644 --- a/env.d.ts +++ b/env.d.ts @@ -1,9 +1,9 @@ declare global { - namespace NodeJS { - interface ProcessEnv { - UPTIMEFLARE_STATE: KVNamespace; - } - } + namespace NodeJS { + interface ProcessEnv { + UPTIMEFLARE_STATE: KVNamespace + } + } } -export {}; +export {} diff --git a/middleware.ts b/middleware.ts index 78ec4d8..3568f08 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { workerConfig } from './uptime.config' - + export async function middleware(request: NextRequest) { // @ts-ignore const passwordProtection = workerConfig.passwordProtection @@ -12,15 +12,16 @@ export async function middleware(request: NextRequest) { if (authHeader && authHeader.length === expected.length) { // a simple timing-safe compare - authenticated = true; + authenticated = true for (let i = 0; i < authHeader.length; i++) { - if (authHeader[i] !== expected[i]) authenticated = false; + if (authHeader[i] !== expected[i]) authenticated = false } } if (!authenticated) { return NextResponse.json( - { code: 401, message: "Not authenticated" }, { status: 401, headers: { 'WWW-Authenticate': 'Basic' } } + { code: 401, message: 'Not authenticated' }, + { status: 401, headers: { 'WWW-Authenticate': 'Basic' } } ) } } diff --git a/next.config.js b/next.config.js index acb8373..5eee312 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true + reactStrictMode: true, } module.exports = nextConfig @@ -11,8 +11,8 @@ if (process.env.NODE_ENV === 'development') { bindings: { UPTIMEFLARE_STATE: { type: 'kv', - id: 'UPTIMEFLARE_STATE' - } - } + id: 'UPTIMEFLARE_STATE', + }, + }, }) } diff --git a/pages/api/data.ts b/pages/api/data.ts index 6186e02..efeb0b9 100644 --- a/pages/api/data.ts +++ b/pages/api/data.ts @@ -1,11 +1,10 @@ -import { workerConfig } from "@/uptime.config" -import { MonitorState } from "@/uptime.types" -import { NextRequest } from "next/server" +import { workerConfig } from '@/uptime.config' +import { MonitorState } from '@/uptime.types' +import { NextRequest } from 'next/server' export const runtime = 'edge' export default async function handler(req: NextRequest): Promise { - const { UPTIMEFLARE_STATE } = process.env as unknown as { UPTIMEFLARE_STATE: KVNamespace } @@ -15,11 +14,11 @@ export default async function handler(req: NextRequest): Promise { return new Response(JSON.stringify({ error: 'No data available' }), { status: 500, headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }) } - const state = (JSON.parse(stateStr)) as unknown as MonitorState + const state = JSON.parse(stateStr) as unknown as MonitorState let monitors: any = {} @@ -29,7 +28,7 @@ export default async function handler(req: NextRequest): Promise { up: isUp, latency: state.latency[monitor.id].recent.slice(-1)[0].ping, location: state.latency[monitor.id].recent.slice(-1)[0].loc, - message: isUp ? "OK" : state.incident[monitor.id].slice(-1)[0].error.slice(-1)[0] + message: isUp ? 'OK' : state.incident[monitor.id].slice(-1)[0].error.slice(-1)[0], } } @@ -37,12 +36,12 @@ export default async function handler(req: NextRequest): Promise { up: state.overallUp, down: state.overallDown, updatedAt: state.lastUpdate, - monitors: monitors + monitors: monitors, } return new Response(JSON.stringify(ret), { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }) -} \ No newline at end of file +} diff --git a/pages/index.tsx b/pages/index.tsx index afc9ec6..782a5bd 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -22,21 +22,17 @@ export default function Home({ tooltip?: string statusPageLink?: string }) { - let state; + let state if (stateStr !== undefined) { state = JSON.parse(stateStr) as MonitorState } // Specify monitorId in URL hash to view a specific monitor (can be used in iframe) - const monitorId = window.location.hash.substring(1); + const monitorId = window.location.hash.substring(1) if (monitorId) { - const monitor = monitors.find((monitor) => monitor.id === monitorId); + const monitor = monitors.find((monitor) => monitor.id === monitorId) if (!monitor || !state) { - return ( - - Monitor with id {monitorId} not found! - - ) + return Monitor with id {monitorId} not found! } return (
@@ -70,9 +66,14 @@ export default function Home({ )} - + Open-source monitoring and status page powered by{' '} Uptimeflare diff --git a/proxy/api/index.js b/proxy/api/index.js index 2cec8fb..e212e78 100644 --- a/proxy/api/index.js +++ b/proxy/api/index.js @@ -1,7 +1,7 @@ // This is a Node.js implementation of status monitoring const location = '' -const defaultTimeout = 5000 // 5 seconds, a lower default for deployments on platforms like Vercel +const defaultTimeout = 5000 // 5 seconds, a lower default for deployments on platforms like Vercel const express = require('express') const net = require('net') @@ -39,31 +39,30 @@ async function getStatus(monitor) { try { // This is not a real https connection, but we need to add a dummy `https://` to parse the hostname & port // TODO: ipv6 buggy - const parsed = new URL("https://" + monitor.target) + const parsed = new URL('https://' + monitor.target) host = parsed.hostname port = parsed.port - + await new Promise((resolve, reject) => { - const socket = net.createConnection({ host: host, port: Number(port) }) + const socket = net.createConnection({ host: host, port: Number(port) }) - const timer = setTimeout(() => { - socket.destroy() - reject(new Error(`Timeout after ${monitor.timeout || defaultTimeout}ms`)) - }, monitor.timeout || defaultTimeout) - - socket.on('connect', () => { - clearTimeout(timer) - socket.end() - resolve(null) - }) + const timer = setTimeout(() => { + socket.destroy() + reject(new Error(`Timeout after ${monitor.timeout || defaultTimeout}ms`)) + }, monitor.timeout || defaultTimeout) - socket.on('error', (err) => { - clearTimeout(timer) - socket.destroy() - reject(err) - }) - } - ) + socket.on('connect', () => { + clearTimeout(timer) + socket.end() + resolve(null) + }) + + socket.on('error', (err) => { + clearTimeout(timer) + socket.destroy() + reject(err) + }) + }) status.up = true status.err = '' @@ -90,8 +89,9 @@ async function getStatus(monitor) { if (!monitor.expectedCodes.includes(response.status)) { console.log(`${monitor.name} expected ${monitor.expectedCodes}, got ${response.status}`) status.up = false - status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${response.status - }` + status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${ + response.status + }` return status } } else { @@ -109,17 +109,28 @@ async function getStatus(monitor) { // MUST contain responseKeyword if (monitor.responseKeyword && !responseBody.includes(monitor.responseKeyword)) { - console.log(`${monitor.name} expected keyword ${monitor.responseKeyword}, not found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`) + console.log( + `${monitor.name} expected keyword ${ + monitor.responseKeyword + }, not found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}` + ) status.up = false status.err = "HTTP response doesn't contain the configured keyword" return status } // MUST NOT contain responseForbiddenKeyword - if (monitor.responseForbiddenKeyword && responseBody.includes(monitor.responseForbiddenKeyword)) { - console.log(`${monitor.name} forbidden keyword ${monitor.responseForbiddenKeyword}, found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`) + if ( + monitor.responseForbiddenKeyword && + responseBody.includes(monitor.responseForbiddenKeyword) + ) { + console.log( + `${monitor.name} forbidden keyword ${ + monitor.responseForbiddenKeyword + }, found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}` + ) status.up = false - status.err = "HTTP response contains the configured forbidden keyword" + status.err = 'HTTP response contains the configured forbidden keyword' return status } } @@ -142,19 +153,17 @@ async function getStatus(monitor) { return status } -app.use(express.json()); +app.use(express.json()) app.post('/', async (req, res) => { - res.json( - { - location: await getWorkerLocation(), - status: await getStatus(req.body), - } - ) + res.json({ + location: await getWorkerLocation(), + status: await getStatus(req.body), + }) }) app.listen(port, () => { console.log(`App listening on port ${port}`) }) -module.exports = app; +module.exports = app diff --git a/proxy/vercel.json b/proxy/vercel.json index 515253d..66f95d8 100644 --- a/proxy/vercel.json +++ b/proxy/vercel.json @@ -1 +1 @@ -{ "version": 2, "rewrites": [{ "source": "/(.*)", "destination": "/api" }] } \ No newline at end of file +{ "version": 2, "rewrites": [{ "source": "/(.*)", "destination": "/api" }] } diff --git a/uptime.config.ts b/uptime.config.ts index b1bdea8..3d22e6d 100644 --- a/uptime.config.ts +++ b/uptime.config.ts @@ -11,8 +11,8 @@ const pageConfig = { // If not specified, all monitors will be shown in a single list // If specified, monitors will be grouped and ordered, not-listed monitors will be invisble (but still monitored) group: { - "🌐 Public (example group name)": ['foo_monitor', 'bar_monitor', 'more monitor ids...'], - "🔐 Private": ['test_tcp_monitor'], + '🌐 Public (example group name)': ['foo_monitor', 'bar_monitor', 'more monitor ids...'], + '🔐 Private': ['test_tcp_monitor'], }, } @@ -77,12 +77,12 @@ const workerConfig = { notification: { // [Optional] apprise API server URL // if not specified, no notification will be sent - appriseApiServer: "https://apprise.example.com/notify", + appriseApiServer: 'https://apprise.example.com/notify', // [Optional] recipient URL for apprise, refer to https://github.com/caronc/apprise // if not specified, no notification will be sent - recipientUrl: "tgram://bottoken/ChatID", + recipientUrl: 'tgram://bottoken/ChatID', // [Optional] timezone used in notification messages, default to "Etc/GMT" - timeZone: "Asia/Shanghai", + timeZone: 'Asia/Shanghai', // [Optional] grace period in minutes before sending a notification // notification will be sent only if the monitor is down for N continuous checks after the initial failure // if not specified, notification will be sent immediately @@ -101,7 +101,6 @@ const workerConfig = { ) => { // This callback will be called when there's a status change for any monitor // Write any Typescript code here - // This will not follow the grace period settings and will be called immediately when the status changes // You need to handle the grace period manually if you want to implement it }, diff --git a/worker/.prettierrc.yaml b/worker/.prettierrc.yaml index 04fef45..75d2ffa 100644 --- a/worker/.prettierrc.yaml +++ b/worker/.prettierrc.yaml @@ -2,4 +2,4 @@ trailingComma: 'es5' tabWidth: 2 semi: false singleQuote: true -printWidth: 100 \ No newline at end of file +printWidth: 100 diff --git a/worker/src/index.ts b/worker/src/index.ts index 74fc868..d4bb455 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -26,7 +26,9 @@ export default { // @ts-ignore const skipList: string[] = workerConfig.notification?.skipNotificationIds if (skipList && skipList.includes(monitor.id)) { - console.log(`Skipping notification for ${monitor.name} (${monitor.id} in skipNotificationIds)`) + console.log( + `Skipping notification for ${monitor.name} (${monitor.id} in skipNotificationIds)` + ) return } @@ -46,7 +48,9 @@ export default { notification.body ) } else { - console.log(`Apprise API server or recipient URL not set, skipping apprise notification for ${monitor.name}`) + console.log( + `Apprise API server or recipient URL not set, skipping apprise notification for ${monitor.name}` + ) } } @@ -83,11 +87,11 @@ export default { try { console.log('Calling check proxy: ' + monitor.checkProxy) let resp - if (monitor.checkProxy.startsWith("worker://")) { - const doLoc = monitor.checkProxy.replace("worker://", "") + if (monitor.checkProxy.startsWith('worker://')) { + const doLoc = monitor.checkProxy.replace('worker://', '') const doId = env.REMOTE_CHECKER_DO.idFromName(doLoc) const doStub = env.REMOTE_CHECKER_DO.get(doId, { - locationHint: doLoc as DurableObjectLocationHint + locationHint: doLoc as DurableObjectLocationHint, }) resp = await doStub.getLocationAndStatus(monitor) } else { @@ -97,7 +101,7 @@ export default { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(monitor), }) - ).json<{location: string; status: {ping: number; up: boolean; err: string}}>() + ).json<{ location: string; status: { ping: number; up: boolean; err: string } }>() } checkLocation = resp.location status = resp.status @@ -144,17 +148,14 @@ export default { // grace period not set OR ... workerConfig.notification?.gracePeriod === undefined || // only when we have sent a notification for DOWN status, we will send a notification for UP status (within 30 seconds of possible drift) - currentTimeSecond - lastIncident.start[0] >= (workerConfig.notification.gracePeriod + 1) * 60 - 30 + currentTimeSecond - lastIncident.start[0] >= + (workerConfig.notification.gracePeriod + 1) * 60 - 30 ) { - await formatAndNotify( - monitor, - true, - lastIncident.start[0], - currentTimeSecond, - 'OK' - ) + await formatAndNotify(monitor, true, lastIncident.start[0], currentTimeSecond, 'OK') } else { - console.log(`grace period (${workerConfig.notification?.gracePeriod}m) not met, skipping apprise UP notification for ${monitor.name}`) + console.log( + `grace period (${workerConfig.notification?.gracePeriod}m) not met, skipping apprise UP notification for ${monitor.name}` + ) } console.log('Calling config onStatusChange callback...') @@ -195,22 +196,20 @@ export default { try { if ( // monitor status changed AND... - (monitorStatusChanged && ( + (monitorStatusChanged && // grace period not set OR ... - workerConfig.notification?.gracePeriod === undefined || - // have sent a notification for DOWN status - currentTimeSecond - currentIncident.start[0] >= (workerConfig.notification.gracePeriod + 1) * 60 - 30 - )) - || - ( - // grace period is set AND... - workerConfig.notification?.gracePeriod !== undefined && - ( - // grace period is met - currentTimeSecond - currentIncident.start[0] >= workerConfig.notification.gracePeriod * 60 - 30 && - currentTimeSecond - currentIncident.start[0] < workerConfig.notification.gracePeriod * 60 + 30 - ) - )) { + (workerConfig.notification?.gracePeriod === undefined || + // have sent a notification for DOWN status + currentTimeSecond - currentIncident.start[0] >= + (workerConfig.notification.gracePeriod + 1) * 60 - 30)) || + // grace period is set AND... + (workerConfig.notification?.gracePeriod !== undefined && + // grace period is met + currentTimeSecond - currentIncident.start[0] >= + workerConfig.notification.gracePeriod * 60 - 30 && + currentTimeSecond - currentIncident.start[0] < + workerConfig.notification.gracePeriod * 60 + 30) + ) { await formatAndNotify( monitor, false, @@ -219,7 +218,14 @@ export default { status.err ) } else { - console.log(`Grace period (${workerConfig.notification?.gracePeriod}m) not met (currently down for ${currentTimeSecond - currentIncident.start[0]}s, changed ${monitorStatusChanged}), skipping apprise DOWN notification for ${monitor.name}`) + console.log( + `Grace period (${workerConfig.notification + ?.gracePeriod}m) not met (currently down for ${ + currentTimeSecond - currentIncident.start[0] + }s, changed ${monitorStatusChanged}), skipping apprise DOWN notification for ${ + monitor.name + }` + ) } if (monitorStatusChanged) { @@ -274,40 +280,45 @@ export default { // discard old incidents let incidentList = state.incident[monitor.id] - while (incidentList.length > 0 && incidentList[0].end && incidentList[0].end < currentTimeSecond - 90 * 24 * 60 * 60) { + while ( + incidentList.length > 0 && + incidentList[0].end && + incidentList[0].end < currentTimeSecond - 90 * 24 * 60 * 60 + ) { incidentList.shift() } - if (incidentList.length == 0 || ( - incidentList[0].start[0] > currentTimeSecond - 90 * 24 * 60 * 60 && - incidentList[0].error[0] != 'dummy' - )) { + if ( + incidentList.length == 0 || + (incidentList[0].start[0] > currentTimeSecond - 90 * 24 * 60 * 60 && + incidentList[0].error[0] != 'dummy') + ) { // put the dummy incident back - incidentList.unshift( - { - start: [currentTimeSecond - 90 * 24 * 60 * 60], - end: currentTimeSecond - 90 * 24 * 60 * 60, - error: ['dummy'], - } - ) + incidentList.unshift({ + start: [currentTimeSecond - 90 * 24 * 60 * 60], + end: currentTimeSecond - 90 * 24 * 60 * 60, + error: ['dummy'], + }) } state.incident[monitor.id] = incidentList statusChanged ||= monitorStatusChanged } - console.log(`statusChanged: ${statusChanged}, lastUpdate: ${state.lastUpdate}, currentTime: ${currentTimeSecond}`) + console.log( + `statusChanged: ${statusChanged}, lastUpdate: ${state.lastUpdate}, currentTime: ${currentTimeSecond}` + ) // Update state // Allow for a cooldown period before writing to KV if ( statusChanged || - currentTimeSecond - state.lastUpdate >= workerConfig.kvWriteCooldownMinutes * 60 - 10 // Allow for 10 seconds of clock drift + currentTimeSecond - state.lastUpdate >= workerConfig.kvWriteCooldownMinutes * 60 - 10 // Allow for 10 seconds of clock drift ) { - console.log("Updating state...") + console.log('Updating state...') state.lastUpdate = currentTimeSecond await env.UPTIMEFLARE_STATE.put('state', JSON.stringify(state)) } else { - console.log("Skipping state update due to cooldown period.") + console.log('Skipping state update due to cooldown period.') } }, } @@ -317,8 +328,10 @@ export class RemoteChecker extends DurableObject { super(ctx, env) } - async getLocationAndStatus(monitor: MonitorTarget): Promise<{location: string; status: {ping: number; up: boolean; err: string}}> { - const colo = await getWorkerLocation() as string + async getLocationAndStatus( + monitor: MonitorTarget + ): Promise<{ location: string; status: { ping: number; up: boolean; err: string } }> { + const colo = (await getWorkerLocation()) as string console.log(`Running remote checker (DurableObject) at ${colo}...`) const status = await getStatus(monitor) return { diff --git a/worker/src/monitor.ts b/worker/src/monitor.ts index ddeb85c..9c0083e 100644 --- a/worker/src/monitor.ts +++ b/worker/src/monitor.ts @@ -1,5 +1,5 @@ -import { MonitorTarget } from "../../uptime.types"; -import { withTimeout, fetchTimeout } from "./util"; +import { MonitorTarget } from '../../uptime.types' +import { withTimeout, fetchTimeout } from './util' export async function getStatus( monitor: MonitorTarget @@ -15,9 +15,11 @@ export async function getStatus( if (monitor.method === 'TCP_PING') { // TCP port endpoint monitor try { - const connect = await import(/* webpackIgnore: true */ "cloudflare:sockets").then((sockets) => sockets.connect) + const connect = await import(/* webpackIgnore: true */ 'cloudflare:sockets').then( + (sockets) => sockets.connect + ) // This is not a real https connection, but we need to add a dummy `https://` to parse the hostname & port - const parsed = new URL("https://" + monitor.target) + const parsed = new URL('https://' + monitor.target) const socket = connect({ hostname: parsed.hostname, port: Number(parsed.port) }) // Now we have an `opened` promise! @@ -58,8 +60,9 @@ export async function getStatus( if (!monitor.expectedCodes.includes(response.status)) { console.log(`${monitor.name} expected ${monitor.expectedCodes}, got ${response.status}`) status.up = false - status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${response.status - }` + status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${ + response.status + }` return status } } else { @@ -77,17 +80,28 @@ export async function getStatus( // MUST contain responseKeyword if (monitor.responseKeyword && !responseBody.includes(monitor.responseKeyword)) { - console.log(`${monitor.name} expected keyword ${monitor.responseKeyword}, not found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`) + console.log( + `${monitor.name} expected keyword ${ + monitor.responseKeyword + }, not found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}` + ) status.up = false status.err = "HTTP response doesn't contain the configured keyword" return status } // MUST NOT contain responseForbiddenKeyword - if (monitor.responseForbiddenKeyword && responseBody.includes(monitor.responseForbiddenKeyword)) { - console.log(`${monitor.name} forbidden keyword ${monitor.responseForbiddenKeyword}, found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`) + if ( + monitor.responseForbiddenKeyword && + responseBody.includes(monitor.responseForbiddenKeyword) + ) { + console.log( + `${monitor.name} forbidden keyword ${ + monitor.responseForbiddenKeyword + }, found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}` + ) status.up = false - status.err = "HTTP response contains the configured forbidden keyword" + status.err = 'HTTP response contains the configured forbidden keyword' return status } } diff --git a/worker/src/util.ts b/worker/src/util.ts index 8749555..2bf77f2 100644 --- a/worker/src/util.ts +++ b/worker/src/util.ts @@ -43,7 +43,7 @@ function formatStatusChangeNotification( timeZone: timeZone, }) - let downtimeDuration = Math.round((timeNow - timeIncidentStart) / 60); + let downtimeDuration = Math.round((timeNow - timeIncidentStart) / 60) const timeNowFormatted = dateFormatter.format(new Date(timeNow * 1000)) const timeIncidentStartFormatted = dateFormatter.format(new Date(timeIncidentStart * 1000)) @@ -60,7 +60,9 @@ function formatStatusChangeNotification( } else { return { title: `🔴 ${monitor.name} is still down.`, - body: `Service is unavailable since ${timeIncidentStartFormatted} (${downtimeDuration} minutes). Issue: ${reason || 'unspecified'}`, + body: `Service is unavailable since ${timeIncidentStartFormatted} (${downtimeDuration} minutes). Issue: ${ + reason || 'unspecified' + }`, } } } @@ -71,7 +73,16 @@ async function notifyWithApprise( title: string, body: string ) { - console.log('Sending Apprise notification: ' + title + '-' + body + ' to ' + recipientUrl + ' via ' + appriseApiServer) + console.log( + 'Sending Apprise notification: ' + + title + + '-' + + body + + ' to ' + + recipientUrl + + ' via ' + + appriseApiServer + ) try { const resp = await fetchTimeout(appriseApiServer, 5000, { method: 'POST', @@ -83,12 +94,14 @@ async function notifyWithApprise( title, body, type: 'warning', - format: 'text' + format: 'text', }), }) if (!resp.ok) { - console.log('Error calling apprise server, code: ' + resp.status + ', response: ' + await resp.text()) + console.log( + 'Error calling apprise server, code: ' + resp.status + ', response: ' + (await resp.text()) + ) } else { console.log('Apprise notification sent successfully, code: ' + resp.status) } @@ -97,4 +110,10 @@ async function notifyWithApprise( } } -export { getWorkerLocation, fetchTimeout, withTimeout, notifyWithApprise, formatStatusChangeNotification } +export { + getWorkerLocation, + fetchTimeout, + withTimeout, + notifyWithApprise, + formatStatusChangeNotification, +} diff --git a/worker/tsconfig.json b/worker/tsconfig.json index cbea026..39e27fb 100644 --- a/worker/tsconfig.json +++ b/worker/tsconfig.json @@ -1,105 +1,105 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ - /* Projects */ - // "incremental": true, /* Enable incremental compilation */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - "lib": [ - "es2021" - ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, - "jsx": "react" /* Specify what JSX code is generated. */, - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ - // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + /* Language and Environment */ + "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": [ + "es2021" + ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + "jsx": "react" /* Specify what JSX code is generated. */, + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - /* Modules */ - "module": "es2022" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ - "types": [ - "@cloudflare/workers-types" - ] /* Specify type package names to be included without being referenced in a source file. */, - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - "resolveJsonModule": true /* Enable importing .json files */, - // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "es2022" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + "types": [ + "@cloudflare/workers-types" + ] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true /* Enable importing .json files */, + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, - "checkJs": false /* Enable error reporting in type-checked JavaScript files. */, - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": false /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - "noEmit": true /* Disable emitting files from a compilation. */, - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true /* Disable emitting files from a compilation. */, + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, - "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, - // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + /* Interop Constraints */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ - // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ - // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } }