diff --git a/cypress/e2e/composer/composer.spec.ts b/cypress/e2e/composer/composer.spec.ts
new file mode 100644
index 00000000000..f3fc374cf02
--- /dev/null
+++ b/cypress/e2e/composer/composer.spec.ts
@@ -0,0 +1,140 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+///
+
+import { SynapseInstance } from "../../plugins/synapsedocker";
+import { SettingLevel } from "../../../src/settings/SettingLevel";
+
+describe("Composer", () => {
+ let synapse: SynapseInstance;
+
+ beforeEach(() => {
+ cy.startSynapse("default").then(data => {
+ synapse = data;
+ });
+ });
+
+ afterEach(() => {
+ cy.stopSynapse(synapse);
+ });
+
+ describe("CIDER", () => {
+ beforeEach(() => {
+ cy.initTestUser(synapse, "Janet").then(() => {
+ cy.createRoom({ name: "Composing Room" });
+ });
+ cy.viewRoomByName("Composing Room");
+ });
+
+ it("sends a message when you click send or press Enter", () => {
+ // Type a message
+ cy.get('div[contenteditable=true]').type('my message 0');
+ // It has not been sent yet
+ cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist');
+
+ // Click send
+ cy.get('div[aria-label="Send message"]').click();
+ // It has been sent
+ cy.contains('.mx_EventTile_body', 'my message 0');
+
+ // Type another and press Enter afterwards
+ cy.get('div[contenteditable=true]').type('my message 1{enter}');
+ // It was sent
+ cy.contains('.mx_EventTile_body', 'my message 1');
+ });
+
+ it("can write formatted text", () => {
+ cy.get('div[contenteditable=true]').type('my bold{ctrl+b} message');
+ cy.get('div[aria-label="Send message"]').click();
+ // Note: both "bold" and "message" are bold, which is probably surprising
+ cy.contains('.mx_EventTile_body strong', 'bold message');
+ });
+
+ describe("when Ctrl+Enter is required to send", () => {
+ beforeEach(() => {
+ cy.setSettingValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);
+ });
+
+ it("only sends when you press Ctrl+Enter", () => {
+ // Type a message and press Enter
+ cy.get('div[contenteditable=true]').type('my message 3{enter}');
+ // It has not been sent yet
+ cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist');
+
+ // Press Ctrl+Enter
+ cy.get('div[contenteditable=true]').type('{ctrl+enter}');
+ // It was sent
+ cy.contains('.mx_EventTile_body', 'my message 3');
+ });
+ });
+ });
+
+ describe("WYSIWYG", () => {
+ beforeEach(() => {
+ cy.enableLabsFeature("feature_wysiwyg_composer");
+ cy.initTestUser(synapse, "Janet").then(() => {
+ cy.createRoom({ name: "Composing Room" });
+ });
+ cy.viewRoomByName("Composing Room");
+ });
+
+ it("sends a message when you click send or press Enter", () => {
+ // Type a message
+ cy.get('div[contenteditable=true]').type('my message 0');
+ // It has not been sent yet
+ cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist');
+
+ // Click send
+ cy.get('div[aria-label="Send message"]').click();
+ // It has been sent
+ cy.contains('.mx_EventTile_body', 'my message 0');
+
+ // Type another
+ cy.get('div[contenteditable=true]').type('my message 1');
+ // Press enter. Would be nice to just use {enter} but we can't because Cypress
+ // does not trigger an insertParagraph when you do that.
+ cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" });
+ // It was sent
+ cy.contains('.mx_EventTile_body', 'my message 1');
+ });
+
+ it("can write formatted text", () => {
+ cy.get('div[contenteditable=true]').type('my {ctrl+b}bold{ctrl+b} message');
+ cy.get('div[aria-label="Send message"]').click();
+ cy.contains('.mx_EventTile_body strong', 'bold');
+ });
+
+ describe("when Ctrl+Enter is required to send", () => {
+ beforeEach(() => {
+ cy.setSettingValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);
+ });
+
+ it("only sends when you press Ctrl+Enter", () => {
+ // Type a message and press Enter
+ cy.get('div[contenteditable=true]').type('my message 3');
+ cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" });
+ // It has not been sent yet
+ cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist');
+
+ // Press Ctrl+Enter
+ cy.get('div[contenteditable=true]').type('{ctrl+enter}');
+ // It was sent
+ cy.contains('.mx_EventTile_body', 'my message 3');
+ });
+ });
+ });
+});
diff --git a/package.json b/package.json
index b203cf51e91..f0ab2c266b4 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.2.0",
- "@matrix-org/matrix-wysiwyg": "^0.2.0",
+ "@matrix-org/matrix-wysiwyg": "^0.3.0",
"@matrix-org/react-sdk-module-api": "^0.0.3",
"@sentry/browser": "^6.11.0",
"@sentry/tracing": "^6.11.0",
diff --git a/src/components/views/rooms/wysiwyg_composer/WysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/WysiwygComposer.tsx
index 8701f5be778..c22e3406fa6 100644
--- a/src/components/views/rooms/wysiwyg_composer/WysiwygComposer.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/WysiwygComposer.tsx
@@ -16,7 +16,7 @@ limitations under the License.
import React, { useCallback, useEffect } from 'react';
import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event';
-import { useWysiwyg } from "@matrix-org/matrix-wysiwyg";
+import { useWysiwyg, Wysiwyg, WysiwygInputEvent } from "@matrix-org/matrix-wysiwyg";
import { Editor } from './Editor';
import { FormattingButtons } from './FormattingButtons';
@@ -25,6 +25,7 @@ import { sendMessage } from './message';
import { useMatrixClientContext } from '../../../../contexts/MatrixClientContext';
import { useRoomContext } from '../../../../contexts/RoomContext';
import { useWysiwygActionHandler } from './useWysiwygActionHandler';
+import { useSettingValue } from '../../../../hooks/useSettings';
interface WysiwygProps {
disabled?: boolean;
@@ -41,8 +42,27 @@ export function WysiwygComposer(
) {
const roomContext = useRoomContext();
const mxClient = useMatrixClientContext();
+ const ctrlEnterToSend = useSettingValue("MessageComposerInput.ctrlEnterToSend");
- const { ref, isWysiwygReady, content, formattingStates, wysiwyg } = useWysiwyg();
+ function inputEventProcessor(event: WysiwygInputEvent, wysiwyg: Wysiwyg): WysiwygInputEvent | null {
+ if (event instanceof ClipboardEvent) {
+ return event;
+ }
+
+ if (
+ (event.inputType === 'insertParagraph' && !ctrlEnterToSend) ||
+ event.inputType === 'sendMessage'
+ ) {
+ sendMessage(content, { mxClient, roomContext, ...props });
+ wysiwyg.actions.clear();
+ ref.current?.focus();
+ return null;
+ }
+
+ return event;
+ }
+
+ const { ref, isWysiwygReady, content, formattingStates, wysiwyg } = useWysiwyg({ inputEventProcessor });
useEffect(() => {
if (!disabled && content !== null) {
diff --git a/test/components/views/rooms/wysiwyg_composer/WysiwygComposer-test.tsx b/test/components/views/rooms/wysiwyg_composer/WysiwygComposer-test.tsx
index b0aa838879b..df2596809cc 100644
--- a/test/components/views/rooms/wysiwyg_composer/WysiwygComposer-test.tsx
+++ b/test/components/views/rooms/wysiwyg_composer/WysiwygComposer-test.tsx
@@ -17,6 +17,7 @@ limitations under the License.
import "@testing-library/jest-dom";
import React from "react";
import { act, render, screen, waitFor } from "@testing-library/react";
+import { InputEventProcessor, Wysiwyg, WysiwygProps } from "@matrix-org/matrix-wysiwyg";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
@@ -26,13 +27,31 @@ import { IRoomState } from "../../../../../src/components/structures/RoomView";
import { Layout } from "../../../../../src/settings/enums/Layout";
import { WysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer/WysiwygComposer";
import { createTestClient, mkEvent, mkStubRoom } from "../../../../test-utils";
+import SettingsStore from "../../../../../src/settings/SettingsStore";
+
+// Work around missing ClipboardEvent type
+class MyClipbardEvent {}
+window.ClipboardEvent = MyClipbardEvent as any;
+
+let inputEventProcessor: InputEventProcessor | null = null;
// The wysiwyg fetch wasm bytes and a specific workaround is needed to make it works in a node (jest) environnement
// See https://github.com/matrix-org/matrix-wysiwyg/blob/main/platforms/web/test.setup.ts
jest.mock("@matrix-org/matrix-wysiwyg", () => ({
- useWysiwyg: () => {
- return { ref: { current: null }, content: 'html', isWysiwygReady: true, wysiwyg: { clear: () => void 0 },
- formattingStates: { bold: 'enabled', italic: 'enabled', underline: 'enabled', strikeThrough: 'enabled' } };
+ useWysiwyg: (props: WysiwygProps) => {
+ inputEventProcessor = props.inputEventProcessor ?? null;
+ return {
+ ref: { current: null },
+ content: 'html',
+ isWysiwygReady: true,
+ wysiwyg: { clear: () => void 0 },
+ formattingStates: {
+ bold: 'enabled',
+ italic: 'enabled',
+ underline: 'enabled',
+ strikeThrough: 'enabled',
+ },
+ };
},
}));
@@ -196,5 +215,62 @@ describe('WysiwygComposer', () => {
// Then we don't get it because we are disabled
expect(screen.getByRole('textbox')).not.toHaveFocus();
});
+
+ it('sends a message when Enter is pressed', async () => {
+ // Given a composer
+ customRender(() => {}, false);
+
+ // When we tell its inputEventProcesser that the user pressed Enter
+ const event = new InputEvent("insertParagraph", { inputType: "insertParagraph" });
+ const wysiwyg = { actions: { clear: () => {} } } as Wysiwyg;
+ inputEventProcessor(event, wysiwyg);
+
+ // Then it sends a message
+ expect(mockClient.sendMessage).toBeCalledWith(
+ "myfakeroom",
+ null,
+ {
+ "body": "html",
+ "format": "org.matrix.custom.html",
+ "formatted_body": "html",
+ "msgtype": "m.text",
+ },
+ );
+ // TODO: plain text body above is wrong - will be fixed when we provide markdown for it
+ });
+
+ describe('when settings require Ctrl+Enter to send', () => {
+ beforeEach(() => {
+ jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
+ if (name === "MessageComposerInput.ctrlEnterToSend") return true;
+ });
+ });
+
+ it('does not send a message when Enter is pressed', async () => {
+ // Given a composer
+ customRender(() => {}, false);
+
+ // When we tell its inputEventProcesser that the user pressed Enter
+ const event = new InputEvent("input", { inputType: "insertParagraph" });
+ const wysiwyg = { actions: { clear: () => {} } } as Wysiwyg;
+ inputEventProcessor(event, wysiwyg);
+
+ // Then it does not send a message
+ expect(mockClient.sendMessage).toBeCalledTimes(0);
+ });
+
+ it('sends a message when Ctrl+Enter is pressed', async () => {
+ // Given a composer
+ customRender(() => {}, false);
+
+ // When we tell its inputEventProcesser that the user pressed Ctrl+Enter
+ const event = new InputEvent("input", { inputType: "sendMessage" });
+ const wysiwyg = { actions: { clear: () => {} } } as Wysiwyg;
+ inputEventProcessor(event, wysiwyg);
+
+ // Then it sends a message
+ expect(mockClient.sendMessage).toBeCalledTimes(1);
+ });
+ });
});
diff --git a/yarn.lock b/yarn.lock
index b54bc1ec815..add14d4c3e5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1660,10 +1660,10 @@
resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.2.0.tgz#453925c939ecdd5ca6c797d293deb8cf0933f1b8"
integrity sha512-+0/Sydm4MNOcqd8iySJmojVPB74Axba4BXlwTsiKmL5fgYqdUkwmqkO39K7Pn8i+a+8pg11oNvBPkpWs3O5Qww==
-"@matrix-org/matrix-wysiwyg@^0.2.0":
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-0.2.0.tgz#651002ad67be3004698d4a89806cf344283a4ca3"
- integrity sha512-m9R1NOd0ogkhrjqFNg159TMXL5dpME90G9RDrZrO106263Qtoj0TazyBaLhNjgvPkogbzbCJUULQWPFiLQfTjw==
+"@matrix-org/matrix-wysiwyg@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-0.3.0.tgz#9a0b996c47fbb63fb235a0810b678158b253f721"
+ integrity sha512-m33qOo64VIZRqzMZ5vJ9m2gYns+sCaFFy3R5Nn9JfDnldQ1oh+ra611I9keFmO/Ls6548ZN8hUkv+49Ua3iBHA==
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz":
version "3.2.8"
@@ -2674,7 +2674,7 @@ ajv-keywords@^3.5.2:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
-ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5:
+ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -3198,6 +3198,11 @@ browser-process-hrtime@^1.0.0:
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
+browser-request@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17"
+ integrity sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg==
+
browserslist@^4.20.2, browserslist@^4.21.3:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
@@ -5280,6 +5285,19 @@ grid-index@^1.1.0:
resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7"
integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==
+har-schema@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+ integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==
+
+har-validator@~5.1.3:
+ version "5.1.5"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
+ integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
+ dependencies:
+ ajv "^6.12.3"
+ har-schema "^2.0.0"
+
hard-rejection@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
@@ -5440,6 +5458,15 @@ http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
+http-signature@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+ integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==
+ dependencies:
+ assert-plus "^1.0.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
http-signature@~1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9"
@@ -6668,6 +6695,16 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+jsprim@^1.2.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
+ integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
+ dependencies:
+ assert-plus "1.0.0"
+ extsprintf "1.3.0"
+ json-schema "0.4.0"
+ verror "1.10.0"
+
jsprim@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d"
@@ -7033,12 +7070,14 @@ matrix-events-sdk@^0.0.1-beta.7:
dependencies:
"@babel/runtime" "^7.12.5"
another-json "^0.2.0"
+ browser-request "^0.3.3"
bs58 "^5.0.0"
content-type "^1.0.4"
loglevel "^1.7.1"
matrix-events-sdk "^0.0.1-beta.7"
p-retry "4"
qs "^6.9.6"
+ request "^2.88.2"
unhomoglyph "^1.0.6"
matrix-mock-request@^2.5.0:
@@ -7357,6 +7396,11 @@ nwsapi@^2.2.0:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c"
integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==
+oauth-sign@~0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
+ integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
+
object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -8259,6 +8303,32 @@ request-progress@^3.0.0:
dependencies:
throttleit "^1.0.0"
+request@^2.88.2:
+ version "2.88.2"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
+ integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.8.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.6"
+ extend "~3.0.2"
+ forever-agent "~0.6.1"
+ form-data "~2.3.2"
+ har-validator "~5.1.3"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.19"
+ oauth-sign "~0.9.0"
+ performance-now "^2.1.0"
+ qs "~6.5.2"
+ safe-buffer "^5.1.2"
+ tough-cookie "~2.5.0"
+ tunnel-agent "^0.6.0"
+ uuid "^3.3.2"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -8725,7 +8795,7 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
-sshpk@^1.14.1:
+sshpk@^1.14.1, sshpk@^1.7.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==
@@ -9439,6 +9509,11 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+uuid@^3.3.2:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
+ integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"