headplane/server/context/global.ts
Aarnav Tale 6108de52e7
feat: switch to a central singleton handler
This also adds support for Headscale TLS installations
2025-03-17 22:21:16 -04:00

86 lines
2.7 KiB
TypeScript

import type { Configuration } from 'openid-client';
import type { Agent } from 'undici';
import type { WebSocket } from 'ws';
import type { HostInfo } from '~/types';
import type { HeadplaneConfig } from '~server/context/parser';
import type { Logger } from '~server/utils/log';
import type { TimedCache } from '~server/ws/cache';
// This is a stupid workaround for how the Remix import context works
// Even though they run in the same Node instance, they have different
// contexts which means importing this in the app code will not work
// because it will be a different instance of the module.
//
// Instead we can rely on globalThis to share the module between the
// different contexts and use some helper functions to make it easier.
// As a part of this global module, we also define all our singletons
// here in order to avoid polluting the global scope and instead just using
// the `__headplane_server_context` object.
interface ServerContext {
config: HeadplaneConfig;
singletons: ServerSingletons;
}
interface ServerSingletons {
api_agent: Agent;
logger: Logger;
oidc_client: Configuration;
ws_agents: Map<string, WebSocket>;
ws_agent_data: TimedCache<HostInfo>;
ws_fetch_data: (nodeList: string[]) => Promise<void>;
}
// These declarations are separate to prevent the Remix context
// from modifying the globalThis object and causing issues with
// the server context.
declare namespace globalThis {
let __headplane_server_context: {
[K in keyof ServerContext]: ServerContext[K] | null | object;
};
}
// We need to check if the context is already initialized and set a default
// value. This is fine as a side-effect since it's just setting up a framework
// for the object to get modified later.
if (!globalThis.__headplane_server_context) {
globalThis.__headplane_server_context = {
config: null,
singletons: {},
};
}
declare global {
const __headplane_server_context: ServerContext;
}
export function hp_getConfig(): HeadplaneConfig {
return __headplane_server_context.config;
}
export function hp_setConfig(config: HeadplaneConfig): void {
__headplane_server_context.config = config;
}
export function hp_getSingleton<T extends keyof ServerSingletons>(
key: T,
): ServerSingletons[T] {
if (!__headplane_server_context.singletons[key]) {
throw new Error(`Singleton ${key} not initialized`);
}
return __headplane_server_context.singletons[key];
}
export function hp_getSingletonUnsafe<T extends keyof ServerSingletons>(
key: T,
): ServerSingletons[T] | undefined {
return __headplane_server_context.singletons[key];
}
export function hp_setSingleton<
T extends ServerSingletons[keyof ServerSingletons],
>(key: keyof ServerSingletons, value: T): void {
(__headplane_server_context.singletons[key] as T) = value;
}