(operation: string, err: unknown, sessionId?: string)
| 974 | } |
| 975 | |
| 976 | function errorDetails(operation: string, err: unknown, sessionId?: string): AppNoticeDetail[] { |
| 977 | const network = isDaemonNetworkError(err); |
| 978 | const api = isDaemonApiError(err); |
| 979 | // Daemon errors carry the failure moment + round-trip time captured in the |
| 980 | // HTTP layer; fall back to "now" for client-side errors that have neither. |
| 981 | const timestamp = network || api ? err.timestamp : undefined; |
| 982 | const durationMs = network || api ? err.durationMs : undefined; |
| 983 | |
| 984 | const details: Array<AppNoticeDetail | undefined> = [ |
| 985 | warningDetail('operation', operation), |
| 986 | // Many call sites don't pass a session id; the active session is the best |
| 987 | // guess and is what the user was looking at when the failure happened. |
| 988 | warningDetail('sessionId', sessionId ?? rawState.activeSessionId), |
| 989 | warningDetail('connection', rawState.connection), |
| 990 | warningDetail('timestamp', formatTimestamp(timestamp ?? Date.now())), |
| 991 | ]; |
| 992 | |
| 993 | if (network) { |
| 994 | details.push( |
| 995 | warningDetail('duration', formatDuration(durationMs)), |
| 996 | warningDetail('request', `${err.method} ${err.path}`), |
| 997 | warningDetail('endpoint', err.url), |
| 998 | warningDetail('requestId', err.requestId), |
| 999 | warningDetail('phase', err.phase), |
| 1000 | warningDetail('timeout', `${err.timeoutMs}ms`), |
| 1001 | warningDetail('status', err.status === undefined ? undefined : `${err.status} ${err.statusText ?? ''}`.trim()), |
| 1002 | warningDetail('contentType', err.contentType), |
| 1003 | warningDetail('responsePreview', err.bodyPreview), |
| 1004 | warningDetail('cause', err.cause), |
| 1005 | ); |
| 1006 | } else if (api) { |
| 1007 | details.push( |
| 1008 | warningDetail('duration', formatDuration(durationMs)), |
| 1009 | warningDetail('code', err.code), |
| 1010 | warningDetail('requestId', err.requestId), |
| 1011 | warningDetail('message', err.message), |
| 1012 | warningDetail('details', err.details), |
| 1013 | ); |
| 1014 | } else { |
| 1015 | details.push( |
| 1016 | warningDetail('errorName', errorName(err)), |
| 1017 | warningDetail('message', errorMessage(err) ?? formatDetailValue(err)), |
| 1018 | warningDetail('stack', errorStack(err)), |
| 1019 | ); |
| 1020 | } |
| 1021 | |
| 1022 | return details.filter((detail): detail is AppNoticeDetail => detail !== undefined); |
| 1023 | } |
| 1024 | |
| 1025 | function operationFailureNotice( |
| 1026 | operation: string, |
no test coverage detected