1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00
This commit is contained in:
Bill Thornton 2025-03-30 11:01:13 -04:00 committed by GitHub
commit cb740ee5f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 192 additions and 12 deletions

View file

@ -88,6 +88,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user
├── components # Higher order visual components and React components
├── constants # Common constant values
├── controllers # Legacy page views and controllers 🧹 ❌
├── crashReporter # Script to send crash report logs to a connected server
├── elements # Basic webcomponents and React equivalents 🧹
├── hooks # Custom React hooks
├── lib # Reusable libraries

View file

@ -0,0 +1,75 @@
import { getClientLogApi } from '@jellyfin/sdk/lib/utils/api/client-log-api';
import ServerConnections from 'components/ServerConnections';
import { getSDK, toApi } from 'utils/jellyfin-apiclient/compat';
import { buildLogTemplate } from './template';
/** Firefox supports additional properties on the Error object */
interface NonstandardError extends Error {
fileName?: string
columnNumber?: number
lineNumber?: number
}
interface OnUnhandledRejectionHandler {
(this: WindowEventHandlers, ev: PromiseRejectionEvent): void
}
const initialTime = Date.now();
const reporter: OnErrorEventHandler = (
event,
source,
lineno,
colno,
error
) => {
const apiClient = window.ApiClient ?? ServerConnections.currentApiClient();
if (!apiClient) {
console.warn('[crash reporter] no api client; unable to report crash', {
event,
source,
lineno,
colno,
error
});
return;
}
const jellyfin = getSDK(apiClient);
const log = buildLogTemplate(jellyfin, {
initialTime
}, {
event,
source,
lineno,
colno,
error
});
if (__WEBPACK_SERVE__) {
console.error('[crash reporter] crash report not submitted in dev server', log);
return;
}
console.debug('[crash reporter] submitting crash report', log);
getClientLogApi(toApi(apiClient))
.logFile({
body: log
})
.catch(err => {
console.error('[crash reporter] failed to submit crash log', err, log);
});
};
const rejectionReporter: OnUnhandledRejectionHandler = (event) => {
const error = event.reason as NonstandardError;
const message = event.reason as string;
reporter(error.message ?? message, error.fileName, error.lineNumber, error.columnNumber, error);
};
window.onerror = reporter;
window.onunhandledrejection = rejectionReporter;

View file

@ -0,0 +1,87 @@
import type { Jellyfin } from '@jellyfin/sdk';
import browser from 'scripts/browser';
import pkg from '../../package.json';
interface CrashContext {
initialTime: number
}
interface CrashDetails {
event: Event | string,
source?: string,
lineno?: number,
colno?: number,
error?: Error
}
const buildDetailsTemplate = (
message: string,
details: CrashDetails
) => {
const templates: string[] = [];
if (details.error?.name) templates.push(`***Name***: \`${details.error.name}\``);
templates.push(`***Message***: \`${message}\``);
if (details.source) templates.push(`***Source***: \`${details.source}\``);
if (details.lineno) templates.push(`***Line number***: \`${details.lineno}\``);
if (details.colno) templates.push(`***Column number***: \`${details.colno}\``);
return templates.join('\n');
};
export const buildLogTemplate = (
jellyfin: Jellyfin,
context: CrashContext,
details: CrashDetails
) => {
const event = details.event as Event;
const message = (details.event as string) ?? details.error?.message;
const startTime = new Date(context.initialTime);
const crashTime = event?.timeStamp ? new Date(context.initialTime + event.timeStamp) : new Date();
return `---
client: ${pkg.name}
client_version: ${pkg.version}
client_repository: ${pkg.repository}
type: crash_report
format: markdown
---
### Logs
${buildDetailsTemplate(message, details)}
***Stack Trace***:
\`\`\`log
${details.error?.stack}
\`\`\`
### App information
***App name***: \`${jellyfin.clientInfo.name}\`
***App version***: \`${jellyfin.clientInfo.version}\`
***Package name***: \`${pkg.name}\`
***Package config***:
\`\`\`json
${JSON.stringify(pkg)}
\`\`\`
***Build options****:
| Option | Value |
|----------------------|-------------------------|
| __USE_SYSTEM_FONTS__ | ${__USE_SYSTEM_FONTS__} |
| __WEBPACK_SERVE__ | ${__WEBPACK_SERVE__} |
### Device information
***Device name***: \`${jellyfin.deviceInfo.name}\`
***Browser information***:
\`\`\`json
${JSON.stringify(browser)}
\`\`\`
### Crash information
***Start time***: \`${startTime.toISOString()}\`
***Crash time***: \`${crashTime.toISOString()}\`
`;
};

4
src/global.d.ts vendored
View file

@ -1,6 +1,10 @@
export declare global {
import { ApiClient, Events } from 'jellyfin-apiclient';
// Globals declared in webpack
declare const __USE_SYSTEM_FONTS__: boolean;
declare const __WEBPACK_SERVE__: boolean;
interface Window {
ApiClient: ApiClient;
Events: Events;

View file

@ -1,13 +1,14 @@
import { Api, Jellyfin } from '@jellyfin/sdk';
import { ApiClient } from 'jellyfin-apiclient';
import type { Api } from '@jellyfin/sdk';
import { Jellyfin } from '@jellyfin/sdk/lib/jellyfin';
import { type ApiClient } from 'jellyfin-apiclient';
/**
* Returns an SDK Api instance using the same parameters as the provided ApiClient.
* Returns an SDK instance from the configuration of an ApiClient instance.
* @param {ApiClient} apiClient The (legacy) ApiClient.
* @returns {Api} An equivalent SDK Api instance.
* @returns {Jellyfin} An instance of the Jellyfin SDK.
*/
export const toApi = (apiClient: ApiClient): Api => {
return (new Jellyfin({
export const getSDK = (apiClient: ApiClient): Jellyfin => (
new Jellyfin({
clientInfo: {
name: apiClient.appName(),
version: apiClient.appVersion()
@ -16,8 +17,18 @@ export const toApi = (apiClient: ApiClient): Api => {
name: apiClient.deviceName(),
id: apiClient.deviceId()
}
})).createApi(
apiClient.serverAddress(),
apiClient.accessToken()
);
})
);
/**
* Returns an SDK Api instance using the same parameters as the provided ApiClient.
* @param {ApiClient} apiClient The (legacy) ApiClient.
* @returns {Api} An equivalent SDK Api instance.
*/
export const toApi = (apiClient: ApiClient): Api => {
return getSDK(apiClient)
.createApi(
apiClient.serverAddress(),
apiClient.accessToken()
);
};

View file

@ -45,7 +45,8 @@ const config = {
target: 'browserslist',
entry: {
'main.jellyfin': './index.jsx',
...THEMES_BY_ID
...THEMES_BY_ID,
'crashReporter': './crashReporter/index.ts'
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
@ -74,7 +75,8 @@ const config = {
hash: true,
chunks: [
'main.jellyfin',
'serviceworker'
'serviceworker',
'crashReporter'
]
}),
new CopyPlugin({