diff --git a/excalidraw-app/sentry.ts b/excalidraw-app/sentry.ts index d224a266af..26dbde3363 100644 --- a/excalidraw-app/sentry.ts +++ b/excalidraw-app/sentry.ts @@ -23,6 +23,9 @@ Sentry.init({ release: import.meta.env.VITE_APP_GIT_SHA, ignoreErrors: [ "undefined is not an object (evaluating 'window.__pad.performLoop')", // Only happens on Safari, but spams our servers. Doesn't break anything + "InvalidStateError: Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.", // Not much we can do about the IndexedDB closing error + /TypeError: Failed to fetch dynamically imported module: https:\/\/excalidraw\.com\/assets\/index\.esm.*/i, // This is happening when a service worker tries to load an old asset + /TypeError: error loading dynamically imported module: https:\/\/excalidraw\.com\/assets\/index\.esm.*/i, // This is happening when a service worker tries to load an old asset ], integrations: [ new SentryIntegrations.CaptureConsole({ diff --git a/packages/excalidraw/data/restore.ts b/packages/excalidraw/data/restore.ts index 2ee843376a..0c9811559a 100644 --- a/packages/excalidraw/data/restore.ts +++ b/packages/excalidraw/data/restore.ts @@ -206,6 +206,25 @@ const restoreElementWithProperties = < "customData" in extra ? extra.customData : element.customData; } + // NOTE (mtolmacs): This is a temporary check to detect extremely large + // element position or sizing + if ( + element.x < -1e6 || + element.x > 1e6 || + element.y < -1e6 || + element.y > 1e6 || + element.width < -1e6 || + element.width > 1e6 || + element.height < -1e6 || + element.height > 1e6 + ) { + console.error( + `Restore element with properties size or position is too large ${JSON.stringify( + element, + )}`, + ); + } + return { // spread the original element properties to not lose unknown ones // for forward-compatibility @@ -220,6 +239,25 @@ const restoreElementWithProperties = < const restoreElement = ( element: Exclude, ): typeof element | null => { + // NOTE (mtolmacs): This is a temporary check to detect extremely large + // element position or sizing + if ( + element.x < -1e6 || + element.x > 1e6 || + element.y < -1e6 || + element.y > 1e6 || + element.width < -1e6 || + element.width > 1e6 || + element.height < -1e6 || + element.height > 1e6 + ) { + console.error( + `Restore element size or position is too large ${JSON.stringify( + element, + )}`, + ); + } + switch (element.type) { case "text": let fontSize = element.fontSize; diff --git a/packages/excalidraw/element/elbowArrow.ts b/packages/excalidraw/element/elbowArrow.ts index a2762adfa6..4c5f577918 100644 --- a/packages/excalidraw/element/elbowArrow.ts +++ b/packages/excalidraw/element/elbowArrow.ts @@ -1,4 +1,5 @@ import { + clamp, pointDistance, pointFrom, pointScaleFromOrigin, @@ -863,6 +864,8 @@ const handleEndpointDrag = ( ); }; +const MAX_POS = 1e6; + /** * */ @@ -883,6 +886,48 @@ export const updateElbowArrowPoints = ( return { points: updates.points ?? arrow.points }; } + // NOTE (mtolmacs): This is a temporary check to ensure that the incoming elbow + // arrow size is valid. This check will be removed once the issue is identified + if ( + arrow.x < -MAX_POS || + arrow.x > MAX_POS || + arrow.y < -MAX_POS || + arrow.y > MAX_POS || + arrow.x + (updates?.points?.[updates?.points?.length - 1]?.[0] ?? 0) < + -MAX_POS || + arrow.x + (updates?.points?.[updates?.points?.length - 1]?.[0] ?? 0) > + MAX_POS || + arrow.y + (updates?.points?.[updates?.points?.length - 1]?.[1] ?? 0) < + -MAX_POS || + arrow.y + (updates?.points?.[updates?.points?.length - 1]?.[1] ?? 0) > + MAX_POS || + arrow.x + (arrow?.points?.[arrow?.points?.length - 1]?.[0] ?? 0) < + -MAX_POS || + arrow.x + (arrow?.points?.[arrow?.points?.length - 1]?.[0] ?? 0) > + MAX_POS || + arrow.y + (arrow?.points?.[arrow?.points?.length - 1]?.[1] ?? 0) < + -MAX_POS || + arrow.y + (arrow?.points?.[arrow?.points?.length - 1]?.[1] ?? 0) > MAX_POS + ) { + console.error( + `Elbow arrow (or update) is outside reasonable bounds (> 1e6) arrow: ${JSON.stringify( + arrow, + )} updates: ${JSON.stringify(updates)}`, + ); + } + // @ts-ignore See above note + arrow.x = clamp(arrow.x, -MAX_POS, MAX_POS); + // @ts-ignore See above note + arrow.y = clamp(arrow.y, -MAX_POS, MAX_POS); + if (updates.points) { + updates.points = updates.points.map(([x, y]) => + pointFrom( + clamp(x, -MAX_POS, MAX_POS), + clamp(y, -MAX_POS, MAX_POS), + ), + ); + } + if (!import.meta.env.PROD) { invariant( !updates.points || updates.points.length >= 2, @@ -1981,17 +2026,45 @@ const normalizeArrowElementUpdate = ( const offsetX = global[0][0]; const offsetY = global[0][1]; - const points = global.map((p) => + let points = global.map((p) => pointTranslate( p, vectorScale(vectorFromPoint(global[0]), -1), ), ); + // NOTE (mtolmacs): This is a temporary check to see if the normalization + // creates an overly large arrow. This should be removed once we have an answer. + if ( + offsetX < -MAX_POS || + offsetX > MAX_POS || + offsetY < -MAX_POS || + offsetY > MAX_POS || + offsetX + points[points.length - 1][0] < -MAX_POS || + offsetY + points[points.length - 1][0] > MAX_POS || + offsetX + points[points.length - 1][1] < -MAX_POS || + offsetY + points[points.length - 1][1] > MAX_POS + ) { + console.error( + `Elbow arrow normalization is outside reasonable bounds (> 1e6) arrow: ${JSON.stringify( + { + x: offsetX, + y: offsetY, + points, + ...getSizeFromPoints(points), + }, + )}`, + ); + } + + points = points.map(([x, y]) => + pointFrom(clamp(x, -1e6, 1e6), clamp(y, -1e6, 1e6)), + ); + return { points, - x: offsetX, - y: offsetY, + x: clamp(offsetX, -1e6, 1e6), + y: clamp(offsetY, -1e6, 1e6), fixedSegments: (nextFixedSegments?.length ?? 0) > 0 ? nextFixedSegments : null, ...getSizeFromPoints(points), diff --git a/packages/excalidraw/element/newElement.ts b/packages/excalidraw/element/newElement.ts index e1039537c3..ab417356cf 100644 --- a/packages/excalidraw/element/newElement.ts +++ b/packages/excalidraw/element/newElement.ts @@ -99,6 +99,30 @@ const _newElementBase = ( ...rest }: ElementConstructorOpts & Omit, "type">, ) => { + // NOTE (mtolmacs): This is a temporary check to detect extremely large + // element position or sizing + if ( + x < -1e6 || + x > 1e6 || + y < -1e6 || + y > 1e6 || + width < -1e6 || + width > 1e6 || + height < -1e6 || + height > 1e6 + ) { + console.error( + `New element size or position is too large ${JSON.stringify({ + x, + y, + width, + height, + // @ts-ignore + points: rest.points, + })}`, + ); + } + // assign type to guard against excess properties const element: Merge = { id: rest.id || randomId(), diff --git a/packages/excalidraw/element/resizeElements.ts b/packages/excalidraw/element/resizeElements.ts index 4b46757c62..ee9c5b8720 100644 --- a/packages/excalidraw/element/resizeElements.ts +++ b/packages/excalidraw/element/resizeElements.ts @@ -769,12 +769,30 @@ const getResizedOrigin = ( y: y - (newHeight - prevHeight) / 2, }; case "east-side": + // NOTE (mtolmacs): Reverting this for a short period to test if it is + // the cause of the megasized elbow arrows showing up. + if ( + Math.abs( + y + + ((prevWidth - newWidth) / 2) * Math.sin(angle) + + (prevHeight - newHeight) / 2, + ) > 1e6 + ) { + console.error( + `getResizedOrigin() new calculation creates extremely large (> 1e6) y value where the old calculation resulted in ${ + y + + (newHeight - prevHeight) / 2 + + ((prevWidth - newWidth) / 2) * Math.sin(angle) + }`, + ); + } + return { x: x + ((prevWidth - newWidth) / 2) * (Math.cos(angle) + 1), y: y + - ((prevWidth - newWidth) / 2) * Math.sin(angle) + - (prevHeight - newHeight) / 2, + (newHeight - prevHeight) / 2 + + ((prevWidth - newWidth) / 2) * Math.sin(angle), }; case "west-side": return {