145 lines
3.5 KiB
TypeScript
145 lines
3.5 KiB
TypeScript
|
|
import { io, type Socket } from 'socket.io-client';
|
||
|
|
|
||
|
|
import { isCreditsContent, type CreditsContent } from '../data';
|
||
|
|
|
||
|
|
type NodeProcess = {
|
||
|
|
env?: Partial<Record<string, string>>;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type ToolkitContentResource = 'credits';
|
||
|
|
|
||
|
|
export type ToolkitContentPayload = Readonly<{
|
||
|
|
content: CreditsContent;
|
||
|
|
resource: ToolkitContentResource;
|
||
|
|
revision: string;
|
||
|
|
}>;
|
||
|
|
|
||
|
|
export type ToolkitContentErrorPayload = Readonly<{
|
||
|
|
error: string;
|
||
|
|
resource: ToolkitContentResource;
|
||
|
|
}>;
|
||
|
|
|
||
|
|
type ToolkitContentSuccessResponse = ToolkitContentPayload &
|
||
|
|
Readonly<{
|
||
|
|
ok: true;
|
||
|
|
}>;
|
||
|
|
|
||
|
|
type ToolkitContentErrorResponse = ToolkitContentErrorPayload &
|
||
|
|
Readonly<{
|
||
|
|
ok: false;
|
||
|
|
}>;
|
||
|
|
|
||
|
|
type ToolkitContentResponse =
|
||
|
|
| ToolkitContentSuccessResponse
|
||
|
|
| ToolkitContentErrorResponse;
|
||
|
|
|
||
|
|
const defaultToolkitSocketUrl = 'http://127.0.0.1:5174';
|
||
|
|
const toolkitSocketTimeoutMs = 5000;
|
||
|
|
|
||
|
|
let toolkitSocket: Socket | null = null;
|
||
|
|
|
||
|
|
function getMaybeProcess() {
|
||
|
|
return typeof process === 'undefined' ? undefined : (process as NodeProcess);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getToolkitSocketUrl() {
|
||
|
|
return (
|
||
|
|
getMaybeProcess()?.env?.TOOLKIT_SOCKET_URL ?? defaultToolkitSocketUrl
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getNodeEnvironment() {
|
||
|
|
return getMaybeProcess()?.env?.NODE_ENV;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getToolkitSocket() {
|
||
|
|
if (toolkitSocket == null) {
|
||
|
|
toolkitSocket = io(getToolkitSocketUrl(), {
|
||
|
|
autoConnect: false,
|
||
|
|
reconnection: true,
|
||
|
|
reconnectionAttempts: Number.POSITIVE_INFINITY,
|
||
|
|
reconnectionDelay: 750,
|
||
|
|
transports: ['websocket'],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return toolkitSocket;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isToolkitContentResource(value: unknown): value is ToolkitContentResource {
|
||
|
|
return value === 'credits';
|
||
|
|
}
|
||
|
|
|
||
|
|
function isToolkitContentPayload(value: unknown): value is ToolkitContentPayload {
|
||
|
|
if (value == null || typeof value !== 'object') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const payload = value as Partial<ToolkitContentPayload>;
|
||
|
|
|
||
|
|
return (
|
||
|
|
isToolkitContentResource(payload.resource) &&
|
||
|
|
typeof payload.revision === 'string' &&
|
||
|
|
isCreditsContent(payload.content)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseToolkitContentResponse(
|
||
|
|
response: unknown,
|
||
|
|
): ToolkitContentPayload {
|
||
|
|
if (response == null || typeof response !== 'object') {
|
||
|
|
throw new Error('Toolkit content response was empty.');
|
||
|
|
}
|
||
|
|
|
||
|
|
const contentResponse = response as Partial<ToolkitContentResponse>;
|
||
|
|
|
||
|
|
if (contentResponse.ok === false) {
|
||
|
|
throw new Error(contentResponse.error ?? 'Toolkit content request failed.');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (contentResponse.ok === true && isToolkitContentPayload(contentResponse)) {
|
||
|
|
return contentResponse;
|
||
|
|
}
|
||
|
|
|
||
|
|
throw new Error('Toolkit content response was invalid.');
|
||
|
|
}
|
||
|
|
|
||
|
|
async function emitContentRequest(
|
||
|
|
eventName: 'toolkit:content:read' | 'toolkit:content:save',
|
||
|
|
payload: Readonly<{
|
||
|
|
content?: CreditsContent;
|
||
|
|
resource: ToolkitContentResource;
|
||
|
|
}>,
|
||
|
|
) {
|
||
|
|
const socket = getToolkitSocket();
|
||
|
|
|
||
|
|
if (!socket.connected) {
|
||
|
|
throw new Error('Toolkit socket is not connected.');
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await socket
|
||
|
|
.timeout(toolkitSocketTimeoutMs)
|
||
|
|
.emitWithAck(eventName, payload);
|
||
|
|
|
||
|
|
return parseToolkitContentResponse(response);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function readToolkitContent(resource: ToolkitContentResource) {
|
||
|
|
return emitContentRequest('toolkit:content:read', { resource });
|
||
|
|
}
|
||
|
|
|
||
|
|
export function saveToolkitContent(
|
||
|
|
resource: ToolkitContentResource,
|
||
|
|
content: CreditsContent,
|
||
|
|
) {
|
||
|
|
return emitContentRequest('toolkit:content:save', {
|
||
|
|
content,
|
||
|
|
resource,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export function resetToolkitSocketForTests() {
|
||
|
|
toolkitSocket?.disconnect();
|
||
|
|
toolkitSocket = null;
|
||
|
|
}
|