feat: Add hardened error handling and retries (#2451)
- Closes #1830 - Closes #2099
This commit is contained in:
committed by
dermotduffy
parent
4bc787e2b7
commit
47bcce93d3
@@ -1,5 +1,5 @@
|
||||
import pkg from '../../package.json';
|
||||
import { ProblemPresence } from '../card-controller/problems/types';
|
||||
import { IssueKey, IssuePresence } from '../card-controller/issues/types';
|
||||
import { RawAdvancedCameraCardConfig } from '../config/types';
|
||||
import { getIntegrationManifest } from '../ha/integration';
|
||||
import { IntegrationManifest } from '../ha/integration/types';
|
||||
@@ -47,7 +47,7 @@ interface Diagnostics {
|
||||
|
||||
ha_version?: string;
|
||||
config?: RawAdvancedCameraCardConfig;
|
||||
problems?: ProblemPresence;
|
||||
issues?: IssueKey[];
|
||||
|
||||
custom_integrations: {
|
||||
frigate: IntegrationDiagnostics & {
|
||||
@@ -82,7 +82,7 @@ export const getDiagnostics = async (
|
||||
hass?: HomeAssistant,
|
||||
deviceRegistryManager?: DeviceRegistryManager,
|
||||
rawConfig?: RawAdvancedCameraCardConfig,
|
||||
problems?: ProblemPresence,
|
||||
issues?: IssuePresence,
|
||||
): Promise<Diagnostics> => {
|
||||
// Get the Frigate devices in order to extract the Frigate integration and
|
||||
// server version numbers.
|
||||
@@ -124,7 +124,7 @@ export const getDiagnostics = async (
|
||||
},
|
||||
hass_web_proxy: await getIntegrationDiagnostics(HASS_WEB_PROXY_DOMAIN, hass),
|
||||
},
|
||||
...(problems && { problems }),
|
||||
issues: issues ? [...issues.keys()] : [],
|
||||
...(rawConfig && { config: rawConfig }),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { AdvancedCameraCardError } from '../types.js';
|
||||
|
||||
// Narrows an unknown error to its structured object `context` — non-null and
|
||||
// of object type — or null if the error is not an AdvancedCameraCardError or
|
||||
// has no usable context. Consolidates the instanceof + typeof + null-guard
|
||||
// dance that notification builders and error handlers would otherwise repeat.
|
||||
export const getContextFromError = (error: unknown): object | null =>
|
||||
error instanceof AdvancedCameraCardError &&
|
||||
typeof error.context === 'object' &&
|
||||
error.context !== null
|
||||
? error.context
|
||||
: null;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { allPromises } from '../basic';
|
||||
|
||||
type InitializationCallback = () => Promise<boolean>;
|
||||
type InitializationCallback = () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Manages initialization state & calling initializers. There is no guarantee
|
||||
@@ -12,30 +12,24 @@ export class Initializer {
|
||||
|
||||
public async initializeMultipleIfNecessary(
|
||||
aspects: Record<string, InitializationCallback>,
|
||||
): Promise<boolean> {
|
||||
const results = await allPromises(
|
||||
): Promise<void> {
|
||||
await allPromises(
|
||||
Object.entries(aspects),
|
||||
async ([aspect, options]) => await this.initializeIfNecessary(aspect, options),
|
||||
);
|
||||
return results.every(Boolean);
|
||||
}
|
||||
|
||||
public async initializeIfNecessary(
|
||||
aspect: string,
|
||||
initializer?: InitializationCallback,
|
||||
): Promise<boolean> {
|
||||
): Promise<void> {
|
||||
if (this._initialized.has(aspect)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (!initializer) {
|
||||
this._initialized.add(aspect);
|
||||
return true;
|
||||
if (initializer) {
|
||||
await initializer();
|
||||
}
|
||||
if (await initializer()) {
|
||||
this._initialized.add(aspect);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
this._initialized.add(aspect);
|
||||
}
|
||||
|
||||
public uninitialize(aspect: string): void {
|
||||
|
||||
+14
-25
@@ -1,9 +1,8 @@
|
||||
import { CameraManager } from '../camera-manager/manager';
|
||||
import { PTZAction } from '../config/schema/actions/custom/ptz';
|
||||
import { IMAGE_VIEW_ZOOM_TARGET_SENTINEL } from '../const';
|
||||
import { PTZCapabilities } from '../types';
|
||||
import { View } from '../view/view';
|
||||
import { getStreamCameraID } from './substream';
|
||||
import { getViewTargetID } from '../view/target-id';
|
||||
|
||||
export type PTZType = 'digital' | 'ptz';
|
||||
interface PTZTarget {
|
||||
@@ -18,39 +17,29 @@ export const getPTZTarget = (
|
||||
cameraManager?: CameraManager;
|
||||
},
|
||||
): PTZTarget | null => {
|
||||
const targetID = getViewTargetID(view);
|
||||
if (!targetID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (view.isViewerView()) {
|
||||
const targetID = view.queryResults?.getSelectedResult()?.getID() ?? null;
|
||||
return options?.type === 'ptz' || !targetID
|
||||
? null
|
||||
: {
|
||||
targetID: targetID,
|
||||
type: 'digital',
|
||||
};
|
||||
} else if (view.is('live')) {
|
||||
const substreamAwareCameraID = getStreamCameraID(view);
|
||||
if (!substreamAwareCameraID) {
|
||||
return null;
|
||||
}
|
||||
return options?.type === 'ptz' ? null : { targetID, type: 'digital' };
|
||||
}
|
||||
if (view.is('image')) {
|
||||
return { targetID, type: 'digital' };
|
||||
}
|
||||
if (view.is('live')) {
|
||||
let type: PTZType = 'digital';
|
||||
|
||||
if (options?.type !== 'digital' && options?.cameraManager) {
|
||||
if (hasCameraTruePTZ(options.cameraManager, substreamAwareCameraID)) {
|
||||
if (hasCameraTruePTZ(options.cameraManager, targetID)) {
|
||||
type = 'ptz';
|
||||
}
|
||||
if (type !== 'ptz' && options?.type === 'ptz') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
targetID: substreamAwareCameraID,
|
||||
type: type,
|
||||
};
|
||||
} else if (view.is('image')) {
|
||||
return {
|
||||
targetID: IMAGE_VIEW_ZOOM_TARGET_SENTINEL,
|
||||
type: 'digital',
|
||||
};
|
||||
return { targetID, type };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user