diff --git a/package.json b/package.json index e6912a3..660f542 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "lru-cache": "^6.0.0", "mysql2": "^2.1.0", "node-fetch": "^2.6.0", + "playwright": "^1.12.3", "react": "^17.0.1", "react-autosuggest": "^10.0.2", "react-dom": "^17.0.1", diff --git a/src/app/App.js b/src/app/App.js index 951f9ed..037e7d8 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -20,6 +20,9 @@ import { loadable } from "./util"; const ConversionPage = loadable(() => import("./ConversionPage")); const HomePage = loadable(() => import("./HomePage")); +const InternalAssetImagePage = loadable(() => + import("./InternalAssetImagePage") +); const ItemSearchPage = loadable(() => import("./ItemSearchPage")); const ItemPage = loadable(() => import("./ItemPage")); const ItemTradesOfferingPage = loadable(() => @@ -178,6 +181,9 @@ function App() { + + + diff --git a/src/app/InternalAssetImagePage.js b/src/app/InternalAssetImagePage.js new file mode 100644 index 0000000..8eaf5ae --- /dev/null +++ b/src/app/InternalAssetImagePage.js @@ -0,0 +1,106 @@ +import React from "react"; +import { Box, Center } from "@chakra-ui/react"; +import { useLocation } from "react-router-dom"; +import * as Sentry from "@sentry/react"; +import OutfitMovieLayer from "./components/OutfitMovieLayer"; + +/** + * We use this in /api/assetImage, to render the asset image! The headless + * browser navigates here, and screenshots the canvas once it loads. + */ +function InternalAssetImagePage() { + return ( + + ( + + Unexpected error: {error.message} + + )} + > + + + + ); +} + +function InternalAssetImagePageContent() { + const location = useLocation(); + const search = new URLSearchParams(location.search); + const libraryUrl = search.get("libraryUrl"); + + const [movieError, setMovieError] = React.useState(null); + + const onMovieError = React.useCallback((error) => { + console.error("Error playing movie:", error); + setMovieError(error); + }, []); + + if (!libraryUrl) { + return ( + + Error: libraryUrl parameter is required + + ); + } + + if (!isNeopetsUrl(libraryUrl)) { + return ( + + Error: libraryUrl must be an HTTPS Neopets URL, but was:{" "} + {JSON.stringify(libraryUrl)} + + ); + } + + if (movieError) { + return ( + + Error playing movie: {movieError.message} + + ); + } + + return ( + + + + ); +} + +function isNeopetsUrl(urlString) { + let url; + try { + url = new URL(urlString); + } catch (e) { + return false; + } + + return url.origin === "https://images.neopets.com"; +} + +function AssetImageErrorMessage({ children }) { + return ( +
+ {children} +
+ ); +} + +export default InternalAssetImagePage; diff --git a/src/app/components/OutfitMovieLayer.js b/src/app/components/OutfitMovieLayer.js index ecb53ab..7c3bd91 100644 --- a/src/app/components/OutfitMovieLayer.js +++ b/src/app/components/OutfitMovieLayer.js @@ -19,6 +19,7 @@ function OutfitMovieLayer({ onLoad = null, onError = null, onLowFps = null, + canvasProps = {}, }) { const [stage, setStage] = React.useState(null); const [library, setLibrary] = React.useState(null); @@ -36,7 +37,7 @@ function OutfitMovieLayer({ const callOnLoadIfNotYetCalled = React.useCallback(() => { setHasCalledOnLoad((alreadyHasCalledOnLoad) => { - if (!alreadyHasCalledOnLoad) { + if (!alreadyHasCalledOnLoad && onLoad) { onLoad(); } return true; @@ -248,20 +249,24 @@ function OutfitMovieLayer({ height: height, gridArea: "single-shared-area", }} + data-is-loaded={movieIsLoaded} + {...canvasProps} /> {/* While the movie is loading, we show our image version as a * placeholder, because it generally loads much faster. * TODO: Show a loading indicator for this partially-loaded state? */} - + {placeholderImageUrl && ( + + )} ); } diff --git a/src/app/util.js b/src/app/util.js index ec401eb..f4caaac 100644 --- a/src/app/util.js +++ b/src/app/util.js @@ -141,14 +141,20 @@ export function safeImageUrl(urlString, { crossOrigin = null } = {}) { return "https://impress-2020.openneo.net/__error__URL-was-not-parseable__"; } - // Rewrite Neopets URLs to their HTTPS equivalents, or to our proxy if we - // need CORS headers. - if (url.origin === "http://images.neopets.com") { + // Rewrite Neopets URLs to their HTTPS equivalents, and additionally to our + // proxy if we need CORS headers. + if ( + url.origin === "http://images.neopets.com" || + url.origin === "https://images.neopets.com" + ) { url.protocol = "https:"; if (crossOrigin) { url.host = "images.neopets-asset-proxy.openneo.net"; } - } else if (url.origin === "http://pets.neopets.com") { + } else if ( + url.origin === "http://pets.neopets.com" || + url.origin === "https://pets.neopets.com" + ) { url.protocol = "https:"; if (crossOrigin) { url.host = "pets.neopets-asset-proxy.openneo.net"; diff --git a/yarn.lock b/yarn.lock index 57298e4..7a0b787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5622,6 +5622,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" + integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== + dependencies: + "@types/node" "*" + "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -6037,6 +6044,13 @@ agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -7993,7 +8007,7 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^6.2.0: +commander@^6.1.0, commander@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== @@ -10083,6 +10097,17 @@ extract-zip@^1.7.0: mkdirp "^0.5.4" yauzl "^2.10.0" +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -11290,6 +11315,14 @@ https-proxy-agent@^3.0.0: agent-base "^4.3.0" debug "^3.1.0" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -12597,6 +12630,11 @@ jpeg-js@^0.4.0: resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.1.tgz#937a3ae911eb6427f151760f8123f04c8bfe6ef7" integrity sha512-jA55yJiB5tCXEddos8JBbvW+IMrqY0y1tjjx9KNVtA+QPmu7ND5j0zkKopClpUTsaETL135uOM2XfcYG4XRjmw== +jpeg-js@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" + integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== + js-base64@^2.5.1: version "2.6.4" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" @@ -14807,6 +14845,26 @@ pkg-up@3.1.0, pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +playwright@^1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.12.3.tgz#113afa2cba10fb56e9a5b307377343e32a155a99" + integrity sha512-eyhHvZV7dMAUltqjQsgJ9CjZM8dznzN1+rcfCI6W6lfQ7IlPvTFGLuKOCcI4ETbjfbxqaS5FKIkb1WDDzq2Nww== + dependencies: + commander "^6.1.0" + debug "^4.1.1" + extract-zip "^2.0.1" + https-proxy-agent "^5.0.0" + jpeg-js "^0.4.2" + mime "^2.4.6" + pngjs "^5.0.0" + progress "^2.0.3" + proper-lockfile "^4.1.1" + proxy-from-env "^1.1.0" + rimraf "^3.0.2" + stack-utils "^2.0.3" + ws "^7.4.6" + yazl "^2.5.1" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -14824,6 +14882,11 @@ pngjs@^4.0.1: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + pnp-webpack-plugin@1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" @@ -15637,7 +15700,7 @@ process@~0.5.1: resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= -progress@^2.0.0: +progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -15684,6 +15747,15 @@ prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +proper-lockfile@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -15706,7 +15778,7 @@ proxy-agent@3: proxy-from-env "^1.0.0" socks-proxy-agent "^4.0.1" -proxy-from-env@^1.0.0: +proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -17510,7 +17582,7 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-utils@^2.0.2: +stack-utils@^2.0.2, stack-utils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== @@ -19380,6 +19452,11 @@ ws@^7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.1.tgz#a333be02696bd0e54cea0434e21dcc8a9ac294bb" integrity sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ== +ws@^7.4.6: + version "7.5.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.1.tgz#44fc000d87edb1d9c53e51fbc69a0ac1f6871d66" + integrity sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow== + ws@~7.4.2: version "7.4.4" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" @@ -19566,6 +19643,13 @@ yauzl@^2.10.0: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" +yazl@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" + yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"