diff --git a/epicshop/package-lock.json b/epicshop/package-lock.json index f8d11b61b..a6364c53c 100644 --- a/epicshop/package-lock.json +++ b/epicshop/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "hasInstallScript": true, "dependencies": { "@epic-web/workshop-app": "^6.90.3", "@epic-web/workshop-utils": "^6.90.3", diff --git a/epicshop/package.json b/epicshop/package.json index c666045fc..521839a96 100644 --- a/epicshop/package.json +++ b/epicshop/package.json @@ -1,6 +1,7 @@ { "type": "module", "scripts": { + "postinstall": "node ./patch-sentry-filters.js", "test:setup": "playwright install chromium --with-deps", "test": "playwright test" }, diff --git a/epicshop/patch-sentry-filters.js b/epicshop/patch-sentry-filters.js new file mode 100644 index 000000000..7e6c5d020 --- /dev/null +++ b/epicshop/patch-sentry-filters.js @@ -0,0 +1,83 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +const clientAssetsDir = path.join( + __dirname, + 'node_modules', + '@epic-web', + 'workshop-app', + 'build', + 'client', + 'assets', +) +const existingPictureInPictureIgnore = + "Failed to execute 'requestPictureInPicture' on 'HTMLVideoElement'" +const pictureInPictureRequestRaceMessage = + 'The video element is processing a Picture-in-Picture request.' + +const sentryBeforeSendPattern = new RegExp( + `ignoreErrors:\\[${JSON.stringify( + existingPictureInPictureIgnore, + )}\\],beforeSend\\(([A-Za-z_$][\\w$]*)\\)\\{`, +) + +const entryClientFilePattern = /^entry\.client-.*\.js$/ + +const entries = await fs.readdir(clientAssetsDir) +const entryClientFiles = entries.filter((entry) => + entryClientFilePattern.test(entry), +) + +if (entryClientFiles.length === 0) { + throw new Error( + `Could not find the workshop app entry.client bundle in ${clientAssetsDir}`, + ) +} + +let patched = false + +for (const entryClientFile of entryClientFiles) { + const entryClientPath = path.join(clientAssetsDir, entryClientFile) + const contents = await fs.readFile(entryClientPath, 'utf8') + + if (contents.includes(pictureInPictureRequestRaceMessage)) { + console.log( + `Sentry Picture-in-Picture request-race filter already present in ${entryClientFile}`, + ) + patched = true + continue + } + + const updated = contents.replace( + sentryBeforeSendPattern, + (match, sentryEventName) => + `${match}if(${sentryEventName}.exception?.values?.some((value)=>value.type==="NotAllowedError"&&value.value===${JSON.stringify( + pictureInPictureRequestRaceMessage, + )}))return null;`, + ) + + if (updated === contents) continue + + await fs.writeFile(entryClientPath, updated) + console.log( + `Added Sentry Picture-in-Picture request-race filter to ${entryClientFile}`, + ) + patched = true +} + +if (!patched) { + throw new Error( + [ + 'Could not patch the workshop app Sentry beforeSend hook.', + 'This filter intentionally drops only Safari DOMExceptions where', + `type is "NotAllowedError" and value is ${JSON.stringify( + pictureInPictureRequestRaceMessage, + )}.`, + 'If @epic-web/workshop-app changed its client Sentry initialization,', + 'update this patch instead of broadening the ignored NotAllowedError cases.', + ].join(' '), + ) +}