mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-05 20:26:19 +02:00
Merge branch 'develop' of https://github.com/element-hq/element-web into langleyd/history_visibililty_update
This commit is contained in:
commit
22ce552d0c
@ -130,7 +130,7 @@
|
||||
"maplibre-gl": "^5.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-widget-api": "^1.14.0",
|
||||
"matrix-widget-api": "^1.15.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"mime": "^4.0.4",
|
||||
"oidc-client-ts": "^3.0.1",
|
||||
|
||||
@ -13,6 +13,7 @@ import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import type { Credentials } from "../../plugins/homeserver";
|
||||
import { Bot } from "../../pages/bot";
|
||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||
|
||||
// Load a copy of our fake Element Call app, and the latest widget API.
|
||||
// The fake call app does *just* enough to convince Element Web that a call is ongoing
|
||||
@ -578,4 +579,84 @@ test.describe("Element Call", () => {
|
||||
await openAndJoinCall(page, true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Widget leak bug reproduction", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
test.skip(isDendrite, "No need to test on other HS, this is a client bug reproduction");
|
||||
test.use({
|
||||
config: {
|
||||
features: {
|
||||
feature_video_rooms: true,
|
||||
feature_element_call_video_rooms: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const fakeCallClientSend = readFile("playwright/sample-files/fake-element-call-with-send.html", "utf-8");
|
||||
|
||||
let charlie: Bot;
|
||||
test.use({
|
||||
room: async ({ page, app, user, homeserver, bot }, use) => {
|
||||
charlie = new Bot(page, homeserver, { displayName: "Charlie" });
|
||||
await charlie.prepareClient();
|
||||
const roomId = await app.client.createRoom({
|
||||
name: "VideoRoom",
|
||||
invite: [bot.credentials.userId, charlie.credentials.userId],
|
||||
creation_content: {
|
||||
type: "org.matrix.msc3417.call",
|
||||
},
|
||||
});
|
||||
await app.client.createRoom({
|
||||
name: "OtherRoom",
|
||||
});
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page, user, app }) => {
|
||||
// use a specific widget to reproduce the bug.
|
||||
// Mock a widget page. We use a fake version of Element Call here.
|
||||
// We should match on things after .html as these widgets get a ton of extra params.
|
||||
await page.route(/\/widget-with-send.html.+/, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
// Do enough to
|
||||
body: (await fakeCallClientSend).replace("widgetCodeHere", await widgetApi),
|
||||
});
|
||||
});
|
||||
await app.settings.setValue(
|
||||
"Developer.elementCallUrl",
|
||||
null,
|
||||
SettingLevel.DEVICE,
|
||||
new URL("/widget-with-send.html#", page.url()).toString(),
|
||||
);
|
||||
});
|
||||
|
||||
test("Switching rooms should not leak widgets", async ({ page, user, room, app }) => {
|
||||
await app.viewRoomByName("VideoRoom");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Approve widget permissions" })).toBeVisible();
|
||||
// approve
|
||||
await page.getByTestId("dialog-primary-button").click();
|
||||
|
||||
// Switch back and forth a few times to trigger the bug.
|
||||
|
||||
await app.viewRoomByName("OtherRoom");
|
||||
await app.viewRoomByName("VideoRoom");
|
||||
await app.viewRoomByName("OtherRoom");
|
||||
await app.viewRoomByName("VideoRoom");
|
||||
|
||||
// For this test we want to display the chat area alongside the widget
|
||||
await page.getByRole("button", { name: "Chat" }).click();
|
||||
|
||||
await page
|
||||
.locator('iframe[title="Element Call"]')
|
||||
.contentFrame()
|
||||
.getByRole("button", { name: "Send Room Message" })
|
||||
.click();
|
||||
|
||||
const messageSent = await page.getByText("I sent this once!!").count();
|
||||
|
||||
expect(messageSent).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
53
playwright/sample-files/fake-element-call-with-send.html
Normal file
53
playwright/sample-files/fake-element-call-with-send.html
Normal file
@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<style>
|
||||
body {
|
||||
background: rgb(139, 192, 253);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- element-call.spec.ts will insert the widget API in this block -->
|
||||
<script>
|
||||
widgetCodeHere;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p>Fake Element Call</p>
|
||||
<p>State: <span id="state">Loading</span></p>
|
||||
<button id="send-button">Send Room Message</button>
|
||||
</div>
|
||||
|
||||
<!-- Minimal fake implementation of Element Call. Just enough for testing the leagkin widgets.-->
|
||||
<script>
|
||||
const stateIndicator = document.querySelector("#state");
|
||||
const { WidgetApi, WidgetApiToWidgetAction, MatrixCapabilities } = mxwidgets();
|
||||
const widgetId = new URLSearchParams(window.location.search).get("widgetId");
|
||||
const params = new URLSearchParams(window.location.hash.slice(1));
|
||||
|
||||
const roomId = params.get("roomId");
|
||||
const api = new WidgetApi(widgetId, "*");
|
||||
|
||||
document.querySelector("#send-button").onclick = async () => {
|
||||
await api.sendRoomEvent(
|
||||
"m.room.message",
|
||||
{ msgtype: "m.text", body: "I sent this once!!" },
|
||||
roomId,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
};
|
||||
|
||||
api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
|
||||
api.requestCapability(`org.matrix.msc2762.timeline:${roomId}`);
|
||||
api.requestCapabilityToSendMessage("m.text");
|
||||
|
||||
api.on("ready", (ev) => {
|
||||
stateIndicator.innerHTML = "Ready";
|
||||
});
|
||||
|
||||
// Start the messaging
|
||||
api.start();
|
||||
|
||||
// If waitForIframeLoad is false, tell the client that we're good to go
|
||||
api.sendContentLoaded();
|
||||
</script>
|
||||
@ -33,12 +33,14 @@ import {
|
||||
EventType,
|
||||
type IContent,
|
||||
MatrixError,
|
||||
type MatrixEvent,
|
||||
Direction,
|
||||
THREAD_RELATION_TYPE,
|
||||
type SendDelayedEventResponse,
|
||||
type StateEvents,
|
||||
type TimelineEvents,
|
||||
type Room,
|
||||
type SendDelayedEventRequestOpts,
|
||||
type MatrixClient,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import {
|
||||
@ -122,6 +124,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`);
|
||||
this.allowedCapabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent);
|
||||
this.allowedCapabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent);
|
||||
this.allowedCapabilities.add(MatrixCapabilities.MSC4354SendStickyEvent);
|
||||
|
||||
this.allowedCapabilities.add(
|
||||
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomName).raw,
|
||||
@ -288,6 +291,13 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
return allAllowed;
|
||||
}
|
||||
|
||||
private getSendEventTarget(roomId: string | null = null): { client: MatrixClient; roomId: string } {
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
roomId = roomId || SdkContextClass.instance.roomViewStore.getRoomId() || null;
|
||||
if (!roomId) throw new Error("No room specified and no room in RoomViewStore focus.");
|
||||
return { client, roomId };
|
||||
}
|
||||
|
||||
public async sendEvent<K extends keyof StateEvents>(
|
||||
eventType: K,
|
||||
content: StateEvents[K],
|
||||
@ -306,10 +316,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
stateKey: string | null = null,
|
||||
targetRoomId: string | null = null,
|
||||
): Promise<ISendEventDetails> {
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
|
||||
if (!client || !roomId) throw new Error("Not in a room or not attached to a client");
|
||||
const { client, roomId } = this.getSendEventTarget(targetRoomId);
|
||||
|
||||
let r: { event_id: string } | null;
|
||||
if (stateKey !== null) {
|
||||
@ -348,6 +355,42 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
return { roomId, eventId: r.event_id };
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental Part of MSC4354
|
||||
* @see {@link WidgetDriver#sendStickyEvent}
|
||||
*/
|
||||
public async sendStickyEvent(
|
||||
stickyDurationMs: number,
|
||||
eventType: string,
|
||||
content: unknown,
|
||||
targetRoomId?: string | null,
|
||||
): Promise<ISendEventDetails> {
|
||||
const { client, roomId } = this.getSendEventTarget(targetRoomId);
|
||||
|
||||
const r = await client._unstable_sendStickyEvent(
|
||||
roomId,
|
||||
stickyDurationMs,
|
||||
null,
|
||||
eventType as keyof TimelineEvents,
|
||||
content as TimelineEvents[keyof TimelineEvents] & { msc4354_sticky_key: string },
|
||||
);
|
||||
return { roomId, eventId: r.event_id };
|
||||
}
|
||||
|
||||
private getSendDelayedEventOpts(delay: number | null, parentDelayId: string | null): SendDelayedEventRequestOpts {
|
||||
if (delay !== null) {
|
||||
return {
|
||||
delay,
|
||||
...(parentDelayId !== null && { parent_delay_id: parentDelayId }),
|
||||
};
|
||||
} else if (parentDelayId !== null) {
|
||||
return {
|
||||
parent_delay_id: parentDelayId,
|
||||
};
|
||||
}
|
||||
throw new Error("Must provide at least one of delay or parentDelayId");
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental Part of MSC4140 & MSC4157
|
||||
* @see {@link WidgetDriver#sendDelayedEvent}
|
||||
@ -379,24 +422,8 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
stateKey: string | null = null,
|
||||
targetRoomId: string | null = null,
|
||||
): Promise<ISendDelayedEventDetails> {
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
|
||||
if (!client || !roomId) throw new Error("Not in a room or not attached to a client");
|
||||
|
||||
let delayOpts;
|
||||
if (delay !== null) {
|
||||
delayOpts = {
|
||||
delay,
|
||||
...(parentDelayId !== null && { parent_delay_id: parentDelayId }),
|
||||
};
|
||||
} else if (parentDelayId !== null) {
|
||||
delayOpts = {
|
||||
parent_delay_id: parentDelayId,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Must provide at least one of delay or parentDelayId");
|
||||
}
|
||||
const { client, roomId } = this.getSendEventTarget(targetRoomId);
|
||||
const delayOpts = this.getSendDelayedEventOpts(delay, parentDelayId);
|
||||
|
||||
let r: SendDelayedEventResponse | null;
|
||||
if (stateKey !== null) {
|
||||
@ -425,13 +452,39 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental Part of MSC4354
|
||||
* @see {@link WidgetDriver#sendStickyEvent}
|
||||
*/
|
||||
public async sendDelayedStickyEvent(
|
||||
delay: number | null,
|
||||
parentDelayId: string | null,
|
||||
stickyDurationMs: number,
|
||||
eventType: string,
|
||||
content: unknown,
|
||||
targetRoomId?: string | null,
|
||||
): Promise<ISendDelayedEventDetails> {
|
||||
const { client, roomId } = this.getSendEventTarget(targetRoomId);
|
||||
const delayOpts = this.getSendDelayedEventOpts(delay, parentDelayId);
|
||||
|
||||
const r = await client._unstable_sendStickyDelayedEvent(
|
||||
roomId,
|
||||
stickyDurationMs,
|
||||
delayOpts,
|
||||
null,
|
||||
eventType as keyof TimelineEvents,
|
||||
content as TimelineEvents[keyof TimelineEvents] & { msc4354_sticky_key: string },
|
||||
);
|
||||
return { roomId, delayId: r.delay_id };
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental Part of MSC4140 & MSC4157
|
||||
*/
|
||||
public async cancelScheduledDelayedEvent(delayId: string): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
if (!client) throw new Error("Not in a room or not attached to a client");
|
||||
if (!client) throw new Error("Not attached to a client");
|
||||
|
||||
await client._unstable_cancelScheduledDelayedEvent(delayId);
|
||||
}
|
||||
@ -442,7 +495,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
public async restartScheduledDelayedEvent(delayId: string): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
if (!client) throw new Error("Not in a room or not attached to a client");
|
||||
if (!client) throw new Error("Not attached to a client");
|
||||
|
||||
await client._unstable_restartScheduledDelayedEvent(delayId);
|
||||
}
|
||||
@ -453,7 +506,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
public async sendScheduledDelayedEvent(delayId: string): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
if (!client) throw new Error("Not in a room or not attached to a client");
|
||||
if (!client) throw new Error("Not attached to a client");
|
||||
|
||||
await client._unstable_sendScheduledDelayedEvent(delayId);
|
||||
}
|
||||
@ -510,6 +563,38 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generator function that retrieves events for readRoomTimeline
|
||||
* @param room The room to check the timeline of.
|
||||
* @param eventType The event type to be read.
|
||||
* @param msgtype The msgtype of the events to be read, if applicable/defined.
|
||||
* @param stateKey The state key of the events to be read, if applicable/defined.
|
||||
* @param limit The maximum number of events to retrieve. Will be zero to denote "as many as
|
||||
* possible".
|
||||
* @param since When null, retrieves the number of events specified by the "limit" parameter.
|
||||
* Otherwise, the event ID at which only subsequent events will be returned, as many as specified
|
||||
* in "limit".
|
||||
* @returns A generator that emits events.
|
||||
*/
|
||||
private *readRoomTimelineIterator(
|
||||
room: Room,
|
||||
eventType: string,
|
||||
msgtype: string | undefined,
|
||||
stateKey: string | undefined,
|
||||
limit: number,
|
||||
since: string | undefined,
|
||||
): Generator<IRoomEvent, void, void> {
|
||||
let resultCount: number = 0;
|
||||
const events = [...room.getLiveTimeline().getEvents()]; // timelines are most recent last
|
||||
for (let ev = events.pop(); ev && resultCount < limit && ev.getId() !== since; ev = events.pop()) {
|
||||
if (ev.getType() !== eventType) continue;
|
||||
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
|
||||
if (stateKey !== undefined && ev.getStateKey() !== stateKey) continue;
|
||||
yield ev.getEffectiveEvent() as IRoomEvent;
|
||||
resultCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all events of the given type, and optionally `msgtype` (if applicable/defined),
|
||||
* the user has access to. The widget API will have already verified that the widget is
|
||||
@ -535,23 +620,9 @@ export class ElementWidgetDriver extends WidgetDriver {
|
||||
since: string | undefined,
|
||||
): Promise<IRoomEvent[]> {
|
||||
limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
|
||||
|
||||
const room = MatrixClientPeg.safeGet().getRoom(roomId);
|
||||
if (room === null) return [];
|
||||
const results: MatrixEvent[] = [];
|
||||
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
|
||||
for (let i = events.length - 1; i >= 0; i--) {
|
||||
const ev = events[i];
|
||||
if (results.length >= limit) break;
|
||||
if (since !== undefined && ev.getId() === since) break;
|
||||
|
||||
if (ev.getType() !== eventType) continue;
|
||||
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
|
||||
if (stateKey !== undefined && ev.getStateKey() !== stateKey) continue;
|
||||
results.push(ev);
|
||||
}
|
||||
|
||||
return results.map((e) => e.getEffectiveEvent() as IRoomEvent);
|
||||
return [...this.readRoomTimelineIterator(room, eventType, msgtype, stateKey, limit, since)];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -508,6 +508,8 @@ export class WidgetMessaging extends TypedEventEmitter<WidgetMessagingEvent, Wid
|
||||
}
|
||||
|
||||
this.emit(WidgetMessagingEvent.Stop, this.widgetApi);
|
||||
this.widgetApi?.stop();
|
||||
// XXX is the removeAllListeners necessary here?
|
||||
this.widgetApi?.removeAllListeners(); // Insurance against resource leaks
|
||||
this.widgetApi = null;
|
||||
this.iframe = null;
|
||||
@ -538,10 +540,8 @@ export class WidgetMessaging extends TypedEventEmitter<WidgetMessagingEvent, Wid
|
||||
});
|
||||
};
|
||||
|
||||
private onToDeviceMessage = async (payload: ReceivedToDeviceMessage): Promise<void> => {
|
||||
const { message, encryptionInfo } = payload;
|
||||
// TODO: Update the widget API to use a proper IToDeviceMessage instead of a IRoomEvent
|
||||
await this.widgetApi?.feedToDevice(message as IRoomEvent, encryptionInfo != null);
|
||||
private onToDeviceMessage = async ({ message, encryptionInfo }: ReceivedToDeviceMessage): Promise<void> => {
|
||||
await this.widgetApi?.feedToDevice(message, encryptionInfo != null);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -308,6 +308,8 @@ export function createTestClient(): MatrixClient {
|
||||
_unstable_cancelScheduledDelayedEvent: jest.fn(),
|
||||
_unstable_restartScheduledDelayedEvent: jest.fn(),
|
||||
_unstable_sendScheduledDelayedEvent: jest.fn(),
|
||||
_unstable_sendStickyEvent: jest.fn(),
|
||||
_unstable_sendStickyDelayedEvent: jest.fn(),
|
||||
|
||||
searchUserDirectory: jest.fn().mockResolvedValue({ limited: false, results: [] }),
|
||||
setDeviceVerified: jest.fn(),
|
||||
|
||||
@ -131,6 +131,7 @@ describe("ElementWidgetDriver", () => {
|
||||
"org.matrix.msc3819.receive.to_device:m.call.replaces",
|
||||
"org.matrix.msc4157.send.delayed_event",
|
||||
"org.matrix.msc4157.update_delayed_event",
|
||||
"org.matrix.msc4354.send_sticky_event",
|
||||
// RTC decline events (send/receive, unstable/stable)
|
||||
"org.matrix.msc2762.send.event:org.matrix.msc4310.rtc.decline",
|
||||
"org.matrix.msc2762.send.event:m.rtc.decline",
|
||||
@ -593,6 +594,84 @@ describe("ElementWidgetDriver", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendStickyEvent", () => {
|
||||
let driver: WidgetDriver;
|
||||
const roomId = "!this-room-id";
|
||||
|
||||
beforeEach(() => {
|
||||
driver = mkDefaultDriver();
|
||||
});
|
||||
|
||||
it("sends sticky message events", async () => {
|
||||
client._unstable_sendStickyEvent.mockResolvedValue({
|
||||
event_id: "id",
|
||||
});
|
||||
|
||||
await expect(driver.sendStickyEvent(2000, EventType.RoomMessage, {})).resolves.toEqual({
|
||||
roomId,
|
||||
eventId: "id",
|
||||
});
|
||||
|
||||
expect(client._unstable_sendStickyEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
2000,
|
||||
null,
|
||||
EventType.RoomMessage,
|
||||
{},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendDelayedStickyEvent", () => {
|
||||
let driver: WidgetDriver;
|
||||
const roomId = "!this-room-id";
|
||||
|
||||
beforeEach(() => {
|
||||
driver = mkDefaultDriver();
|
||||
});
|
||||
|
||||
it("sends delayed sticky message events", async () => {
|
||||
client._unstable_sendStickyDelayedEvent.mockResolvedValue({
|
||||
delay_id: "id",
|
||||
});
|
||||
|
||||
await expect(driver.sendDelayedStickyEvent(1000, null, 2000, EventType.RoomMessage, {})).resolves.toEqual({
|
||||
roomId,
|
||||
delayId: "id",
|
||||
});
|
||||
|
||||
expect(client._unstable_sendStickyDelayedEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
2000,
|
||||
{ delay: 1000 },
|
||||
null,
|
||||
EventType.RoomMessage,
|
||||
{},
|
||||
);
|
||||
});
|
||||
it("sends child action delayed sticky message events", async () => {
|
||||
client._unstable_sendStickyDelayedEvent.mockResolvedValue({
|
||||
delay_id: "id-child",
|
||||
});
|
||||
|
||||
await expect(
|
||||
driver.sendDelayedStickyEvent(null, "id-parent", 2000, EventType.RoomMessage, {}),
|
||||
).resolves.toEqual({
|
||||
roomId,
|
||||
delayId: "id-child",
|
||||
});
|
||||
|
||||
expect(client._unstable_sendStickyDelayedEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
2000,
|
||||
{ parent_delay_id: "id-parent" },
|
||||
null,
|
||||
EventType.RoomMessage,
|
||||
{},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("If the feature_dynamic_room_predecessors feature is not enabled", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@ -1577,17 +1577,8 @@
|
||||
yaml "^2.7.0"
|
||||
|
||||
"@element-hq/web-shared-components@link:packages/shared-components":
|
||||
version "0.0.0-test.12"
|
||||
dependencies:
|
||||
"@element-hq/element-web-module-api" "^1.8.0"
|
||||
"@vector-im/compound-design-tokens" "^6.3.0"
|
||||
classnames "^2.5.1"
|
||||
counterpart "^0.18.6"
|
||||
lodash "^4.17.21"
|
||||
matrix-web-i18n "^3.4.0"
|
||||
patch-package "^8.0.1"
|
||||
react-merge-refs "^3.0.2"
|
||||
temporal-polyfill "^0.3.0"
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@emnapi/core@^1.4.3":
|
||||
version "1.7.0"
|
||||
@ -4223,6 +4214,7 @@
|
||||
|
||||
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@vector-im/matrix-wysiwyg@2.40.0":
|
||||
version "2.40.0"
|
||||
@ -9693,7 +9685,7 @@ matrix-web-i18n@^3.2.1, matrix-web-i18n@^3.4.0:
|
||||
minimist "^1.2.8"
|
||||
walk "^2.3.15"
|
||||
|
||||
matrix-widget-api@^1.14.0:
|
||||
matrix-widget-api@^1.10.0, matrix-widget-api@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.15.0.tgz#a508f72a5993a95382bdf890bd9e54525295b321"
|
||||
integrity sha512-Yu9rX9wyF3A1sqviKgiYHz8aGgL3HhJe9OXKi/lccr1eZnNb6y+ELdbshTjs+VLKM4rkTWt6CE3THsw3f/CZhg==
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user