112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import {
|
|
UmbNotificationOptions,
|
|
UmbNotificationService,
|
|
UmbNotificationDefaultData,
|
|
UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN,
|
|
} from '@umbraco-cms/notification';
|
|
import { ApiError, CancelablePromise, ProblemDetails } from '@umbraco-cms/backend-api';
|
|
import { UmbController, UmbControllerHostInterface } from '@umbraco-cms/controller';
|
|
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
|
|
|
|
export class UmbResourceController extends UmbController {
|
|
#promise: Promise<any>;
|
|
|
|
#notificationService?: UmbNotificationService;
|
|
|
|
constructor(host: UmbControllerHostInterface, promise: Promise<any>, alias?: string) {
|
|
super(host, alias);
|
|
|
|
this.#promise = promise;
|
|
|
|
new UmbContextConsumerController(host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (_instance) => {
|
|
this.#notificationService = _instance;
|
|
});
|
|
}
|
|
|
|
hostConnected(): void {
|
|
// Do nothing
|
|
}
|
|
|
|
hostDisconnected(): void {
|
|
this.cancel();
|
|
}
|
|
|
|
/**
|
|
* Extract the ProblemDetails object from an ApiError.
|
|
*
|
|
* This assumes that all ApiErrors contain a ProblemDetails object in their body.
|
|
*/
|
|
static toProblemDetails(error: unknown): ProblemDetails | undefined {
|
|
if (error instanceof ApiError) {
|
|
const errorDetails = error.body as ProblemDetails;
|
|
return errorDetails;
|
|
} else if (error instanceof Error) {
|
|
return {
|
|
title: error.name,
|
|
detail: error.message,
|
|
};
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Base execute function with a try/catch block and return a tuple with the result and the error.
|
|
*/
|
|
static async tryExecute<T>(promise: Promise<T>): Promise<{ data?: T; error?: ProblemDetails }> {
|
|
try {
|
|
return { data: await promise };
|
|
} catch (e) {
|
|
return { error: UmbResourceController.toProblemDetails(e) };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrap the {execute} function in a try/catch block and return the result.
|
|
* If the executor function throws an error, then show the details in a notification.
|
|
*/
|
|
async tryExecuteAndNotify<T>(options?: UmbNotificationOptions<any>): Promise<{ data?: T; error?: ProblemDetails }> {
|
|
const { data, error } = await UmbResourceController.tryExecute<T>(this.#promise);
|
|
|
|
if (error) {
|
|
const data: UmbNotificationDefaultData = {
|
|
headline: error.title ?? 'Server Error',
|
|
message: error.detail ?? 'Something went wrong',
|
|
};
|
|
|
|
if (this.#notificationService) {
|
|
this.#notificationService?.peek('danger', { data, ...options });
|
|
} else {
|
|
console.group('UmbResourceController');
|
|
console.error(error);
|
|
console.groupEnd();
|
|
}
|
|
}
|
|
|
|
return { data, error };
|
|
}
|
|
|
|
/**
|
|
* Cancel all resources that are currently being executed by this controller if they are cancelable.
|
|
*
|
|
* This works by checking if the promise is a CancelablePromise and if so, it will call the cancel method.
|
|
*
|
|
* This is useful when the controller is being disconnected from the DOM.
|
|
*
|
|
* @see CancelablePromise
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
|
*/
|
|
cancel() {
|
|
if (this.#promise instanceof CancelablePromise) {
|
|
this.#promise.cancel();
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
super.destroy();
|
|
this.cancel();
|
|
}
|
|
}
|