From ea8d6c3737b6fa6d8d45ac88320d096988ca3e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Tue, 12 Mar 2024 19:09:14 +0100 Subject: [PATCH] [#378] feat: add hash and validation of the metadata --- CHANGELOG.md | 3 +- govtool/frontend/package.json | 2 + .../CreateGovernanceActionForm.tsx | 8 +- .../StorageInformation.tsx | 103 ++++++++++++++- .../src/components/organisms/StatusModal.tsx | 46 ++++++- .../src/consts/governanceActionFields.ts | 23 ++-- govtool/frontend/src/context/modal.tsx | 2 +- govtool/frontend/src/i18n/locales/en.ts | 19 +++ govtool/frontend/src/utils/canonizeJSON.ts | 12 ++ govtool/frontend/src/utils/index.ts | 2 + .../src/utils/validateMetadataHash.ts | 61 +++++++++ govtool/frontend/yarn.lock | 117 ++++++++++++++++++ 12 files changed, 372 insertions(+), 26 deletions(-) create mode 100644 govtool/frontend/src/utils/canonizeJSON.ts create mode 100644 govtool/frontend/src/utils/validateMetadataHash.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b4be57e6..fff3b1651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,8 @@ changes. - Create GA creation form [Issue 360](https://github.com/IntersectMBO/govtool/issues/360) - Create TextArea [Issue 110](https://github.com/IntersectMBO/govtool/issues/110) - Choose GA type - GA Submiter [Issue 358](https://github.com/IntersectMBO/govtool/issues/358) - - Add on-chain inputs validation [Issue 377](https://github.com/IntersectMBO/govtool/issues/377) +- Add hash and validation of the metadata [Issue 378](https://github.com/IntersectMBO/govtool/issues/378) ### Added @@ -44,6 +44,7 @@ changes. - Fixed CSP settings to allow error reports with Sentry [Issue 291](https://github.com/IntersectMBO/govtool/issues/291). ### Changed + - `drep/list` now return also `status` and `type` fields. Also it now returns the retired dreps, and you can search for given drep by name using optional query parameter. If the drep name is passed exactly, then you can even find a drep that's sole voter. [Issue 446](https://github.com/IntersectMBO/govtool/issues/446) - `drep/list` and `drep/info` endpoints now return additional data such as metadata url and hash, and voting power [Issue 223](https://github.com/IntersectMBO/govtool/issues/223) - `drep/info` now does not return sole voters (dreps without metadata) [Issue 317](https://github.com/IntersectMBO/govtool/issues/317) diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index 5d76fd45f..355a705a8 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -22,6 +22,7 @@ "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@sentry/react": "^7.77.0", + "@types/jsonld": "^1.5.13", "@types/react": "^18.2.12", "@types/react-gtm-module": "^2.0.2", "axios": "^1.4.0", @@ -31,6 +32,7 @@ "date-fns": "^2.30.0", "esbuild": "^0.19.8", "i18next": "^23.7.19", + "jsonld": "^8.3.2", "keen-slider": "^6.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/CreateGovernanceActionForm.tsx b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/CreateGovernanceActionForm.tsx index 0d49018e5..ca96904e6 100644 --- a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/CreateGovernanceActionForm.tsx +++ b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/CreateGovernanceActionForm.tsx @@ -6,11 +6,11 @@ import { Button, InfoText, Spacer, Typography } from "@atoms"; import { GOVERNANCE_ACTION_FIELDS } from "@consts"; import { useCreateGovernanceActionForm, useTranslation } from "@hooks"; import { Field } from "@molecules"; +import { URL_REGEX } from "@/utils"; +import { GovernanceActionField } from "@/types/governanceAction"; import { BgCard } from "../BgCard"; import { ControlledField } from "../ControlledField"; -import { GovernanceActionField } from "@/types/governanceAction"; -import { URL_REGEX } from "@/utils"; const LINK_PLACEHOLDER = "https://website.com/"; const MAX_NUMBER_OF_LINKS = 8; @@ -117,10 +117,6 @@ export const CreateGovernanceActionForm = ({ placeholder={LINK_PLACEHOLDER} name={`links.${index}.link`} rules={{ - required: { - value: true, - message: t("createGovernanceAction.fields.validations.required"), - }, pattern: { value: URL_REGEX, message: t("createGovernanceAction.fields.validations.url"), diff --git a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx index 4454b897d..8b50ccc6f 100644 --- a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx +++ b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx @@ -1,20 +1,85 @@ import { Dispatch, SetStateAction, useCallback, useState } from "react"; +import { useNavigate } from "react-router-dom"; import { Box } from "@mui/material"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import { Button, Spacer, Typography } from "@atoms"; -import { ICONS } from "@consts"; +import { ICONS, PATHS } from "@consts"; import { useCreateGovernanceActionForm, useTranslation } from "@hooks"; import { Step } from "@molecules"; -import { BgCard, ControlledField } from "@organisms"; -import { URL_REGEX, downloadJson, openInNewTab } from "@utils"; +import { BgCard, ControlledField, StatusModalState } from "@organisms"; +import { + URL_REGEX, + downloadJson, + openInNewTab, + validateMetadataHash, + MetadataHashValidationErrors, +} from "@utils"; +import { ModalState, useModal } from "@/context"; +import I18n from "@/i18n"; type StorageInformationProps = { setStep: Dispatch>; }; +const externalDataDoesntMatchModal = { + type: "statusModal", + state: { + status: "warning", + title: I18n.t( + "createGovernanceAction.modals.externalDataDoesntMatch.title" + ), + message: I18n.t( + "createGovernanceAction.modals.externalDataDoesntMatch.message" + ), + buttonText: I18n.t( + "createGovernanceAction.modals.externalDataDoesntMatch.buttonText" + ), + cancelText: I18n.t( + "createGovernanceAction.modals.externalDataDoesntMatch.cancelRegistrationText" + ), + feedbackText: I18n.t( + "createGovernanceAction.modals.externalDataDoesntMatch.feedbackText" + ), + }, +} as const; + +const urlCannotBeFound = { + type: "statusModal", + state: { + status: "warning", + title: I18n.t("createGovernanceAction.modals.urlCannotBeFound.title"), + message: I18n.t("createGovernanceAction.modals.urlCannotBeFound.message"), + link: "https://docs.sanchogov.tools", + linkText: I18n.t("createGovernanceAction.modals.urlCannotBeFound.linkText"), + buttonText: I18n.t( + "createGovernanceAction.modals.urlCannotBeFound.buttonText" + ), + cancelText: I18n.t( + "createGovernanceAction.modals.urlCannotBeFound.cancelRegistrationText" + ), + feedbackText: I18n.t( + "createGovernanceAction.modals.urlCannotBeFound.feedbackText" + ), + }, +} as const; + +const storageInformationErrorModals: Record< + MetadataHashValidationErrors, + ModalState< + | (typeof externalDataDoesntMatchModal)["state"] + | (typeof urlCannotBeFound)["state"] + > +> = { + [MetadataHashValidationErrors.INVALID_URL]: urlCannotBeFound, + [MetadataHashValidationErrors.FETCH_ERROR]: urlCannotBeFound, + [MetadataHashValidationErrors.INVALID_JSON]: externalDataDoesntMatchModal, + [MetadataHashValidationErrors.INVALID_HASH]: externalDataDoesntMatchModal, +}; + export const StorageInformation = ({ setStep }: StorageInformationProps) => { const { t } = useTranslation(); + const navigate = useNavigate(); const { control, errors, @@ -23,6 +88,7 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { getValues, watch, } = useCreateGovernanceActionForm(); + const { openModal, closeModal } = useModal(); const [isJsonDownloaded, setIsJsonDownloaded] = useState(false); // TODO: change on correct file name @@ -45,12 +111,41 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { setIsJsonDownloaded(true); }; + const backToDashboard = () => { + navigate(PATHS.dashboard); + closeModal(); + }; + + const handleStoringURLJSONValidation = useCallback(async () => { + const storingURL = getValues("storingURL"); + try { + await createGovernanceAction(); + + // TODO: To be replaced wtih the correct hash + await validateMetadataHash(storingURL, "hash"); + } catch (error: any) { + if (Object.values(MetadataHashValidationErrors).includes(error.message)) { + openModal({ + ...storageInformationErrorModals[ + error.message as MetadataHashValidationErrors + ], + onSubmit: () => { + setStep(3); + }, + onCancel: backToDashboard, + // TODO: Open usersnap feedback + onFeedback: backToDashboard, + } as ModalState); + } + } + }, [getValues, generateJsonBody]); + return ( diff --git a/govtool/frontend/src/components/organisms/StatusModal.tsx b/govtool/frontend/src/components/organisms/StatusModal.tsx index 35b24ef4a..0a6ec6c66 100644 --- a/govtool/frontend/src/components/organisms/StatusModal.tsx +++ b/govtool/frontend/src/components/organisms/StatusModal.tsx @@ -8,11 +8,16 @@ import { useScreenDimension, useTranslation } from "@/hooks"; export interface StatusModalState { buttonText?: string; + cancelText?: string; + feedbackText?: string; status: "warning" | "info" | "success"; isInfo?: boolean; link?: string; + linkText?: string; message: React.ReactNode; onSubmit?: () => void; + onCancel?: () => void; + onFeedback?: () => void; title: string; dataTestId: string; } @@ -43,20 +48,20 @@ export function StatusModal() { textAlign="center" sx={{ fontSize: "16px", fontWeight: "400" }} > - {state?.message}{" "} + {state?.message} {state?.link && ( openInNewTab(state?.link || "")} target="_blank" sx={[{ "&:hover": { cursor: "pointer" } }]} > - {t("thisLink")} + {state?.linkText || t("thisLink")} )} + {state?.cancelText && ( + + )} + {state?.feedbackText && ( + + )} ); } diff --git a/govtool/frontend/src/consts/governanceActionFields.ts b/govtool/frontend/src/consts/governanceActionFields.ts index 42fc035d1..cbded7d8c 100644 --- a/govtool/frontend/src/consts/governanceActionFields.ts +++ b/govtool/frontend/src/consts/governanceActionFields.ts @@ -90,10 +90,15 @@ export const GOVERNANCE_ACTION_FIELDS: GovernanceActionFields = { placeholderI18nKey: "createGovernanceAction.fields.declarations.receivingAddress.placeholder", rules: { - validate: (value) => { - if (bech32.decode(value).words.length) { - return true; - } else { + validate: async (value) => { + try { + const decoded = await bech32.decode(value); + if (decoded.words.length) { + return true; + } else { + throw new Error(); + } + } catch (error) { return I18n.t("createGovernanceAction.fields.validations.bech32"); } }, @@ -109,13 +114,9 @@ export const GOVERNANCE_ACTION_FIELDS: GovernanceActionFields = { value: true, message: I18n.t("createGovernanceAction.fields.validations.required"), }, - validate: (value) => { - if (Number.isInteger(Number(value))) { - return true; - } else { - return I18n.t("createGovernanceAction.fields.validations.number"); - } - }, + validate: (value) => + Number.isInteger(Number(value)) || + I18n.t("createGovernanceAction.fields.validations.number"), }, }, }, diff --git a/govtool/frontend/src/context/modal.tsx b/govtool/frontend/src/context/modal.tsx index 4d6afdf0a..985910fde 100644 --- a/govtool/frontend/src/context/modal.tsx +++ b/govtool/frontend/src/context/modal.tsx @@ -47,7 +47,7 @@ const modals: Record = { type Optional = Pick, K> & Omit; -interface ModalState { +export interface ModalState { type: ModalType; state: T | null; } diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index edc1e2be7..904bcc045 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -191,6 +191,25 @@ export const en = { url: "Invalid URL", }, }, + modals: { + externalDataDoesntMatch: { + title: "Your External Data Does Not Match the Original File.", + message: + "GovTool checks the URL you entered to see if the JSON file that you self-host matches the one that was generated in GovTool. To complete registration, this match must be exact.\n\nIn this case, there is a mismatch. You can go back to the data edit screen and try the process again.", + buttonText: "Go to Data Edit Screen", + cancelRegistrationText: "Cancel Registration", + feedbackText: "Feedback", + }, + urlCannotBeFound: { + title: "The URL You Entered Cannot Be Found", + message: + "GovTool cannot find the URL that you entered. Please check it and re-enter.", + linkText: "Learn More about self-hosting", + buttonText: "Go to Data Edit Screen", + cancelRegistrationText: "Cancel Registration", + feedbackText: "Feedback", + }, + }, }, delegation: { description: diff --git a/govtool/frontend/src/utils/canonizeJSON.ts b/govtool/frontend/src/utils/canonizeJSON.ts new file mode 100644 index 000000000..72f884708 --- /dev/null +++ b/govtool/frontend/src/utils/canonizeJSON.ts @@ -0,0 +1,12 @@ +import jsonld from "jsonld"; + +/** + * Canonizes a JSON object using jsonld.canonize. + * + * @param json - The JSON object to be canonized. + * @returns A Promise that resolves to the canonized JSON object. + */ +export const canonizeJSON = async (json: Record) => { + const canonized = await jsonld.canonize(json); + return canonized; +}; diff --git a/govtool/frontend/src/utils/index.ts b/govtool/frontend/src/utils/index.ts index 313353de7..6a738852d 100644 --- a/govtool/frontend/src/utils/index.ts +++ b/govtool/frontend/src/utils/index.ts @@ -1,6 +1,7 @@ export * from "./adaFormat"; export * from "./basicReducer"; export * from "./callAll"; +export * from "./canonizeJSON"; export * from "./checkIsMaintenanceOn"; export * from "./checkIsWalletConnected"; export * from "./formatDate"; @@ -14,3 +15,4 @@ export * from "./jsonUtils"; export * from "./localStorage"; export * from "./openInNewTab"; export * from "./removeDuplicatedProposals"; +export * from "./validateMetadataHash"; diff --git a/govtool/frontend/src/utils/validateMetadataHash.ts b/govtool/frontend/src/utils/validateMetadataHash.ts new file mode 100644 index 000000000..b35f23e71 --- /dev/null +++ b/govtool/frontend/src/utils/validateMetadataHash.ts @@ -0,0 +1,61 @@ +import * as blake from "blakejs"; +import { isAxiosError } from "axios"; + +import { API } from "@/services"; + +import { canonizeJSON } from "./canonizeJSON"; +import { URL_REGEX } from "."; + +export enum MetadataHashValidationErrors { + INVALID_URL = "Invalid URL", + INVALID_JSON = "Invalid JSON", + INVALID_HASH = "Invalid hash", + FETCH_ERROR = "Error fetching data", +} + +/** + * Validates the metadata hash by fetching the metadata from the given URL, + * canonizing it, and comparing the hash with the provided hash. + * + * @param storingURL - The URL where the metadata is stored. + * @param hash - The hash to compare with the calculated hash of the metadata. + * @returns A promise that resolves to `true` if the metadata hash is valid, or rejects with an error message if validation fails. + */ +export const validateMetadataHash = async ( + storingURL: string, + hash: string +) => { + try { + if (!storingURL.match(URL_REGEX)) { + throw new Error(MetadataHashValidationErrors.INVALID_URL); + } + + const { data: userMetadataJSON } = await API.get(storingURL); + + let canonizedUserMetadata; + try { + canonizedUserMetadata = await canonizeJSON(userMetadataJSON); + } catch (error) { + throw new Error(MetadataHashValidationErrors.INVALID_JSON); + } + if (!canonizedUserMetadata) { + throw new Error(MetadataHashValidationErrors.INVALID_JSON); + } + + const hashedUserMetadata = blake.blake2bHex( + canonizedUserMetadata, + undefined, + 32 + ); + + if (hashedUserMetadata !== hash) { + throw new Error(MetadataHashValidationErrors.INVALID_HASH); + } + return true; + } catch (error) { + if (isAxiosError(error)) { + throw new Error(MetadataHashValidationErrors.FETCH_ERROR); + } + throw error; + } +}; diff --git a/govtool/frontend/yarn.lock b/govtool/frontend/yarn.lock index 75561ec59..7686b75ac 100644 --- a/govtool/frontend/yarn.lock +++ b/govtool/frontend/yarn.lock @@ -1072,6 +1072,15 @@ resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@digitalbazaar/http-client@^3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-3.4.1.tgz#5116fc44290d647cfe4b615d1f3fad9d6005e44d" + integrity sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g== + dependencies: + ky "^0.33.3" + ky-universal "^0.11.0" + undici "^5.21.2" + "@discoveryjs/json-ext@^0.5.3": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" @@ -1561,6 +1570,11 @@ resolved "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz" integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ== +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + "@floating-ui/core@^1.4.2": version "1.5.1" resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.1.tgz" @@ -3561,6 +3575,11 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/jsonld@^1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.13.tgz#32cb7bf8893ff811036b5025edaf626593a6f36f" + integrity sha512-n7fUU6W4kSYK8VQlf/LsE9kddBHPKhODoVOjsZswmve+2qLwBy6naWxs/EiuSZN9NU0N06Ra01FR+j87C62T0A== + "@types/lodash@^4.14.167", "@types/lodash@^4.14.178", "@types/lodash@^4.14.191": version "4.14.202" resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz" @@ -3951,6 +3970,13 @@ "@types/emscripten" "^1.39.6" tslib "^1.13.0" +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" @@ -4529,6 +4555,11 @@ caniuse-lite@^1.0.30001565: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz" integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== +canonicalize@^1.0.1: + version "1.0.8" + resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.8.tgz#24d1f1a00ed202faafd9bf8e63352cd4450c6df1" + integrity sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A== + chai@^4.3.10: version "4.3.10" resolved "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz" @@ -4921,6 +4952,11 @@ cwd@^0.10.0: find-pkg "^0.1.2" fs-exists-sync "^0.1.0" +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + data-urls@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz" @@ -5596,6 +5632,11 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + execa@^5.0.0, execa@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" @@ -5753,6 +5794,14 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + fetch-retry@^5.0.2: version "5.0.6" resolved "https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.6.tgz" @@ -5931,6 +5980,13 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -7439,6 +7495,16 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonld@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-8.3.2.tgz#7033f8994aed346b536e9046025f7f1fe9669934" + integrity sha512-MwBbq95szLwt8eVQ1Bcfwmgju/Y5P2GdtlHE2ncyfuYjIdEhluUVyj1eudacf1mOkWIoS9GpDBTECqhmq7EOaA== + dependencies: + "@digitalbazaar/http-client" "^3.4.1" + canonicalize "^1.0.1" + lru-cache "^6.0.0" + rdf-canonize "^3.4.0" + keen-slider@^6.8.5: version "6.8.6" resolved "https://registry.npmjs.org/keen-slider/-/keen-slider-6.8.6.tgz" @@ -7461,6 +7527,19 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +ky-universal@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.11.0.tgz#f5edf857865aaaea416a1968222148ad7d9e4017" + integrity sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw== + dependencies: + abort-controller "^3.0.0" + node-fetch "^3.2.10" + +ky@^0.33.3: + version "0.33.3" + resolved "https://registry.yarnpkg.com/ky/-/ky-0.33.3.tgz#bf1ad322a3f2c3428c13cfa4b3af95e6c4a2f543" + integrity sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw== + lazy-universal-dotenv@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz" @@ -7902,6 +7981,11 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch-native@^1.4.0: version "1.4.1" resolved "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.4.1.tgz" @@ -7914,6 +7998,15 @@ node-fetch@^2, node-fetch@^2.0.0: dependencies: whatwg-url "^5.0.0" +node-fetch@^3.2.10: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" @@ -8572,6 +8665,13 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +rdf-canonize@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/rdf-canonize/-/rdf-canonize-3.4.0.tgz#87f88342b173cc371d812a07de350f0c1aa9f058" + integrity sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA== + dependencies: + setimmediate "^1.0.5" + react-base16-styling@^0.9.1: version "0.9.1" resolved "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz" @@ -9154,6 +9254,11 @@ set-function-name@^2.0.0: functions-have-names "^1.2.3" has-property-descriptors "^1.0.0" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" @@ -9816,6 +9921,13 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici@^5.21.2: + version "5.28.3" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.3.tgz#a731e0eff2c3fcfd41c1169a869062be222d1e5b" + integrity sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA== + dependencies: + "@fastify/busboy" "^2.0.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -10128,6 +10240,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"