Files
triggerdotdev--trigger.dev/apps/webapp/app/v3/handleWebsockets.server.ts
Matt Aitken 4986bfda2e Scheduled tasks (#1036)
* Database schema and migrations for schedules

* Added schedules to the side menu

* The pagination can optionally hide the page numbers for a compactive mode

* Filters for the schedule page

* Added triggerSource (“STANDARD”, “SCHEDULED”) to BackgroundWorkerTask

* Added the ability to disabled a LinkButton

* Started work on the schedule page

* Environment buttons

* The new schedule form styles

* Added a cxouple of extra fields

* Allow a checkbox to have a rich label

* Added cronstrue package to the webapp

* WIP creating tasks using the form

* Improved the form styling

* Creating schedules is working in the UI

* Minor improvements

* Basic schedule table is displaying

* Creating tasks with triggerSource = scheduled. Refactored how task metadata is stored and accessed to be cleaner

* Resource route

* Fixes in the form

* Added a gap between the environments

* WIP on OpenAI generating CRON expressions

* AI generated CRON expressions is working

* Fix for the CRON field being uneditable after an AI generation

* Improvements

* Table padding

* useThrottle now behaves correctly

* Added filtering to the schedules list

* Improved the layout and fixed CRON search

* Fixed pagination for the schedules list. Just use a regular Prisma query

* Page size of 20

* Added links to the schedule rows

* Get rid of the Last run column for now

* Implement triggered scheduled tasks

Also implemented superjson payloads and dev runtime environment “presence” with RuntimeEnvironmentSession

* Move CronPattern and CreateSchedule into a common client-accessible file

* Latest UI changes

* Fix for creating a task schedule with a blank dedup key

* Refactor the human to cron stuff into a separate file and use json_object OpenAI response format

* Fix for trying to use a hook on the server-side…

* A couple of fixes to the new schedule form

* WIP on viewing a scheduled run

* Make the filters all optional

* Use the RunListPresenter from the schedule presenter

* Display a table of runs… the wrong runs but still

* Runs from the schedule

* Deleting schedules from the UI

* Tidied imports and fixed name of options object

* Disabling a schedule

* Editing schedules

* Tidied imports

* Added icons to the task list, needs some design love

* Added a tooltip for CRON pattersn

* Show the last run in the schedules table

* Some tweaks

* Added a placeholder to the CRON AI field

* Improved the trigger source icon

* Dim out disabled schedules

* Scheduled tasks have the correct icon in a run

* Added the task source icon to the test task list

* Added the date field component to storybook

* Style improvements to the date field

* Implement Task Schedule API

* added a medium sized variant to the date field

* Fixed replay run for superjson payload types

Also now linking from the replaying run to the original run (using span links)

Also added a project metrics prometheus endpoint to detect the state of the queues

* WIP on allowing different forms for testing

* If you pass a string to prettyPrintPacket which is json or superjson, safeParse it first

* Test page, deal json and superjson. Started splitting UI for schedules

* WIP on schedule form

* Removed the Label from the DateField

* WIP on test schedule form

* Fix for the runs page showing the wrong message when there are no runs from time filtering

* Removed labels from the DateField

* Fixes for the form

* Test runs are working for schedules

* Fix for nextScheduledTimestamps in triggerScheduledTask

* Implement idempotency key support and fix issue with cancelled runs causing concurrency usage

* Added API documentation for the schedule API

* Remove log

* Removed console log from runs page

* Transform the recent runs test data on the server

* Fix for hydration mismatch

* Current date as the default for the test form

* Recent payloads working

* Delete schedule modal

* Deal with empty strings from the form

* Set the initial value for the scheduled test form

* Add option to print console logs in the dev CLI locally (issue #1014)

* Export queue from the SDK

* Fix for schedules list when you have no schedule tasks

* Blank states improved

* Make task schedules more generic, to support additional schedule generators in the future

* Removed log from maqrs

* Removed “v3/schedules” export from the SDK

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
Co-authored-by: James Ritchie <james@jamesritchie.co.uk>
2024-04-17 11:14:23 +01:00

80 lines
2.3 KiB
TypeScript

import { IncomingMessage } from "node:http";
import { WebSocketServer, type WebSocket } from "ws";
import { authenticateApiKey } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { singleton } from "../utils/singleton";
import { AuthenticatedSocketConnection } from "./authenticatedSocketConnection.server";
import { Gauge } from "prom-client";
import { metricsRegister } from "~/metrics.server";
export const wss = singleton("wss", initalizeWebSocketServer);
let authenticatedConnections: Map<string, AuthenticatedSocketConnection>;
function initalizeWebSocketServer() {
const server = new WebSocketServer({ noServer: true });
server.on("connection", handleWebSocketConnection);
authenticatedConnections = new Map();
new Gauge({
name: "dev_authenticated_connections",
help: "Number of authenticated dev connections",
collect() {
this.set(authenticatedConnections.size);
},
registers: [metricsRegister],
});
return server;
}
async function handleWebSocketConnection(ws: WebSocket, req: IncomingMessage) {
logger.debug("Handle websocket connection", {
ipAddress: req.headers["x-forwarded-for"] || req.socket.remoteAddress,
});
const authHeader = req.headers.authorization;
if (!authHeader || typeof authHeader !== "string") {
ws.close(1008, "Missing Authorization header");
return;
}
const [authType, apiKey] = authHeader.split(" ");
if (authType !== "Bearer" || !apiKey) {
ws.close(1008, "Invalid Authorization header");
return;
}
const authenticationResult = await authenticateApiKey(apiKey);
if (!authenticationResult) {
ws.close(1008, "Invalid API key");
return;
}
const authenticatedEnv = authenticationResult.environment;
const authenticatedConnection = new AuthenticatedSocketConnection(
ws,
authenticatedEnv,
req.headers["x-forwarded-for"] ?? req.socket.remoteAddress ?? "unknown"
);
authenticatedConnections.set(authenticatedConnection.id, authenticatedConnection);
authenticatedConnection.onClose.attachOnce((closeEvent) => {
logger.debug("Websocket closed", {
closeEvent,
authenticatedConnectionId: authenticatedConnection.id,
});
authenticatedConnections.delete(authenticatedConnection.id);
});
await authenticatedConnection.initialize();
}