mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge 256c2412e0
into 7d84185d0e
This commit is contained in:
commit
cb740ee5f4
6 changed files with 192 additions and 12 deletions
|
@ -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
|
├── components # Higher order visual components and React components
|
||||||
├── constants # Common constant values
|
├── constants # Common constant values
|
||||||
├── controllers # Legacy page views and controllers 🧹 ❌
|
├── controllers # Legacy page views and controllers 🧹 ❌
|
||||||
|
├── crashReporter # Script to send crash report logs to a connected server
|
||||||
├── elements # Basic webcomponents and React equivalents 🧹
|
├── elements # Basic webcomponents and React equivalents 🧹
|
||||||
├── hooks # Custom React hooks
|
├── hooks # Custom React hooks
|
||||||
├── lib # Reusable libraries
|
├── lib # Reusable libraries
|
||||||
|
|
75
src/crashReporter/index.ts
Normal file
75
src/crashReporter/index.ts
Normal 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;
|
87
src/crashReporter/template.ts
Normal file
87
src/crashReporter/template.ts
Normal 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
4
src/global.d.ts
vendored
|
@ -1,6 +1,10 @@
|
||||||
export declare global {
|
export declare global {
|
||||||
import { ApiClient, Events } from 'jellyfin-apiclient';
|
import { ApiClient, Events } from 'jellyfin-apiclient';
|
||||||
|
|
||||||
|
// Globals declared in webpack
|
||||||
|
declare const __USE_SYSTEM_FONTS__: boolean;
|
||||||
|
declare const __WEBPACK_SERVE__: boolean;
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
ApiClient: ApiClient;
|
ApiClient: ApiClient;
|
||||||
Events: Events;
|
Events: Events;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Api, Jellyfin } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { ApiClient } from 'jellyfin-apiclient';
|
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.
|
* @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 => {
|
export const getSDK = (apiClient: ApiClient): Jellyfin => (
|
||||||
return (new Jellyfin({
|
new Jellyfin({
|
||||||
clientInfo: {
|
clientInfo: {
|
||||||
name: apiClient.appName(),
|
name: apiClient.appName(),
|
||||||
version: apiClient.appVersion()
|
version: apiClient.appVersion()
|
||||||
|
@ -16,8 +17,18 @@ export const toApi = (apiClient: ApiClient): Api => {
|
||||||
name: apiClient.deviceName(),
|
name: apiClient.deviceName(),
|
||||||
id: apiClient.deviceId()
|
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()
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -45,7 +45,8 @@ const config = {
|
||||||
target: 'browserslist',
|
target: 'browserslist',
|
||||||
entry: {
|
entry: {
|
||||||
'main.jellyfin': './index.jsx',
|
'main.jellyfin': './index.jsx',
|
||||||
...THEMES_BY_ID
|
...THEMES_BY_ID,
|
||||||
|
'crashReporter': './crashReporter/index.ts'
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
@ -74,7 +75,8 @@ const config = {
|
||||||
hash: true,
|
hash: true,
|
||||||
chunks: [
|
chunks: [
|
||||||
'main.jellyfin',
|
'main.jellyfin',
|
||||||
'serviceworker'
|
'serviceworker',
|
||||||
|
'crashReporter'
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue