diff --git a/deploy/playbooks/setup.yml b/deploy/playbooks/setup.yml
index 9279d5b..4c5e977 100644
--- a/deploy/playbooks/setup.yml
+++ b/deploy/playbooks/setup.yml
@@ -160,7 +160,7 @@
# process. They'll be able to manage it without `sudo`, including during
# normal deploys, and run `pm2 monit` from their shell to see status.
become: yes
- command: "pm2 startup systemd {{ ansible_user_id }} --hp /home/{{ ansible_user_id }}"
+ command: "pm2 startup systemd -u {{ ansible_user_id }} --hp /home/{{ ansible_user_id }}"
- name: Create pm2 ecosystem file
copy:
@@ -267,6 +267,62 @@
- libgif-dev
- librsvg2-dev
+ - name: Install Playwright system dependencies
+ # NOTE: I copied the package list from the source list for
+ # `npx playwright install-deps`, which I couldn't get running in
+ # Ansible as root, and besides, I prefer manually managing the
+ # package list over running an npm script as root!
+ # TODO: We're using Puppeteer now, should this list change in some way?
+ become: yes
+ apt:
+ update_cache: yes
+ name:
+ # Tools
+ - xvfb
+ - fonts-noto-color-emoji
+ - ttf-unifont
+ - libfontconfig
+ - libfreetype6
+ - xfonts-cyrillic
+ - xfonts-scalable
+ - fonts-liberation
+ - fonts-ipafont-gothic
+ - fonts-wqy-zenhei
+ - fonts-tlwg-loma-otf
+ - ttf-ubuntu-font-family
+ # Chromium
+ - fonts-liberation
+ - libasound2
+ - libatk-bridge2.0-0
+ - libatk1.0-0
+ - libatspi2.0-0
+ - libcairo2
+ - libcups2
+ - libdbus-1-3
+ - libdrm2
+ - libegl1
+ - libgbm1
+ - libglib2.0-0
+ - libgtk-3-0
+ - libnspr4
+ - libnss3
+ - libpango-1.0-0
+ - libx11-6
+ - libx11-xcb1
+ - libxcb1
+ - libxcomposite1
+ - libxdamage1
+ - libxext6
+ - libxfixes3
+ - libxrandr2
+ - libxshmfence1
+
+ - name: Enable user namespace cloning for Chromium sandboxing
+ become: yes
+ ansible.posix.sysctl:
+ name: kernel.unprivileged_userns_clone
+ value: "1"
+
handlers:
- name: Restart nginx
become: yes
diff --git a/package.json b/package.json
index b0dea43..f8cd806 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"easeljs": "^1.0.2",
"escape-html": "^1.0.3",
"framer-motion": "^4.1.11",
+ "generic-pool": "^3.8.2",
"graphql": "^15.5.0",
"honeycomb-beeline": "^2.7.4",
"immer": "^9.0.6",
@@ -43,7 +44,7 @@
"mysql2": "^2.1.0",
"next": "12.0.2",
"node-fetch": "^2.6.0",
- "playwright-core": "^1.14.0",
+ "puppeteer": "^11.0.0",
"react": "^17.0.1",
"react-autosuggest": "^10.0.2",
"react-dom": "^17.0.1",
@@ -112,7 +113,6 @@
"inquirer": "^7.3.3",
"jest-image-snapshot": "^4.3.0",
"lint-staged": "^10.5.4",
- "playwright": "^1.14.0",
"prettier": "^2.0.5",
"react-is": "^16.13.1",
"ts-node": "^9.1.1",
diff --git a/pages/api/allWakaValues.js b/pages/api/allWakaValues.js
index 1b4dcc6..5894eeb 100644
--- a/pages/api/allWakaValues.js
+++ b/pages/api/allWakaValues.js
@@ -8,127 +8,15 @@ const beeline = require("honeycomb-beeline")({
disableInstrumentationOnLoad: true,
});
-import fetch from "node-fetch";
-
-import connectToDb from "../../src/server/db";
-
async function handle(req, res) {
- const allNcItemNamesAndIdsPromise = loadAllNcItemNamesAndIds();
-
- let itemValuesByIdOrName;
- try {
- itemValuesByIdOrName = await loadWakaValuesByIdOrName();
- } catch (e) {
- console.error(e);
- res.setHeader("Content-Type", "text/plain");
- res.status(500).send("Error loading Waka data from Google Sheets API");
- return;
- }
-
- // Restructure the value data to use IDs as keys, instead of names.
- const allNcItemNamesAndIds = await allNcItemNamesAndIdsPromise;
- const itemValues = {};
- for (const { name, id } of allNcItemNamesAndIds) {
- if (id in itemValuesByIdOrName) {
- itemValues[id] = itemValuesByIdOrName[id];
- } else if (name in itemValuesByIdOrName) {
- itemValues[id] = itemValuesByIdOrName[name];
- }
- }
-
- // Cache for 1 minute, and immediately serve stale data for a day after.
- // This should keep it fast and responsive, and stay well within our API key
- // limits. (This will cause the client to send more requests than necessary,
- // but the CDN cache should generally respond quickly with a small 304 Not
- // Modified, unless the data really did change.)
- res.setHeader(
- "Cache-Control",
- "public, max-age=3600, stale-while-revalidate=86400"
- );
- return res.send(itemValues);
-}
-
-async function loadAllNcItemNamesAndIds() {
- const db = await connectToDb();
-
- const [rows] = await db.query(`
- SELECT items.id, item_translations.name FROM items
- INNER JOIN item_translations ON item_translations.item_id = items.id
- WHERE
- (items.rarity_index IN (0, 500) OR is_manually_nc = 1)
- AND item_translations.locale = "en"
- `);
-
- return rows.map(({ id, name }) => ({ id, name: normalizeItemName(name) }));
-}
-
-/**
- * Load all Waka values from the spreadsheet. Returns an object keyed by ID or
- * name - that is, if the item ID is provided in the sheet, we use that as the
- * key; or if not, we use the name as the key.
- */
-async function loadWakaValuesByIdOrName() {
- if (!process.env["GOOGLE_API_KEY"]) {
- throw new Error(`GOOGLE_API_KEY environment variable must be provided`);
- }
-
- const res = await fetch(
- `https://sheets.googleapis.com/v4/spreadsheets/` +
- `1DRMrniTSZP0sgZK6OAFFYqpmbT6xY_Ve_i480zghOX0/values/NC%20Values` +
- `?fields=values&key=${encodeURIComponent(process.env["GOOGLE_API_KEY"])}`
- );
- const json = await res.json();
-
- if (!res.ok) {
- if (json.error) {
- const { code, status, message } = json.error;
- throw new Error(
- `Google Sheets API returned error ${code} ${status}: ${message}`
- );
- } else {
- throw new Error(
- `Google Sheets API returned unexpected error: ${res.status} ${res.statusText}`
- );
- }
- }
-
- // Get the rows from the JSON response - skipping the first-row headers.
- const rows = json.values.slice(1);
-
- // Reformat the rows as a map from item name to value. We offer the item data
- // as an object with a single field `value` for extensibility, but we omit
- // the spreadsheet columns that we don't use on DTI, like Notes.
- //
- // NOTE: The Sheets API only returns the first non-empty cells of the row.
- // That's why we set `""` as the defaults, in case the value/notes/etc
- // aren't provided.
- const itemValuesByIdOrName = {};
- for (const [
- itemName,
- value = "",
- unusedNotes = "",
- unusedMarks = "",
- itemId = "",
- ] of rows) {
- const normalizedItemName = normalizeItemName(itemName);
- itemValuesByIdOrName[itemId || normalizedItemName] = { value };
- }
-
- return itemValuesByIdOrName;
-}
-
-function normalizeItemName(name) {
- return (
- name
- // Remove all spaces, they're a common source of inconsistency
- .replace(/\s+/g, "")
- // Lower case, because capitalization is another common source
- .toLowerCase()
- // Remove diacritics: https://stackoverflow.com/a/37511463/107415
- // Waka has some stray ones in item names, not sure why!
- .normalize("NFD")
- .replace(/[\u0300-\u036f]/g, "")
- );
+ res.setHeader("Content-Type", "text/plain; charset=utf8");
+ res
+ .status(410)
+ .send(
+ "WakaGuide.com is no longer updating its values, so we no longer " +
+ "serve them from this endpoint. The most recent set of values is " +
+ "archived here: https://docs.google.com/spreadsheets/d/1DRMrniTSZP0sgZK6OAFFYqpmbT6xY_Ve_i480zghOX0"
+ );
}
async function handleWithBeeline(req, res) {
diff --git a/pages/api/assetImage.js b/pages/api/assetImage.js
index ab05e94..6c7d4d9 100644
--- a/pages/api/assetImage.js
+++ b/pages/api/assetImage.js
@@ -22,34 +22,37 @@ const beeline = require("honeycomb-beeline")({
disableInstrumentationOnLoad: true,
});
-// To render the image, we load the /internal/assetImage page in the web app,
-// a simple page specifically designed for this API endpoint!
-const ASSET_IMAGE_PAGE_BASE_URL = process.env.VERCEL_URL
- ? `https://${process.env.VERCEL_URL}/internal/assetImage`
- : process.env.NODE_ENV === "development"
- ? "http://localhost:3000/internal/assetImage"
- : "https://impress-2020.openneo.net/internal/assetImage";
+const puppeteer = require("puppeteer");
+const genericPool = require("generic-pool");
-// TODO: We used to share a browser instamce, but we couldn't get it to reload
-// correctly after accidental closes, so we're just gonna always load a
-// new one now. What are the perf implications of this? Does it slow down
-// response time substantially?
-async function getBrowser() {
- if (process.env["NODE_ENV"] === "production") {
- // In production, we use a special chrome-aws-lambda Chromium.
- const chromium = require("chrome-aws-lambda");
- const playwright = require("playwright-core");
- return await playwright.chromium.launch({
- args: chromium.args,
- executablePath: await chromium.executablePath,
- headless: true,
- });
- } else {
- // In development, we use the standard playwright Chromium.
- const playwright = require("playwright");
- return await playwright.chromium.launch({ headless: true });
- }
-}
+console.info(`Creating new browser instance`);
+const browserPromise = puppeteer.launch({ headless: true });
+
+// We maintain a small pool of browser pages, to manage memory usage. If all
+// the pages are already in use, a request will wait for one of them to become
+// available.
+//
+// NOTE: 4 pages is about where our 1-cpu prod environment maxes out. We might
+// want to upgrade to the 2-cpu box as we add more pressure though, and
+// then maybe we can afford more pages in the pool?
+
+const PAGE_POOL = genericPool.createPool(
+ {
+ create: async () => {
+ console.debug(`Creating a browser page`);
+ const browser = await browserPromise;
+ return await browser.newPage();
+ },
+ destroy: (page) => {
+ console.debug(`Closing a browser page`);
+ page.close();
+ },
+ validate: (page) => page.browser().isConnected(),
+ },
+ { min: 4, max: 4, testOnBorrow: true, acquireTimeoutMillis: 15000 }
+);
+PAGE_POOL.on("factoryCreateError", (error) => console.error(error));
+PAGE_POOL.on("factoryDestroyError", (error) => console.error(error));
async function handle(req, res) {
const { libraryUrl, size } = req.query;
@@ -73,6 +76,9 @@ async function handle(req, res) {
imageBuffer = await loadAndScreenshotImage(libraryUrl, size);
} catch (e) {
console.error(e);
+ if (e.name === "TimeoutError") {
+ return reject(res, `Server under heavy load: ${e.message}`, 503);
+ }
return reject(res, `Could not load image: ${e.message}`, 500);
}
@@ -86,18 +92,24 @@ async function handle(req, res) {
}
async function loadAndScreenshotImage(libraryUrl, size) {
- const assetImagePageUrl = new URL(ASSET_IMAGE_PAGE_BASE_URL);
+ // To render the image, we load the /internal/assetImage page in the web app,
+ // a simple page specifically designed for this API endpoint!
+ //
+ // NOTE: If we deploy to a host where localhost:3000 won't work, make this
+ // configurable with an env var, e.g. process.env.LOCAL_APP_HOST
+ const assetImagePageUrl = new URL(
+ "http://localhost:3000/internal/assetImage"
+ );
assetImagePageUrl.search = new URLSearchParams({
libraryUrl,
size,
}).toString();
- console.debug("Opening browser page");
- const browser = await getBrowser();
- const page = await browser.newPage();
- console.debug("Page opened, navigating to: " + assetImagePageUrl.toString());
+ console.debug("Getting browser page");
+ const page = await PAGE_POOL.acquire();
try {
+ console.debug("Page ready, navigating to: " + assetImagePageUrl.toString());
await page.goto(assetImagePageUrl.toString());
console.debug("Page loaded, awaiting image");
@@ -106,10 +118,20 @@ async function loadAndScreenshotImage(libraryUrl, size) {
// present, or raising the error if present.
const imageBufferPromise = screenshotImageFromPage(page);
const errorMessagePromise = readErrorMessageFromPage(page);
- const firstResultFromPage = await Promise.any([
- imageBufferPromise.then((imageBuffer) => ({ imageBuffer })),
- errorMessagePromise.then((errorMessage) => ({ errorMessage })),
- ]);
+ let firstResultFromPage;
+ try {
+ firstResultFromPage = await Promise.any([
+ imageBufferPromise.then((imageBuffer) => ({ imageBuffer })),
+ errorMessagePromise.then((errorMessage) => ({ errorMessage })),
+ ]);
+ } catch (error) {
+ if (error.errors) {
+ // If both promises failed, show all error messages.
+ throw new Error(error.errors.map((e) => e.message).join(", "));
+ } else {
+ throw error;
+ }
+ }
if (firstResultFromPage.errorMessage) {
throw new Error(firstResultFromPage.errorMessage);
@@ -122,18 +144,9 @@ async function loadAndScreenshotImage(libraryUrl, size) {
);
}
} finally {
- // Tear down our resources when we're done! If it fails, log the error, but
- // don't block the success of the image.
- try {
- await page.close();
- } catch (e) {
- console.warn("Error closing page after image finished", e);
- }
- try {
- await browser.close();
- } catch (e) {
- console.warn("Error closing browser after image finished", e);
- }
+ // To avoid memory leaks, we destroy the page when we're done with it.
+ // The pool will replace it with a fresh one!
+ PAGE_POOL.destroy(page);
}
}
@@ -173,7 +186,7 @@ function isNeopetsUrl(urlString) {
}
function reject(res, message, status = 400) {
- res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Content-Type", "text/plain; charset=utf8");
return res.status(status).send(message);
}
diff --git a/pages/api/outfitImage.js b/pages/api/outfitImage.js
index 4904757..d1386b1 100644
--- a/pages/api/outfitImage.js
+++ b/pages/api/outfitImage.js
@@ -35,11 +35,12 @@ const beeline = require("honeycomb-beeline")({
sampleRate: 10,
});
-import fetch from "node-fetch";
import gql from "graphql-tag";
-import { print as graphqlPrint } from "graphql/language/printer";
+import { ApolloServer } from "apollo-server";
+import { createTestClient } from "apollo-server-testing";
import connectToDb from "../../src/server/db";
+import { config as graphqlConfig } from "../../src/server";
import { renderOutfitImage } from "../../src/server/outfit-images";
import getVisibleLayers, {
petAppearanceFragmentForGetVisibleLayers,
@@ -143,50 +144,35 @@ async function handle(req, res) {
return res.send(image);
}
-const GRAPHQL_ENDPOINT = process.env.VERCEL_URL
- ? `https://${process.env.VERCEL_URL}/api/graphql`
- : process.env.NODE_ENV === "development"
- ? "http://localhost:3000/api/graphql"
- : "https://impress-2020.openneo.net/api/graphql";
-
-// NOTE: Unlike in-app views, we only load PNGs here. We expect this to
-// generally perform better, and be pretty reliable now that TNT is
-// generating canonical PNGs for every layer!
-const GRAPHQL_QUERY = gql`
- query ApiOutfitImage($outfitId: ID!, $size: LayerImageSize) {
- outfit(id: $outfitId) {
- petAppearance {
- layers {
- id
- imageUrl(size: $size)
- }
- ...PetAppearanceForGetVisibleLayers
- }
- itemAppearances {
- layers {
- id
- imageUrl(size: $size)
- }
- ...ItemAppearanceForGetVisibleLayers
- }
- }
- }
- ${petAppearanceFragmentForGetVisibleLayers}
- ${itemAppearanceFragmentForGetVisibleLayers}
-`;
-const GRAPHQL_QUERY_STRING = graphqlPrint(GRAPHQL_QUERY);
+// Check out this scrappy way of making a query against server code ^_^`
+const graphqlClient = createTestClient(new ApolloServer(graphqlConfig));
async function loadLayerUrlsForSavedOutfit(outfitId, size) {
- const { errors, data } = await fetch(GRAPHQL_ENDPOINT, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: GRAPHQL_QUERY_STRING,
- variables: { outfitId, size: `SIZE_${size}` },
- }),
- }).then((res) => res.json());
+ const { errors, data } = await graphqlClient.query({
+ query: gql`
+ query ApiOutfitImage($outfitId: ID!, $size: LayerImageSize) {
+ outfit(id: $outfitId) {
+ petAppearance {
+ layers {
+ id
+ imageUrl(size: $size)
+ }
+ ...PetAppearanceForGetVisibleLayers
+ }
+ itemAppearances {
+ layers {
+ id
+ imageUrl(size: $size)
+ }
+ ...ItemAppearanceForGetVisibleLayers
+ }
+ }
+ }
+ ${petAppearanceFragmentForGetVisibleLayers}
+ ${itemAppearanceFragmentForGetVisibleLayers}
+ `,
+ variables: { outfitId, size: `SIZE_${size}` },
+ });
if (errors && errors.length > 0) {
throw new Error(
@@ -225,7 +211,7 @@ async function loadUpdatedAtForSavedOutfit(outfitId) {
}
function reject(res, message, status = 400) {
- res.setHeader("Content-Type", "text/plain");
+ res.setHeader("Content-Type", "text/plain; charset=utf8");
return res.status(status).send(message);
}
diff --git a/pages/api/outfitPageSSR.js b/pages/api/outfitPageSSR.js
deleted file mode 100644
index d34bd74..0000000
--- a/pages/api/outfitPageSSR.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * /api/outfitPageSSR also serves the initial request for /outfits/:id, to
- * add title and meta tags. This primarily for sharing, like on Discord or
- * Twitter or Facebook!
- *
- * The route is configured in vercel.json, at the project root.
- *
- * To be honest, we probably should have built Impress 2020 on Next.js, and
- * then we'd be getting realistic server-side rendering across practically the
- * whole app very cheaply. But this is a good hack for what we have!
- *
- * TODO: We could add the basic outfit page layout and image preview, to use
- * SSR to decrease time-to-first-content for the end-user, too…
- */
-const beeline = require("honeycomb-beeline")({
- writeKey: process.env["HONEYCOMB_WRITE_KEY"],
- dataset:
- process.env["NODE_ENV"] === "production"
- ? "Dress to Impress (2020)"
- : "Dress to Impress (2020, dev)",
- serviceName: "impress-2020-gql-server",
- disableInstrumentationOnLoad: true,
-});
-
-import escapeHtml from "escape-html";
-import fetch from "node-fetch";
-
-import connectToDb from "../../src/server/db";
-import { normalizeRow } from "../../src/server/util";
-
-async function handle(req, res) {
- // Load index.html as our initial page content. If this fails, it probably
- // means something is misconfigured in a big way; we don't have a great way
- // to recover, and we'll just show an error message.
- let initialHtml;
- try {
- initialHtml = await loadIndexPageHtml();
- } catch (e) {
- console.error("Error loading index.html:", e);
- return reject(res, "Sorry, there was an error loading this outfit page!");
- }
-
- // Load the given outfit by ID. If this fails, it's possible that it's just a
- // problem with the SSR, and the client will be able to handle it better
- // anyway, so just show the standard index.html and let the app load
- // normally, as if there was no error. (We'll just log it.)
- let outfit;
- try {
- outfit = await loadOutfitData(req.query.id);
- } catch (e) {
- console.error("Error loading outfit data:", e);
- return sendHtml(res, initialHtml, 200);
- }
-
- // Similarly, if the outfit isn't found, we just show index.html - but with a
- // 404 and a gentler log message.
- if (outfit == null) {
- console.info(`Outfit not found: ${req.query.id}`);
- return sendHtml(res, initialHtml, 404);
- }
-
- const outfitName = outfit.name || "Untitled outfit";
-
- // Okay, now let's rewrite the HTML to include some outfit data!
- //
- // WARNING!!!
- // Be sure to always use `escapeHtml` when inserting user data!!
- // WARNING!!!
- //
- let html = initialHtml;
-
- // Add the outfit name to the title.
- html = html.replace(
- /
(.*)<\/title>/,
- `${escapeHtml(outfitName)} | Dress to Impress`
- );
-
- // Add sharing meta tags just before the tag.
- const updatedAtTimestamp = Math.floor(
- new Date(outfit.updatedAt).getTime() / 1000
- );
- const outfitUrl =
- `https://impress-2020.openneo.net/outfits` +
- `/${encodeURIComponent(outfit.id)}`;
- const imageUrl =
- `https://impress-outfit-images.openneo.net/outfits` +
- `/${encodeURIComponent(outfit.id)}` +
- `/v/${encodeURIComponent(updatedAtTimestamp)}` +
- `/600.png`;
- const metaTags = `
-
-
-
-
-
-
- `;
- html = html.replace(/<\/head>/, `${metaTags}`);
-
- console.info(`Successfully SSR'd outfit ${outfit.id}`);
-
- return sendHtml(res, html);
-}
-
-async function loadOutfitData(id) {
- const db = await connectToDb();
- const [rows] = await db.query(`SELECT * FROM outfits WHERE id = ?;`, [id]);
- if (rows.length === 0) {
- return null;
- }
-
- return normalizeRow(rows[0]);
-}
-
-let cachedIndexPageHtml = null;
-async function loadIndexPageHtml() {
- if (cachedIndexPageHtml == null) {
- // Request the same built copy of index.html that we're already serving at
- // our homepage.
- const homepageUrl = process.env.VERCEL_URL
- ? `https://${process.env.VERCEL_URL}/`
- : process.env.NODE_ENV === "development"
- ? "http://localhost:3000/"
- : "https://impress-2020.openneo.net/";
- const liveIndexPageHtml = await fetch(homepageUrl).then((res) =>
- res.text()
- );
- cachedIndexPageHtml = liveIndexPageHtml;
- }
-
- return cachedIndexPageHtml;
-}
-
-function reject(res, message, status = 400) {
- res.setHeader("Content-Type", "text/plain");
- return res.status(status).send(message);
-}
-
-function sendHtml(res, html, status = 200) {
- res.setHeader("Content-Type", "text/html");
- return res.status(status).send(html);
-}
-
-async function handleWithBeeline(req, res) {
- beeline.withTrace(
- { name: "api/outfitPageSSR", operation_name: "api/outfitPageSSR" },
- () => handle(req, res)
- );
-}
-
-export default handleWithBeeline;
diff --git a/src/app/HomePage.js b/src/app/HomePage.js
index 2c524cc..42a7e8b 100644
--- a/src/app/HomePage.js
+++ b/src/app/HomePage.js
@@ -2,6 +2,7 @@ import React from "react";
import { ClassNames } from "@emotion/react";
import gql from "graphql-tag";
import {
+ Alert,
Box,
Button,
Center,
@@ -56,6 +57,23 @@ function HomePage() {
return (
+
+
+
+ The Neopets Metaverse team is no longer licensed to use this
+ software.
+ {" "}
+
+ More information available here.
+ {" "}
+ Thanks for understanding!
+
+
+
@@ -795,30 +794,6 @@ const buildItemTradesLoader = (db, loaders) =>
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
);
-const buildItemWakaValueLoader = () =>
- new DataLoader(async (itemIds) => {
- // This loader calls our /api/allWakaValues endpoint, to take advantage of
- // the CDN caching. This helps us respond a bit faster than Google Sheets
- // API would, and avoid putting pressure on our Google Sheets API quotas.
- // (Some kind of internal memcache or process-level cache would be a more
- // idiomatic solution in a monolith server environment!)
- const url = process.env.VERCEL_URL
- ? `https://${process.env.VERCEL_URL}/api/allWakaValues`
- : process.env.NODE_ENV === "production"
- ? "https://impress-2020.openneo.net/api/allWakaValues"
- : "http://localhost:3000/api/allWakaValues";
- const res = await fetch(url);
- if (!res.ok) {
- throw new Error(
- `Error loading /api/allWakaValues: ${res.status} ${res.statusText}`
- );
- }
-
- const allWakaValues = await res.json();
-
- return itemIds.map((itemId) => allWakaValues[itemId]);
- });
-
const buildPetTypeLoader = (db, loaders) =>
new DataLoader(async (petTypeIds) => {
const qs = petTypeIds.map((_) => "?").join(",");
@@ -1470,7 +1445,6 @@ function buildLoaders(db) {
db
);
loaders.itemTradesLoader = buildItemTradesLoader(db, loaders);
- loaders.itemWakaValueLoader = buildItemWakaValueLoader();
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
db,
diff --git a/src/server/types/Item.js b/src/server/types/Item.js
index 1722dc9..5656d14 100644
--- a/src/server/types/Item.js
+++ b/src/server/types/Item.js
@@ -28,14 +28,13 @@ const typeDefs = gql`
createdAt: String
"""
- This item's capsule trade value as text, according to wakaguide.com, as a
- human-readable string. Will be null if the value is not known, or if
- there's an error connecting to the data source.
+ Deprecated: This item's capsule trade value as text, according to
+ wakaguide.com, as a human-readable string. **This now always returns null.**
"""
wakaValueText: String @cacheControl(maxAge: ${oneHour})
- currentUserOwnsThis: Boolean! @cacheControl(scope: PRIVATE)
- currentUserWantsThis: Boolean! @cacheControl(scope: PRIVATE)
+ currentUserOwnsThis: Boolean! @cacheControl(maxAge: 0, scope: PRIVATE)
+ currentUserWantsThis: Boolean! @cacheControl(maxAge: 0, scope: PRIVATE)
"""
How many users are offering/seeking this in their public trade lists.
@@ -315,17 +314,9 @@ const resolvers = {
const item = await itemLoader.load(id);
return item.createdAt && item.createdAt.toISOString();
},
- wakaValueText: async ({ id }, _, { itemWakaValueLoader }) => {
- let wakaValue;
- try {
- wakaValue = await itemWakaValueLoader.load(id);
- } catch (e) {
- console.error(`Error loading wakaValueText for item ${id}, skipping:`);
- console.error(e);
- wakaValue = null;
- }
-
- return wakaValue ? wakaValue.value : null;
+ wakaValueText: () => {
+ // This feature is deprecated, so now we just always return unknown value.
+ return null;
},
currentUserOwnsThis: async (
@@ -665,8 +656,13 @@ const resolvers = {
itemSearchItemsLoader,
petTypeBySpeciesAndColorLoader,
currentUserId,
- }
+ },
+ { cacheControl }
) => {
+ if (currentUserOwnsOrWants != null) {
+ cacheControl.setCacheHint({ scope: "PRIVATE" });
+ }
+
let bodyId = null;
if (fitsPet) {
const petType = await petTypeBySpeciesAndColorLoader.load({
@@ -799,8 +795,12 @@ const resolvers = {
numTotalItems: async (
{ query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds },
{ offset, limit },
- { currentUserId, itemSearchNumTotalItemsLoader }
+ { currentUserId, itemSearchNumTotalItemsLoader },
+ { cacheControl }
) => {
+ if (currentUserOwnsOrWants != null) {
+ cacheControl.setCacheHint({ scope: "PRIVATE" });
+ }
const numTotalItems = await itemSearchNumTotalItemsLoader.load({
query: query.trim(),
bodyId,
@@ -816,8 +816,12 @@ const resolvers = {
items: async (
{ query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds },
{ offset, limit },
- { currentUserId, itemSearchItemsLoader }
+ { currentUserId, itemSearchItemsLoader },
+ { cacheControl }
) => {
+ if (currentUserOwnsOrWants != null) {
+ cacheControl.setCacheHint({ scope: "PRIVATE" });
+ }
const items = await itemSearchItemsLoader.load({
query: query.trim(),
bodyId,
diff --git a/src/server/types/User.js b/src/server/types/User.js
index ac81383..9d40f1e 100644
--- a/src/server/types/User.js
+++ b/src/server/types/User.js
@@ -47,7 +47,20 @@ const typeDefs = gql`
user(id: ID!): User
userByName(name: String!): User
userByEmail(email: String!, supportSecret: String!): User
- currentUser: User
+
+ """
+ The currently logged-in user.
+ """
+ # Don't allow caching of *anything* nested inside currentUser, because we
+ # want logins/logouts always reset user data properly.
+ #
+ # TODO: If we wanted to privately cache a currentUser field, we could
+ # remove the maxAge condition here, and attach user ID to the GraphQL
+ # request URL when sending auth headers. That way, changing user
+ # would send different requests and avoid the old cache hits. (But we
+ # should leave the scope, to emphasize that the CDN cache shouldn't
+ # cache it.)
+ currentUser: User @cacheControl(maxAge: 0, scope: PRIVATE)
}
`;
diff --git a/yarn.lock b/yarn.lock
index 5277daf..e9f17e0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4564,7 +4564,7 @@ buffer@5.6.0, buffer@^5.2.0:
base64-js "^1.0.2"
ieee754 "^1.1.4"
-buffer@^5.5.0:
+buffer@^5.2.1, buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
@@ -4961,7 +4961,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.1.0, commander@^6.2.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==
@@ -5560,6 +5560,11 @@ detect-node@^2.0.4:
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
+devtools-protocol@0.0.901419:
+ version "0.0.901419"
+ resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.901419.tgz#79b5459c48fe7e1c5563c02bd72f8fec3e0cebcd"
+ integrity sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==
+
dicer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
@@ -5930,11 +5935,6 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
-escape-string-regexp@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
- integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
-
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
@@ -6361,17 +6361,7 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-extract-zip@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
- integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
- dependencies:
- concat-stream "^1.6.2"
- debug "^2.6.9"
- mkdirp "^0.5.4"
- yauzl "^2.10.0"
-
-extract-zip@^2.0.1:
+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==
@@ -6382,6 +6372,16 @@ extract-zip@^2.0.1:
optionalDependencies:
"@types/yauzl" "^2.9.1"
+extract-zip@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
+ integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
+ dependencies:
+ concat-stream "^1.6.2"
+ debug "^2.6.9"
+ mkdirp "^0.5.4"
+ yauzl "^2.10.0"
+
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -6771,6 +6771,11 @@ generate-function@^2.3.1:
dependencies:
is-property "^1.0.2"
+generic-pool@^3.8.2:
+ version "3.8.2"
+ resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9"
+ integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==
+
gensync@^1.0.0-beta.1:
version "1.0.0-beta.1"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
@@ -6979,11 +6984,6 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
-graceful-fs@^4.2.4:
- version "4.2.4"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
- integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
-
graphql-extensions@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.11.1.tgz#f543f544a047a7a4dd930123f662dfcc01527416"
@@ -7055,9 +7055,9 @@ graphql@^14.5.3:
iterall "^1.2.2"
graphql@^15.5.0:
- version "15.5.0"
- resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
- integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==
+ version "15.7.2"
+ resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.7.2.tgz#85ab0eeb83722977151b3feb4d631b5f2ab287ef"
+ integrity sha512-AnnKk7hFQFmU/2I9YSQf3xw44ctnSFCfp3zE0N6W174gqe9fWG/2rKaKxROK7CcI3XtERpjEKFqts8o319Kf7A==
gud@^1.0.0:
version "1.0.0"
@@ -7290,6 +7290,14 @@ https-browserify@1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
+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"
+
https-proxy-agent@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
@@ -7298,14 +7306,6 @@ 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"
@@ -7988,11 +7988,6 @@ 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"
@@ -9007,6 +9002,13 @@ node-fetch@2.6.1:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
+node-fetch@2.6.5:
+ version "2.6.5"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
+ integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
+ dependencies:
+ whatwg-url "^5.0.0"
+
node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
@@ -9649,6 +9651,13 @@ pixelmatch@^5.1.0:
dependencies:
pngjs "^4.0.1"
+pkg-dir@4.2.0, pkg-dir@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+ integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+ dependencies:
+ find-up "^4.0.0"
+
pkg-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
@@ -9663,58 +9672,11 @@ pkg-dir@^3.0.0:
dependencies:
find-up "^3.0.0"
-pkg-dir@^4.1.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
- integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
- dependencies:
- find-up "^4.0.0"
-
platform@1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==
-playwright-core@^1.14.0:
- version "1.14.0"
- resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.14.0.tgz#af51da7b201c11eeda780e2db3f05c8bca74c8be"
- integrity sha512-n6NdknezSfRgB6LkLwcrbm5orRQZSpbd8LZmlc4YrIXV0VEvJr5tzP3xlHXpiFBfTr3yoFuagldI3T7bD/8H3w==
- 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"
-
-playwright@^1.14.0:
- version "1.14.0"
- resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.14.0.tgz#18301b11f5278a446d36b5cf96f67db36ce2cd20"
- integrity sha512-aR5oZ1iVsjQkGfYCjgYAmyMAVu0MQ0i8MgdnfdqDu9EVLfbnpuuFmTv/Rb7/Yjno1kOrDUP9+RyNC+zfG3wozA==
- 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"
@@ -9732,11 +9694,6 @@ 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==
-
popmotion@9.3.5:
version "9.3.5"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.5.tgz#e821aff3424a021b0f2c93922db31c55cfe64149"
@@ -9845,7 +9802,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.3:
+progress@2.0.3, progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
@@ -9864,15 +9821,6 @@ 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"
@@ -9895,7 +9843,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.1.0:
+proxy-from-env@1.1.0, proxy-from-env@^1.0.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==
@@ -9940,6 +9888,24 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+puppeteer@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-11.0.0.tgz#0808719c38e15315ecc1b1c28911f1c9054d201f"
+ integrity sha512-6rPFqN1ABjn4shgOICGDBITTRV09EjXVqhDERBDKwCLz0UyBxeeBH6Ay0vQUJ84VACmlxwzOIzVEJXThcF3aNg==
+ dependencies:
+ debug "4.3.2"
+ devtools-protocol "0.0.901419"
+ extract-zip "2.0.1"
+ https-proxy-agent "5.0.0"
+ node-fetch "2.6.5"
+ pkg-dir "4.2.0"
+ progress "2.0.3"
+ proxy-from-env "1.1.0"
+ rimraf "3.0.2"
+ tar-fs "2.1.1"
+ unbzip2-stream "1.4.3"
+ ws "8.2.3"
+
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@@ -10484,6 +10450,13 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@@ -10491,13 +10464,6 @@ rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
dependencies:
glob "^7.1.3"
-rimraf@^3.0.0, rimraf@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
- integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
- dependencies:
- glob "^7.1.3"
-
rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@@ -11019,13 +10985,6 @@ ssim.js@^3.1.1:
resolved "https://registry.yarnpkg.com/ssim.js/-/ssim.js-3.5.0.tgz#d7276b9ee99b57a5ff0db34035f02f35197e62df"
integrity sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==
-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==
- dependencies:
- escape-string-regexp "^2.0.0"
-
stacktrace-parser@0.1.10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a"
@@ -11400,7 +11359,7 @@ table@^6.0.9:
string-width "^4.2.3"
strip-ansi "^6.0.1"
-tar-fs@^2.0.0, tar-fs@^2.1.1:
+tar-fs@2.1.1, tar-fs@^2.0.0, tar-fs@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
@@ -11582,6 +11541,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
+
truncate-utf8-bytes@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
@@ -11745,6 +11709,14 @@ unbox-primitive@^1.0.1:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
+unbzip2-stream@1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
+ integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
+ dependencies:
+ buffer "^5.2.1"
+ through "^2.3.8"
+
unfetch@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
@@ -12008,6 +11980,11 @@ watchpack@2.1.1:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
+
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@@ -12018,6 +11995,14 @@ whatwg-fetch@^3.0.0:
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
whatwg-url@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
@@ -12109,6 +12094,11 @@ write-file-atomic@^2.3.0:
imurmurhash "^0.1.4"
signal-exit "^3.0.2"
+ws@8.2.3:
+ version "8.2.3"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
+ integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
+
ws@^5.2.0:
version "5.2.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"
@@ -12123,11 +12113,6 @@ ws@^6.0.0:
dependencies:
async-limiter "~1.0.0"
-ws@^7.4.6:
- version "7.5.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
- integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
-
ws@~7.4.2:
version "7.4.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59"
@@ -12245,13 +12230,6 @@ 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"