1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: extension error and console logging

This commit is contained in:
Ash Keel 2023-02-02 16:33:57 +01:00
parent 9b6f034803
commit 4a243e60a7
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
8 changed files with 117 additions and 22 deletions

View file

@ -20,7 +20,7 @@ export class Extension extends EventTarget {
private workerStatus = ExtensionStatus.GettingReady;
private workerError?: ErrorEvent;
private workerError?: ErrorEvent | Error;
constructor(
public readonly info: ExtensionEntry,
@ -34,7 +34,7 @@ export class Extension extends EventTarget {
{ type: 'module' },
);
this.worker.onerror = (ev) => {
this.workerError = ev;
this.status = ExtensionStatus.Error;
this.dispatchEvent(new CustomEvent('error', { detail: ev }));
};
this.worker.onmessage = (ev: MessageEvent<ExtensionHostMessage>) =>
@ -57,14 +57,27 @@ export class Extension extends EventTarget {
const msg = ev.data;
switch (msg.kind) {
case 'status-change':
this.workerStatus = msg.status;
this.dispatchEvent(
new CustomEvent('statusChanged', { detail: msg.status }),
);
this.status = msg.status;
break;
case 'error':
if (msg.error instanceof Error) {
this.workerError = msg.error;
} else {
this.workerError = new Error(msg.error.toString());
}
this.status = ExtensionStatus.Error;
break;
case 'log':
this.dispatchEvent(new CustomEvent('log', { detail: msg }));
break;
}
}
private set status(newValue: ExtensionStatus) {
this.workerStatus = newValue;
this.dispatchEvent(new CustomEvent('statusChanged', { detail: newValue }));
}
public get status() {
return this.workerStatus;
}
@ -100,14 +113,11 @@ export class Extension extends EventTarget {
}
stop() {
if (this.workerStatus === ExtensionStatus.Terminated) {
if (this.status === ExtensionStatus.Terminated) {
return;
}
this.worker.terminate();
this.workerStatus = ExtensionStatus.Terminated;
this.dispatchEvent(
new CustomEvent('statusChanged', { detail: this.workerStatus }),
);
this.status = ExtensionStatus.Terminated;
}
dispose() {

View file

@ -22,7 +22,10 @@ export enum ExtensionStatus {
}
export type ExtensionHostCommand = EHParamMessage | EHStartMessage;
export type ExtensionHostMessage = EHStatusChangeMessage;
export type ExtensionHostMessage =
| EHStatusChangeMessage
| EHErrorMessage
| EHLogMessage;
interface EHParamMessage {
kind: 'arguments';
options: ExtensionRunOptions;
@ -36,3 +39,17 @@ interface EHStatusChangeMessage {
kind: 'status-change';
status: ExtensionStatus;
}
interface EHErrorMessage {
kind: 'error';
error: unknown;
}
interface EHLogMessage {
kind: 'log';
level: string;
message: string;
}
export interface LogMessage {
level: string;
message: string;
}

View file

@ -26,12 +26,31 @@ function setStatus(status: ExtensionStatus) {
});
}
function log(level: string) {
// eslint-disable-next-line func-names
return function (...args: { toString(): string }[]) {
const message = args.join(' ');
sendMessage({
kind: 'log',
level,
message,
});
};
}
function start() {
if (!extFn || !kv || extensionStatus !== ExtensionStatus.Ready)
throw new Error('extension not ready');
void extFn(kv).then(() => {
setStatus(ExtensionStatus.Finished);
});
void extFn(kv)
.then(() => {
setStatus(ExtensionStatus.Finished);
})
.catch((error: Error) => {
sendMessage({
kind: 'error',
error,
});
});
setStatus(ExtensionStatus.Running);
}
@ -51,6 +70,12 @@ onmessage = async (ev: MessageEvent<ExtensionHostCommand>) => {
compilerOptions: { module: ts.ModuleKind.CommonJS },
});
// Replace console.* methods with something that logs to UI
console.log = log('info');
console.info = log('info');
console.warn = log('warn');
console.error = log('error');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
extFn = ExtensionFunction.constructor('kv', out.outputText);
setStatus(ExtensionStatus.Ready);

View file

@ -331,7 +331,8 @@
"main-loop-finished": "Active",
"error": "Error encountered",
"terminated": "Stopped"
}
},
"error-alert": "Error details for {{name}}"
}
},
"form-actions": {

View file

@ -6,9 +6,11 @@ import {
ExtensionOptions,
ExtensionRunOptions,
ExtensionStatus,
LogMessage,
} from '~/lib/extensions/types';
import { RootState } from '..';
import { HTTPConfig } from '../api/types';
import loggingReducer from '../logging/reducer';
interface ExtensionsState {
ready: boolean;
@ -127,6 +129,17 @@ export const createExtensionInstance = createAsyncThunk(
payload.dependencies,
payload.runOptions,
);
ext.addEventListener('log', (ev: CustomEvent<LogMessage>) => {
dispatch(
loggingReducer.actions.uiLogEvent({
time: new Date(),
caller: `extensionHost/${payload.entry.name}`,
level: ev.detail.level,
message: ev.detail.message,
data: { extension: payload.entry.name },
}),
);
});
ext.addEventListener(
'statusChanged',
(ev: CustomEvent<ExtensionStatus>) => {

View file

@ -41,10 +41,13 @@ const loggingReducer = createSlice({
loadedLogData(state, { payload }: PayloadAction<main.LogEntry[]>) {
state.messages = payload
.map(processEntry)
.sort((a, b) => b.time.getTime() - a.time.getTime());
.sort((a, b) => a.time.getTime() - b.time.getTime());
},
receivedEvent(state, { payload }: PayloadAction<main.LogEntry>) {
state.messages = [processEntry(payload), ...state.messages];
state.messages.push(processEntry(payload));
},
uiLogEvent(state, { payload }: PayloadAction<ProcessedLogEntry>) {
state.messages.push(payload);
},
clearedEvents(state) {
state.messages = [];

View file

@ -366,7 +366,7 @@ function LogDialog({ initialFilter }: LogDialogProps) {
viewport={{ maxHeight: 'calc(80vh - 100px)' }}
>
<LogEntriesContainer>
{filtered.map((entry) => (
{filtered.reverse().map((entry) => (
<LogItem
key={entry.caller + entry.time.getTime().toString()}
data={entry}

View file

@ -1,5 +1,6 @@
import Editor, { Monaco, useMonaco } from '@monaco-editor/react';
import {
ExclamationTriangleIcon,
InfoCircledIcon,
InputIcon,
PilcrowIcon,
@ -134,6 +135,7 @@ type ExtensionListItemProps = {
enabled: boolean;
entry: ExtensionEntry;
status: ExtensionStatus;
error?: Error | ErrorEvent;
onEdit: () => void;
onRemove: () => void;
onToggleEnable: () => void;
@ -174,9 +176,32 @@ function ExtensionListItem(props: ExtensionListItemProps) {
props.entry.name
)}
{props.enabled ? (
<ExtensionStatusNote color={colorByStatus(props.status)}>
{t(`pages.extensions.statuses.${props.status}`)}
</ExtensionStatusNote>
<>
<ExtensionStatusNote color={colorByStatus(props.status)}>
{t(`pages.extensions.statuses.${props.status}`)}
</ExtensionStatusNote>
{props.error ? (
<Alert>
<AlertTrigger asChild>
<Button variation="danger" size="small">
<ExclamationTriangleIcon />
</Button>
</AlertTrigger>
<AlertContent
variation="danger"
title={t('pages.extensions.error-alert', {
name: props.entry.name,
})}
actionButtonProps={{
variation: 'danger',
}}
showCancel={false}
>
<code>{props.error.toString()}</code>
</AlertContent>
</Alert>
) : null}
</>
) : null}
</ExtensionName>
<ExtensionActions>
@ -274,6 +299,7 @@ function ExtensionList({ onNew, onEdit }: ExtensionListProps) {
entry={e}
enabled={e.options.enabled}
status={extensions.status[e.name]}
error={extensions.running[e.name]?.error}
onEdit={() => onEdit(e.name)}
onRemove={() => {
// Toggle enabled status