diff --git a/package-lock.json b/package-lock.json index 7fd5c8b51..c2df55291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18353,11 +18353,45 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, + "stack-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "requires": { + "stackframe": "^1.1.1" + } + }, "stackframe": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" }, + "stacktrace-gps": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz", + "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", + "requires": { + "source-map": "0.5.6", + "stackframe": "^1.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + } + } + }, + "stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "requires": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "standard-version": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.3.0.tgz", diff --git a/package.json b/package.json index 449ab200e..b49694687 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "sass-loader": "^10.1.1", "semver": "7.3.5", "sockjs-client": "1.5.1", + "stacktrace-js": "^2.0.2", "style-loader": "2.0.0", "terser-webpack-plugin": "4.2.3", "ts-pnp": "1.2.0", diff --git a/src/components/app/ErrorBoundary.tsx b/src/components/app/ErrorBoundary.tsx index d36c549f7..20f33a5d4 100644 --- a/src/components/app/ErrorBoundary.tsx +++ b/src/components/app/ErrorBoundary.tsx @@ -1,4 +1,5 @@ import React, { ReactNode, Component } from 'react'; +import StackTrace from 'stacktrace-js'; import { apiPostError } from '../../api/apiPostError'; import { redirectToErrorPage } from '../error/errorHandling'; import { Loading } from './Loading'; @@ -21,6 +22,7 @@ export type ErrorBoundaryError = { 'Referer'?: string; }; stack: string; + parsedStack?: string; }; class ErrorBoundary extends Component { @@ -29,6 +31,8 @@ class ErrorBoundary extends Component { window: null }; + prevError: null | Error = null; + componentDidMount() { this.setState({ window: typeof window !== 'undefined' ? window : null @@ -38,6 +42,13 @@ class ErrorBoundary extends Component { componentDidCatch(error, info) { const { window } = this.state; + const isNewError = + !this.prevError || error.toString() !== this.prevError.toString(); + + if (!isNewError) return; + + this.prevError = error; + const errorBoundaryError: ErrorBoundaryError = { name: error.name, message: error.message, @@ -58,9 +69,19 @@ class ErrorBoundary extends Component { }; } - apiPostError(errorBoundaryError, info).finally(() => { - redirectToErrorPage(500); - }); + StackTrace.fromError(error) + .then((stackFrames) => { + errorBoundaryError.parsedStack = stackFrames + .map((sf) => { + return sf.toString(); + }) + .join('\n'); + }) + .finally(() => { + apiPostError(errorBoundaryError, info).finally(() => { + redirectToErrorPage(500); + }); + }); } static getDerivedStateFromError() { diff --git a/src/components/askerInfo/AskerInfo.tsx b/src/components/askerInfo/AskerInfo.tsx index f10dc684b..e31f057bc 100644 --- a/src/components/askerInfo/AskerInfo.tsx +++ b/src/components/askerInfo/AskerInfo.tsx @@ -39,7 +39,7 @@ export const AskerInfo = () => { const isLiveChat = isAnonymousSession(activeSession?.session); const isGroupChat = isGroupChatForSessionItem(activeSession); const sessionListType = getTypeOfLocation(); - const isPeerChat = activeSession.session?.isPeerChat; + const isPeerChat = activeSession?.session?.isPeerChat; if (!activeSession) { return ; diff --git a/src/components/askerInfo/AskerInfoData.tsx b/src/components/askerInfo/AskerInfoData.tsx index b8f5cb9f8..94ea3be92 100644 --- a/src/components/askerInfo/AskerInfoData.tsx +++ b/src/components/askerInfo/AskerInfoData.tsx @@ -27,7 +27,7 @@ export const AskerInfoData = () => { const isLiveChat = isAnonymousSession(activeSession?.session); const consultingType = useConsultingType( - activeSession.session.consultingType + activeSession?.session.consultingType ); const userSessionData = getContact(activeSession).sessionData; @@ -48,7 +48,7 @@ export const AskerInfoData = () => { {consultingType.titles.default}

- {activeSession.session.consultingType === 0 && !isLiveChat && ( + {activeSession?.session.consultingType === 0 && !isLiveChat && (

{translate('userProfile.data.postcode')} diff --git a/src/components/askerInfo/AskerInfoMonitoring.tsx b/src/components/askerInfo/AskerInfoMonitoring.tsx index 941878fe8..3d27163b7 100644 --- a/src/components/askerInfo/AskerInfoMonitoring.tsx +++ b/src/components/askerInfo/AskerInfoMonitoring.tsx @@ -38,7 +38,7 @@ export const AskerInfoMonitoring = () => { ); useEffect(() => { - apiGetMonitoring(activeSession.session.id) + apiGetMonitoring(activeSession?.session.id) .then((monitoringData) => { setMonitoringData(monitoringData); }) @@ -52,12 +52,12 @@ export const AskerInfoMonitoring = () => { cleanMonitoringData = deleteKeyFromObject(cleanMonitoringData, 'meta'); const resort = - activeSession.session.consultingType === 0 + activeSession?.session.consultingType === 0 ? 'monitoringAddiction' : 'monitoringU25'; const monitoringLink = `${getSessionListPathForLocation()}/${ - activeSession.session.groupId - }/${activeSession.session.id}/userProfile/monitoring${ + activeSession?.session.groupId + }/${activeSession?.session.id}/userProfile/monitoring${ sessionListTab ? `?sessionListTab=${sessionListTab}` : '' }`; diff --git a/src/components/message/MessageItemComponent.tsx b/src/components/message/MessageItemComponent.tsx index 147d32f33..c065959de 100644 --- a/src/components/message/MessageItemComponent.tsx +++ b/src/components/message/MessageItemComponent.tsx @@ -177,7 +177,7 @@ export const MessageItemComponent = ({ const showForwardMessage = () => hasRenderedMessage && activeSession.type !== SESSION_LIST_TYPES.ENQUIRY && - chatItem.feedbackGroupId && + chatItem?.feedbackGroupId && hasUserAuthority(AUTHORITIES.USE_FEEDBACK, userData) && !activeSession?.isFeedbackSession; diff --git a/src/components/session/SessionItemComponent.tsx b/src/components/session/SessionItemComponent.tsx index e452dc148..18ef92e49 100644 --- a/src/components/session/SessionItemComponent.tsx +++ b/src/components/session/SessionItemComponent.tsx @@ -93,8 +93,10 @@ export const SessionItemComponent = (props: SessionItemProps) => { const getSessionListTab = () => `${sessionListTab ? `?sessionListTab=${sessionListTab}` : ''}`; + const { isAnonymousEnquiry } = props; + const resetUnreadCount = () => { - if (!props.isAnonymousEnquiry) { + if (!isAnonymousEnquiry) { setNewMessages(0); initMessageCount = messages?.length; scrollContainerRef.current @@ -104,13 +106,13 @@ export const SessionItemComponent = (props: SessionItemProps) => { }; useEffect(() => { - if (!props.isAnonymousEnquiry) { + if (scrollContainerRef.current) { resetUnreadCount(); } - }, []); // eslint-disable-line + }, [scrollContainerRef]); // eslint-disable-line useEffect(() => { - if (!props.isAnonymousEnquiry && messages) { + if (!isAnonymousEnquiry && messages) { if ( initialScrollCompleted && isMyMessage(messages[messages.length - 1]?.userId) @@ -193,7 +195,7 @@ export const SessionItemComponent = (props: SessionItemProps) => { } setIsRequestInProgress(true); - apiEnquiryAcceptance(sessionId, props.isAnonymousEnquiry) + apiEnquiryAcceptance(sessionId, isAnonymousEnquiry) .then(() => { setOverlayItem(enquirySuccessfullyAcceptedOverlayItem); setCurrentGroupId(sessionGroupId); @@ -284,7 +286,7 @@ export const SessionItemComponent = (props: SessionItemProps) => { const isOnlyEnquiry = typeIsEnquiry(getTypeOfLocation()); const buttonItem: ButtonItem = { - label: props.isAnonymousEnquiry + label: isAnonymousEnquiry ? translate('enquiry.acceptButton.anonymous') : translate('enquiry.acceptButton'), type: BUTTON_TYPES.PRIMARY @@ -351,7 +353,7 @@ export const SessionItemComponent = (props: SessionItemProps) => { legalComponent={props.legalComponent} /> - {!props.isAnonymousEnquiry && ( + {!isAnonymousEnquiry && (

{ {messages && resortData && messages.map((message: MessageItem, index) => ( - <> + { /> {index === messages.length - 1 && enableInitialScroll()} - + ))}
{
)} - {props.isAnonymousEnquiry && ( + {isAnonymousEnquiry && (
{
) : null} - {!props.isAnonymousEnquiry && + {!isAnonymousEnquiry && (!typeIsEnquiry(getTypeOfLocation()) || (typeIsEnquiry(getTypeOfLocation()) && hasUserAuthority(