import { Env } from '.' import { IncidentRecord, LatencyRecord, MonitorState, MonitorStateCompacted } from '../../types/config' export async function getFromStore(env: Env, key: string): Promise { const stmt = env.UPTIMEFLARE_D1.prepare('SELECT value FROM uptimeflare WHERE key = ?') const result = await stmt.bind(key).first<{ value: string }>() return result?.value || null } export async function setToStore(env: Env, key: string, value: string): Promise { const stmt = env.UPTIMEFLARE_D1.prepare( 'INSERT INTO uptimeflare (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value;' ) await stmt.bind(key, value).run() } export class CompactedMonitorStateWrapper { data: MonitorStateCompacted constructor(compactedStateStr: string | null) { if (!compactedStateStr) { // Initialize empty state this.data = { lastUpdate: 0, overallUp: 0, overallDown: 0, incident: {}, latency: {}, } return } this.data = JSON.parse(compactedStateStr) } getCompactedStateStr(): string { return JSON.stringify(this.data) } // Don't use this method at server-side uncompact(): MonitorState { let state: MonitorState = { lastUpdate: this.data.lastUpdate, overallUp: this.data.overallUp, overallDown: this.data.overallDown, incident: {}, latency: {}, } Object.keys(this.data.incident).forEach((monitorId) => { state.incident[monitorId] = [] const incidents = this.data.incident[monitorId] if (incidents.start.length !== incidents.end.length || incidents.start.length !== incidents.error.length) { throw new Error('Inconsistent incident data lengths, please report an issue at https://github.com/lyc8503/UptimeFlare') } for (let i = 0; i < incidents.start.length; i++) { state.incident[monitorId].push({ start: incidents.start[i], end: incidents.end[i], error: incidents.error[i], }) } }) Object.keys(this.data.latency).forEach((monitorId) => { state.latency[monitorId] = [] const latencies = this.data.latency[monitorId] const locUncompacted: string[] = [] latencies.loc.c.forEach((count, index) => { for (let i = 0; i < count; i++) { locUncompacted.push(latencies.loc.v[index]) } }) // @ts-expect-error const timeArr = new Uint32Array(Uint8Array.fromHex(latencies.time).buffer) // @ts-expect-error const pingArr = new Uint16Array(Uint8Array.fromHex(latencies.ping).buffer) if (timeArr.length !== pingArr.length || timeArr.length !== locUncompacted.length) { throw new Error('Inconsistent latency data lengths, please report an issue at https://github.com/lyc8503/UptimeFlare.') } for (let i = 0; i < timeArr.length; i++) { state.latency[monitorId].push({ time: timeArr[i], ping: pingArr[i], loc: locUncompacted[i], }) } }) return state } incidentLen(monitorId: string): number { const incidents = this.data.incident[monitorId] if (!incidents) return 0 return incidents.start.length } getIncident(monitorId: string, index: number): IncidentRecord { const incidents = this.data.incident[monitorId] if (!incidents || index < 0 || index >= incidents.start.length) { throw new Error('Index out of bounds or monitor not found') } return { start: incidents.start[index], end: incidents.end[index], error: incidents.error[index], } } setIncident(monitorId: string, index: number, incident: IncidentRecord) { const incidents = this.data.incident[monitorId] if (!incidents || index < 0 || index >= incidents.start.length) { throw new Error('Index out of bounds or monitor not found') } incidents.start[index] = incident.start incidents.end[index] = incident.end incidents.error[index] = incident.error } appendIncident(monitorId: string, incident: IncidentRecord) { let incidents = this.data.incident[monitorId] if (!incidents) { // Initialize incident arrays this.data.incident[monitorId] = { start: [], end: [], error: [], } incidents = this.data.incident[monitorId] } incidents.start.push(incident.start) incidents.end.push(incident.end) incidents.error.push(incident.error) } shiftIncident(monitorId: string) { const incidents = this.data.incident[monitorId] incidents.start.shift() incidents.end.shift() incidents.error.shift() } unshiftIncident(monitorId: string, incident: IncidentRecord) { const incidents = this.data.incident[monitorId] incidents.start.unshift(incident.start) incidents.end.unshift(incident.end) incidents.error.unshift(incident.error) } latencyLen(monitorId: string): number { const latencies = this.data.latency[monitorId] if (!latencies) return 0 return latencies.ping.length / 4 // Uint16Array, 4 characters per entry in hex } appendLatency(monitorId: string, record: LatencyRecord) { let latencies = this.data.latency[monitorId] if (!latencies) { // Initialize latency arrays this.data.latency[monitorId] = { time: '', ping: '', loc: { c: [], v: [], }, } latencies = this.data.latency[monitorId] } // @ts-expect-error latencies.time += new Uint8Array(new Uint32Array([record.time]).buffer).toHex() // @ts-expect-error latencies.ping += new Uint8Array(new Uint16Array([record.ping]).buffer).toHex() if (latencies.loc.v[latencies.loc.v.length - 1] !== record.loc) { latencies.loc.c.push(1) latencies.loc.v.push(record.loc) } else { latencies.loc.c[latencies.loc.c.length - 1] += 1 } } getFirstLatency(monitorId: string): LatencyRecord { let latencies = this.data.latency[monitorId] return { // @ts-expect-error time: new Uint32Array(Uint8Array.fromHex(latencies.time.slice(0, 8)).buffer)[0], // @ts-expect-error ping: new Uint16Array(Uint8Array.fromHex(latencies.ping.slice(0, 4)).buffer)[0], loc: latencies.loc.v[0], } } getLastLatency(monitorId: string): LatencyRecord { let latencies = this.data.latency[monitorId] return { // @ts-expect-error time: new Uint32Array(Uint8Array.fromHex(latencies.time.slice(-8)).buffer)[0], // @ts-expect-error ping: new Uint16Array(Uint8Array.fromHex(latencies.ping.slice(-4)).buffer)[0], loc: latencies.loc.v[latencies.loc.v.length - 1], } } unshiftLatency(monitorId: string) { let latencies = this.data.latency[monitorId] latencies.time = latencies.time.slice(8) latencies.ping = latencies.ping.slice(4) latencies.loc.c[0] -= 1 if (latencies.loc.c[0] === 0) { latencies.loc.c.shift() latencies.loc.v.shift() } } }