Widget permissions customizations using module api (#10121)
* Using module api to customize widget permissions Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net> * Revert type export and use ComponentProps instead. Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net> --------- Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net> Co-authored-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net>
This commit is contained in:
parent
9a0e537916
commit
7b77f76486
11 changed files with 241 additions and 31 deletions
|
@ -133,6 +133,7 @@ describe("Stickers", () => {
|
||||||
type: "m.stickerpicker",
|
type: "m.stickerpicker",
|
||||||
name: STICKER_PICKER_WIDGET_NAME,
|
name: STICKER_PICKER_WIDGET_NAME,
|
||||||
url: stickerPickerUrl,
|
url: stickerPickerUrl,
|
||||||
|
creatorUserId: "@userId",
|
||||||
},
|
},
|
||||||
id: STICKER_PICKER_WIDGET_ID,
|
id: STICKER_PICKER_WIDGET_ID,
|
||||||
},
|
},
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@matrix-org/analytics-events": "^0.4.0",
|
"@matrix-org/analytics-events": "^0.4.0",
|
||||||
"@matrix-org/matrix-wysiwyg": "^1.1.1",
|
"@matrix-org/matrix-wysiwyg": "^1.1.1",
|
||||||
"@matrix-org/react-sdk-module-api": "^0.0.3",
|
"@matrix-org/react-sdk-module-api": "^0.0.4",
|
||||||
"@sentry/browser": "^7.0.0",
|
"@sentry/browser": "^7.0.0",
|
||||||
"@sentry/tracing": "^7.0.0",
|
"@sentry/tracing": "^7.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { MatrixCapabilities } from "matrix-widget-api";
|
import { MatrixCapabilities } from "matrix-widget-api";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
|
||||||
|
|
||||||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
||||||
import { ChevronFace } from "../../structures/ContextMenu";
|
import { ChevronFace } from "../../structures/ContextMenu";
|
||||||
|
@ -34,6 +35,8 @@ import { WidgetType } from "../../../widgets/WidgetType";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||||
import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../../Livestream";
|
import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../../Livestream";
|
||||||
|
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
||||||
|
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
|
||||||
|
|
||||||
interface IProps extends React.ComponentProps<typeof IconizedContextMenu> {
|
interface IProps extends React.ComponentProps<typeof IconizedContextMenu> {
|
||||||
app: IApp;
|
app: IApp;
|
||||||
|
@ -45,7 +48,7 @@ interface IProps extends React.ComponentProps<typeof IconizedContextMenu> {
|
||||||
onEditClick?(): void;
|
onEditClick?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WidgetContextMenu: React.FC<IProps> = ({
|
export const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
onFinished,
|
onFinished,
|
||||||
app,
|
app,
|
||||||
userWidget,
|
userWidget,
|
||||||
|
@ -158,24 +161,31 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
const isLocalWidget = WidgetType.JITSI.matches(app.type);
|
const isLocalWidget = WidgetType.JITSI.matches(app.type);
|
||||||
let revokeButton;
|
let revokeButton;
|
||||||
if (!userWidget && !isLocalWidget && isAllowedWidget) {
|
if (!userWidget && !isLocalWidget && isAllowedWidget) {
|
||||||
const onRevokeClick = (): void => {
|
const opts: ApprovalOpts = { approved: undefined };
|
||||||
logger.info("Revoking permission for widget to load: " + app.eventId);
|
ModuleRunner.instance.invoke(WidgetLifecycle.PreLoadRequest, opts, new ElementWidget(app));
|
||||||
const current = SettingsStore.getValue("allowedWidgets", roomId);
|
|
||||||
if (app.eventId !== undefined) current[app.eventId] = false;
|
|
||||||
const level = SettingsStore.firstSupportedLevel("allowedWidgets");
|
|
||||||
SettingsStore.setValue("allowedWidgets", roomId, level, current).catch((err) => {
|
|
||||||
logger.error(err);
|
|
||||||
// We don't really need to do anything about this - the user will just hit the button again.
|
|
||||||
});
|
|
||||||
onFinished();
|
|
||||||
};
|
|
||||||
|
|
||||||
revokeButton = <IconizedContextMenuOption onClick={onRevokeClick} label={_t("Revoke permissions")} />;
|
if (!opts.approved) {
|
||||||
|
const onRevokeClick = (): void => {
|
||||||
|
logger.info("Revoking permission for widget to load: " + app.eventId);
|
||||||
|
const current = SettingsStore.getValue("allowedWidgets", roomId);
|
||||||
|
if (app.eventId !== undefined) current[app.eventId] = false;
|
||||||
|
const level = SettingsStore.firstSupportedLevel("allowedWidgets");
|
||||||
|
if (!level) throw new Error("level must be defined");
|
||||||
|
SettingsStore.setValue("allowedWidgets", roomId ?? null, level, current).catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
// We don't really need to do anything about this - the user will just hit the button again.
|
||||||
|
});
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
revokeButton = <IconizedContextMenuOption onClick={onRevokeClick} label={_t("Revoke permissions")} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let moveLeftButton;
|
let moveLeftButton;
|
||||||
if (showUnpin && widgetIndex > 0) {
|
if (showUnpin && widgetIndex > 0) {
|
||||||
const onClick = (): void => {
|
const onClick = (): void => {
|
||||||
|
if (!room) throw new Error("room must be defined");
|
||||||
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, -1);
|
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, -1);
|
||||||
onFinished();
|
onFinished();
|
||||||
};
|
};
|
||||||
|
@ -207,5 +217,3 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
</IconizedContextMenu>
|
</IconizedContextMenu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WidgetContextMenu;
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import classNames from "classnames";
|
||||||
import { MatrixCapabilities } from "matrix-widget-api";
|
import { MatrixCapabilities } from "matrix-widget-api";
|
||||||
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
|
||||||
|
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
@ -36,7 +37,7 @@ import { aboveLeftOf, ContextMenuButton } from "../../structures/ContextMenu";
|
||||||
import PersistedElement, { getPersistKey } from "./PersistedElement";
|
import PersistedElement, { getPersistKey } from "./PersistedElement";
|
||||||
import { WidgetType } from "../../../widgets/WidgetType";
|
import { WidgetType } from "../../../widgets/WidgetType";
|
||||||
import { ElementWidget, StopGapWidget } from "../../../stores/widgets/StopGapWidget";
|
import { ElementWidget, StopGapWidget } from "../../../stores/widgets/StopGapWidget";
|
||||||
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
|
||||||
import WidgetAvatar from "../avatars/WidgetAvatar";
|
import WidgetAvatar from "../avatars/WidgetAvatar";
|
||||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||||
import { IApp } from "../../../stores/WidgetStore";
|
import { IApp } from "../../../stores/WidgetStore";
|
||||||
|
@ -50,6 +51,7 @@ import { Action } from "../../../dispatcher/actions";
|
||||||
import { ElementWidgetCapabilities } from "../../../stores/widgets/ElementWidgetCapabilities";
|
import { ElementWidgetCapabilities } from "../../../stores/widgets/ElementWidgetCapabilities";
|
||||||
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
|
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
|
||||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||||
|
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
app: IApp;
|
app: IApp;
|
||||||
|
@ -162,6 +164,9 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
private hasPermissionToLoad = (props: IProps): boolean => {
|
private hasPermissionToLoad = (props: IProps): boolean => {
|
||||||
if (this.usingLocalWidget()) return true;
|
if (this.usingLocalWidget()) return true;
|
||||||
if (!props.room) return true; // user widgets always have permissions
|
if (!props.room) return true; // user widgets always have permissions
|
||||||
|
const opts: ApprovalOpts = { approved: undefined };
|
||||||
|
ModuleRunner.instance.invoke(WidgetLifecycle.PreLoadRequest, opts, new ElementWidget(this.props.app));
|
||||||
|
if (opts.approved) return true;
|
||||||
|
|
||||||
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", props.room.roomId);
|
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", props.room.roomId);
|
||||||
const allowed = props.app.eventId !== undefined && (currentlyAllowedWidgets[props.app.eventId] ?? false);
|
const allowed = props.app.eventId !== undefined && (currentlyAllowedWidgets[props.app.eventId] ?? false);
|
||||||
|
|
|
@ -40,7 +40,7 @@ import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
import { UIComponent, UIFeature } from "../../../settings/UIFeature";
|
import { UIComponent, UIFeature } from "../../../settings/UIFeature";
|
||||||
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||||
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
|
||||||
import { useRoomMemberCount } from "../../../hooks/useRoomMembers";
|
import { useRoomMemberCount } from "../../../hooks/useRoomMembers";
|
||||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||||
import { usePinnedEvents } from "./PinnedMessagesCard";
|
import { usePinnedEvents } from "./PinnedMessagesCard";
|
||||||
|
|
|
@ -24,7 +24,7 @@ import AppTile from "../elements/AppTile";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { useWidgets } from "./RoomSummaryCard";
|
import { useWidgets } from "./RoomSummaryCard";
|
||||||
import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
|
import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
|
||||||
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
|
||||||
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||||
import UIStore from "../../../stores/UIStore";
|
import UIStore from "../../../stores/UIStore";
|
||||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||||
|
|
|
@ -39,6 +39,11 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||||
import { Direction } from "matrix-js-sdk/src/matrix";
|
import { Direction } from "matrix-js-sdk/src/matrix";
|
||||||
|
import {
|
||||||
|
ApprovalOpts,
|
||||||
|
CapabilitiesOpts,
|
||||||
|
WidgetLifecycle,
|
||||||
|
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
|
||||||
|
|
||||||
import SdkConfig, { DEFAULTS } from "../../SdkConfig";
|
import SdkConfig, { DEFAULTS } from "../../SdkConfig";
|
||||||
import { iterableDiff, iterableIntersection } from "../../utils/iterables";
|
import { iterableDiff, iterableIntersection } from "../../utils/iterables";
|
||||||
|
@ -55,6 +60,7 @@ import dis from "../../dispatcher/dispatcher";
|
||||||
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
|
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
|
||||||
import { navigateToPermalink } from "../../utils/permalinks/navigator";
|
import { navigateToPermalink } from "../../utils/permalinks/navigator";
|
||||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||||
|
import { ModuleRunner } from "../../modules/ModuleRunner";
|
||||||
|
|
||||||
// TODO: Purge this from the universe
|
// TODO: Purge this from the universe
|
||||||
|
|
||||||
|
@ -171,15 +177,22 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
allowedSoFar.add(cap);
|
allowedSoFar.add(cap);
|
||||||
missing.delete(cap);
|
missing.delete(cap);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let approved: Set<string> | undefined;
|
||||||
if (WidgetPermissionCustomisations.preapproveCapabilities) {
|
if (WidgetPermissionCustomisations.preapproveCapabilities) {
|
||||||
const approved = await WidgetPermissionCustomisations.preapproveCapabilities(this.forWidget, requested);
|
approved = await WidgetPermissionCustomisations.preapproveCapabilities(this.forWidget, requested);
|
||||||
if (approved) {
|
} else {
|
||||||
approved.forEach((cap) => {
|
const opts: CapabilitiesOpts = { approvedCapabilities: undefined };
|
||||||
allowedSoFar.add(cap);
|
ModuleRunner.instance.invoke(WidgetLifecycle.CapabilitiesRequest, opts, this.forWidget, requested);
|
||||||
missing.delete(cap);
|
approved = opts.approvedCapabilities;
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (approved) {
|
||||||
|
approved.forEach((cap) => {
|
||||||
|
allowedSoFar.add(cap);
|
||||||
|
missing.delete(cap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Do something when the widget requests new capabilities not yet asked for
|
// TODO: Do something when the widget requests new capabilities not yet asked for
|
||||||
let rememberApproved = false;
|
let rememberApproved = false;
|
||||||
if (missing.size > 0) {
|
if (missing.size > 0) {
|
||||||
|
@ -366,6 +379,15 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
|
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
|
||||||
|
const opts: ApprovalOpts = { approved: undefined };
|
||||||
|
ModuleRunner.instance.invoke(WidgetLifecycle.IdentityRequest, opts, this.forWidget);
|
||||||
|
if (opts.approved) {
|
||||||
|
return observer.update({
|
||||||
|
state: OpenIDRequestState.Allowed,
|
||||||
|
token: await MatrixClientPeg.get().getOpenIdToken(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const oidcState = SdkContextClass.instance.widgetPermissionStore.getOIDCState(
|
const oidcState = SdkContextClass.instance.widgetPermissionStore.getOIDCState(
|
||||||
this.forWidget,
|
this.forWidget,
|
||||||
this.forWidgetKind,
|
this.forWidgetKind,
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 Mikhail Aheichyk
|
||||||
|
Copyright 2023 Nordeck IT + Consulting GmbH.
|
||||||
|
|
||||||
|
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 React, { ComponentProps } from "react";
|
||||||
|
import { screen, render } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { MatrixWidgetType } from "matrix-widget-api";
|
||||||
|
import {
|
||||||
|
ApprovalOpts,
|
||||||
|
WidgetInfo,
|
||||||
|
WidgetLifecycle,
|
||||||
|
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
|
||||||
|
|
||||||
|
import { WidgetContextMenu } from "../../../../src/components/views/context_menus/WidgetContextMenu";
|
||||||
|
import { IApp } from "../../../../src/stores/WidgetStore";
|
||||||
|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
|
import WidgetUtils from "../../../../src/utils/WidgetUtils";
|
||||||
|
import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
|
||||||
|
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||||
|
|
||||||
|
describe("<WidgetContextMenu />", () => {
|
||||||
|
const widgetId = "w1";
|
||||||
|
const eventId = "e1";
|
||||||
|
const roomId = "r1";
|
||||||
|
const userId = "@user-id:server";
|
||||||
|
|
||||||
|
const app: IApp = {
|
||||||
|
id: widgetId,
|
||||||
|
eventId,
|
||||||
|
roomId,
|
||||||
|
type: MatrixWidgetType.Custom,
|
||||||
|
url: "https://example.com",
|
||||||
|
name: "Example 1",
|
||||||
|
creatorUserId: userId,
|
||||||
|
avatar_url: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockClient = {
|
||||||
|
getUserId: jest.fn().mockReturnValue(userId),
|
||||||
|
} as unknown as MatrixClient;
|
||||||
|
|
||||||
|
let onFinished: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
onFinished = jest.fn();
|
||||||
|
jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
function getComponent(props: Partial<ComponentProps<typeof WidgetContextMenu>> = {}): JSX.Element {
|
||||||
|
return (
|
||||||
|
<MatrixClientContext.Provider value={mockClient}>
|
||||||
|
<WidgetContextMenu app={app} onFinished={onFinished} {...props} />
|
||||||
|
</MatrixClientContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("renders revoke button", async () => {
|
||||||
|
const { rerender } = render(getComponent());
|
||||||
|
|
||||||
|
const revokeButton = screen.getByLabelText("Revoke permissions");
|
||||||
|
expect(revokeButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation((lifecycleEvent, opts, widgetInfo) => {
|
||||||
|
if (lifecycleEvent === WidgetLifecycle.PreLoadRequest && (widgetInfo as WidgetInfo).id === widgetId) {
|
||||||
|
(opts as ApprovalOpts).approved = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rerender(getComponent());
|
||||||
|
expect(revokeButton).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("revokes permissions", async () => {
|
||||||
|
render(getComponent());
|
||||||
|
await userEvent.click(screen.getByLabelText("Revoke permissions"));
|
||||||
|
expect(onFinished).toHaveBeenCalled();
|
||||||
|
expect(SettingsStore.getValue("allowedWidgets", roomId)[eventId]).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -23,6 +23,11 @@ import { act, render, RenderResult } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { SpiedFunction } from "jest-mock";
|
import { SpiedFunction } from "jest-mock";
|
||||||
|
import {
|
||||||
|
ApprovalOpts,
|
||||||
|
WidgetInfo,
|
||||||
|
WidgetLifecycle,
|
||||||
|
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
|
||||||
|
|
||||||
import RightPanel from "../../../../src/components/structures/RightPanel";
|
import RightPanel from "../../../../src/components/structures/RightPanel";
|
||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
|
@ -44,6 +49,7 @@ import AppsDrawer from "../../../../src/components/views/rooms/AppsDrawer";
|
||||||
import { ElementWidgetCapabilities } from "../../../../src/stores/widgets/ElementWidgetCapabilities";
|
import { ElementWidgetCapabilities } from "../../../../src/stores/widgets/ElementWidgetCapabilities";
|
||||||
import { ElementWidget } from "../../../../src/stores/widgets/StopGapWidget";
|
import { ElementWidget } from "../../../../src/stores/widgets/StopGapWidget";
|
||||||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
||||||
|
import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
|
||||||
|
|
||||||
describe("AppTile", () => {
|
describe("AppTile", () => {
|
||||||
let cli: MatrixClient;
|
let cli: MatrixClient;
|
||||||
|
@ -380,4 +386,21 @@ describe("AppTile", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("for a pinned widget permission load", () => {
|
||||||
|
jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation((lifecycleEvent, opts, widgetInfo) => {
|
||||||
|
if (lifecycleEvent === WidgetLifecycle.PreLoadRequest && (widgetInfo as WidgetInfo).id === app1.id) {
|
||||||
|
(opts as ApprovalOpts).approved = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// userId and creatorUserId are different
|
||||||
|
const renderResult = render(
|
||||||
|
<MatrixClientContext.Provider value={cli}>
|
||||||
|
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
|
||||||
|
</MatrixClientContext.Provider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(renderResult.queryByRole("button", { name: "Continue" })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,12 +18,27 @@ import { mocked, MockedObject } from "jest-mock";
|
||||||
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "matrix-js-sdk/src/client";
|
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "matrix-js-sdk/src/client";
|
||||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||||
import { Direction, EventType, MatrixEvent, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
|
import { Direction, EventType, MatrixEvent, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
|
||||||
import { Widget, MatrixWidgetType, WidgetKind, WidgetDriver, ITurnServer } from "matrix-widget-api";
|
import {
|
||||||
|
Widget,
|
||||||
|
MatrixWidgetType,
|
||||||
|
WidgetKind,
|
||||||
|
WidgetDriver,
|
||||||
|
ITurnServer,
|
||||||
|
SimpleObservable,
|
||||||
|
OpenIDRequestState,
|
||||||
|
IOpenIDUpdate,
|
||||||
|
} from "matrix-widget-api";
|
||||||
|
import {
|
||||||
|
ApprovalOpts,
|
||||||
|
CapabilitiesOpts,
|
||||||
|
WidgetLifecycle,
|
||||||
|
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
|
||||||
|
|
||||||
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
||||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||||
import { StopGapWidgetDriver } from "../../../src/stores/widgets/StopGapWidgetDriver";
|
import { StopGapWidgetDriver } from "../../../src/stores/widgets/StopGapWidgetDriver";
|
||||||
import { stubClient } from "../../test-utils";
|
import { stubClient } from "../../test-utils";
|
||||||
|
import { ModuleRunner } from "../../../src/modules/ModuleRunner";
|
||||||
import dis from "../../../src/dispatcher/dispatcher";
|
import dis from "../../../src/dispatcher/dispatcher";
|
||||||
|
|
||||||
describe("StopGapWidgetDriver", () => {
|
describe("StopGapWidgetDriver", () => {
|
||||||
|
@ -101,6 +116,44 @@ describe("StopGapWidgetDriver", () => {
|
||||||
expect(approvedCapabilities).toEqual(requestedCapabilities);
|
expect(approvedCapabilities).toEqual(requestedCapabilities);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("approves capabilities via module api", async () => {
|
||||||
|
const driver = mkDefaultDriver();
|
||||||
|
|
||||||
|
const requestedCapabilities = new Set(["org.matrix.msc2931.navigate", "org.matrix.msc2762.timeline:*"]);
|
||||||
|
|
||||||
|
jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation(
|
||||||
|
(lifecycleEvent, opts, widgetInfo, requested) => {
|
||||||
|
if (lifecycleEvent === WidgetLifecycle.CapabilitiesRequest) {
|
||||||
|
(opts as CapabilitiesOpts).approvedCapabilities = requested;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const approvedCapabilities = await driver.validateCapabilities(requestedCapabilities);
|
||||||
|
expect(approvedCapabilities).toEqual(requestedCapabilities);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("approves identity via module api", async () => {
|
||||||
|
const driver = mkDefaultDriver();
|
||||||
|
|
||||||
|
jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation((lifecycleEvent, opts, widgetInfo) => {
|
||||||
|
if (lifecycleEvent === WidgetLifecycle.IdentityRequest) {
|
||||||
|
(opts as ApprovalOpts).approved = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const listener = jest.fn();
|
||||||
|
const observer = new SimpleObservable<IOpenIDUpdate>();
|
||||||
|
observer.onUpdate(listener);
|
||||||
|
await driver.askOpenID(observer);
|
||||||
|
|
||||||
|
const openIdUpdate: IOpenIDUpdate = {
|
||||||
|
state: OpenIDRequestState.Allowed,
|
||||||
|
token: await client.getOpenIdToken(),
|
||||||
|
};
|
||||||
|
expect(listener).toBeCalledWith(openIdUpdate);
|
||||||
|
});
|
||||||
|
|
||||||
describe("sendToDevice", () => {
|
describe("sendToDevice", () => {
|
||||||
const contentMap = {
|
const contentMap = {
|
||||||
"@alice:example.org": {
|
"@alice:example.org": {
|
||||||
|
|
|
@ -1598,10 +1598,10 @@
|
||||||
version "3.2.14"
|
version "3.2.14"
|
||||||
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984"
|
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984"
|
||||||
|
|
||||||
"@matrix-org/react-sdk-module-api@^0.0.3":
|
"@matrix-org/react-sdk-module-api@^0.0.4":
|
||||||
version "0.0.3"
|
version "0.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-0.0.3.tgz#a7ac1b18a72d18d08290b81fa33b0d8d00a77d2b"
|
resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-0.0.4.tgz#da71fc2e4c8143e87b5c2bc067ccbc0c146816fe"
|
||||||
integrity sha512-jQmLhVIanuX0g7Jx1OIqlzs0kp72PfSpv3umi55qVPYcAPQmO252AUs0vncatK8O4e013vohdnNhly19a/kmLQ==
|
integrity sha512-4gcgef3Ne9+Ae0bAErK1Swo9FxTZBDEogX/Iu2kcLWWROOKMjmeWL2PkM83ylsxZ32YY6a6ndRqV/SwRmDeJxg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.17.9"
|
"@babel/runtime" "^7.17.9"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue