Compare commits

..

2 Commits

Author SHA1 Message Date
Michael Telatynski
bc03ba64de Add modernizr check
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-01-23 09:47:45 +00:00
Michael Telatynski
859ba0e02c Switch from defer to PromiseWithResolvers
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-01-23 09:45:35 +00:00
59 changed files with 1087 additions and 1184 deletions

View File

@@ -51,7 +51,7 @@ jobs:
- name: Build and push
id: build-and-push
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6
with:
context: .
push: true

View File

@@ -104,7 +104,7 @@ jobs:
- name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: guibranco/github-status-action-v2@ecd54a02cf761e85a8fb328fe937710fd4227cda
uses: guibranco/github-status-action-v2@56cd38caf0615dd03f49d42ed301f1469911ac61
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success

View File

@@ -8,13 +8,11 @@
#### develop
The develop branch holds the very latest and greatest code we have to offer, as such it may be less stable.
It is auto-deployed on every commit to element-web or matrix-js-sdk to develop.element.io via GitHub Actions `build_develop.yml`.
The develop branch holds the very latest and greatest code we have to offer, as such it may be less stable. It corresponds to the develop.element.io CD platform.
#### staging
The staging branch corresponds to the very latest release regardless of whether it is an RC or not. Deployed to staging.element.io manually.
It is auto-deployed on every release of element-web to staging.element.io via GitHub Actions `deploy.yml`.
#### master
@@ -217,7 +215,7 @@ We ship Element Web to dockerhub, `*.element.io`, and packages.element.io.
We ship Element Desktop to packages.element.io.
- [ ] Check that element-web has shipped to dockerhub
- [ ] Check that the staging [deployment](https://github.com/element-hq/element-web/actions/workflows/deploy.yml) has completed successfully
- [ ] Deploy staging.element.io. [See docs.](https://handbook.element.io/books/element-web-team/page/deploying-appstagingelementio)
- [ ] Test staging.element.io
For final releases additionally do these steps:
@@ -227,9 +225,6 @@ For final releases additionally do these steps:
- [ ] Ensure Element Web package has shipped to packages.element.io
- [ ] Ensure Element Desktop packages have shipped to packages.element.io
If you need to roll back a deployment to staging.element.io,
you can run the `deploy.yml` automation choosing an older tag which you wish to deploy.
# Housekeeping
We have some manual housekeeping to do in order to prepare for the next release.

View File

@@ -74,7 +74,7 @@
"@types/react-dom": "18.3.5",
"oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001692",
"caniuse-lite": "1.0.30001690",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
},
@@ -128,7 +128,7 @@
"matrix-encrypt-attachment": "^1.0.3",
"matrix-events-sdk": "0.0.1",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "1.11.0",
"matrix-widget-api": "^1.10.0",
"memoize-one": "^6.0.0",
"mime": "^4.0.4",
"oidc-client-ts": "^3.0.1",
@@ -178,7 +178,7 @@
"@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "^1.40.1",
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
"@sentry/webpack-plugin": "^3.0.0",
"@sentry/webpack-plugin": "^2.7.1",
"@stylistic/eslint-plugin": "^2.9.0",
"@svgr/webpack": "^8.0.0",
"@testcontainers/postgresql": "^10.16.0",
@@ -230,7 +230,7 @@
"dotenv": "^16.0.2",
"eslint": "8.57.1",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-deprecate": "0.8.5",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^28.0.0",
@@ -287,7 +287,7 @@
"terser-webpack-plugin": "^5.3.9",
"testcontainers": "^10.16.0",
"ts-node": "^10.9.1",
"typescript": "5.7.3",
"typescript": "5.7.2",
"util": "^0.12.5",
"web-streams-polyfill": "^4.0.0",
"webpack": "^5.89.0",

View File

@@ -42,7 +42,7 @@ test.describe("Memberlist", () => {
await app.viewRoomByName(ROOM_NAME);
const memberlist = await app.toggleMemberlistPanel();
await expect(memberlist.locator(".mx_MemberTileView")).toHaveCount(4);
await expect(memberlist.getByText("Invited")).toHaveCount(1);
await expect(memberlist.getByText("(Invited)")).toHaveCount(1);
await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -19,7 +19,7 @@ import { HomeserverContainer, StartedHomeserverContainer } from "./HomeserverCon
import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
import { Api, ClientServerApi, Verb } from "../plugins/utils/api.ts";
const TAG = "develop@sha256:3594fba0d21ad44f407225baed4be0542da8abcb6e1a7e2e16d3be35c278a7cb";
const TAG = "develop@sha256:5d62b61c4373eaca25df6c6bb99fc1be92f8f40b8abebd8897bf5b2af9eb137a";
const DEFAULT_CONFIG = {
server_name: "localhost",

View File

@@ -35,8 +35,6 @@ Please see LICENSE files in the repository root for full details.
.mx_DisambiguatedProfile_mxid {
margin-inline-start: 0;
font: var(--cpd-font-body-sm-regular);
text-overflow: ellipsis;
overflow: hidden;
}
span:not(.mx_DisambiguatedProfile_mxid) {

View File

@@ -35,7 +35,6 @@ Please see LICENSE files in the repository root for full details.
font: var(--cpd-font-body-sm-regular);
font-size: 13px;
color: var(--cpd-color-text-secondary);
margin-left: var(--cpd-space-4x);
}
.mx_MemberTileView_avatar {

View File

@@ -10,7 +10,6 @@ Please see LICENSE files in the repository root for full details.
import React, { StrictMode } from "react";
import { createRoot, Root } from "react-dom/client";
import classNames from "classnames";
import { IDeferred, defer } from "matrix-js-sdk/src/utils";
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
import { Glass, TooltipProvider } from "@vector-im/compound-web";
@@ -45,7 +44,7 @@ export interface IModal<C extends ComponentType> {
onFinished: ComponentProps<C>["onFinished"];
close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
hidden?: boolean;
deferred?: IDeferred<Parameters<ComponentProps<C>["onFinished"]>>;
deferred?: PromiseWithResolvers<Parameters<ComponentProps<C>["onFinished"]>>;
}
export interface IHandle<C extends ComponentType> {
@@ -214,7 +213,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
modal: IModal<C>,
props?: ComponentProps<C>,
): [IHandle<C>["close"], IHandle<C>["finished"]] {
modal.deferred = defer<Parameters<ComponentProps<C>["onFinished"]>>();
modal.deferred = Promise.withResolvers<Parameters<ComponentProps<C>["onFinished"]>>();
return [
async (...args: Parameters<ComponentProps<C>["onFinished"]>): Promise<void> => {
if (modal.beforeClosePromise) {

View File

@@ -46,7 +46,7 @@ import {
SlidingSync,
} from "matrix-js-sdk/src/sliding-sync";
import { logger } from "matrix-js-sdk/src/logger";
import { defer, sleep } from "matrix-js-sdk/src/utils";
import { sleep } from "matrix-js-sdk/src/utils";
import SettingsStore from "./settings/SettingsStore";
import SlidingSyncController from "./settings/controllers/SlidingSyncController";
@@ -110,7 +110,7 @@ export class SlidingSyncManager {
public slidingSync?: SlidingSync;
private client?: MatrixClient;
private configureDefer = defer<void>();
private configureDefer = Promise.withResolvers<void>();
public static get instance(): SlidingSyncManager {
return SlidingSyncManager.internalInstance;

View File

@@ -6,14 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
import { WorkerPayload } from "./workers/worker";
export class WorkerManager<Request extends {}, Response> {
private readonly worker: Worker;
private seq = 0;
private pendingDeferredMap = new Map<number, IDeferred<Response>>();
private pendingDeferredMap = new Map<number, PromiseWithResolvers<Response>>();
public constructor(worker: Worker) {
this.worker = worker;
@@ -30,7 +28,7 @@ export class WorkerManager<Request extends {}, Response> {
public call(request: Request): Promise<Response> {
const seq = this.seq++;
const deferred = defer<Response>();
const deferred = Promise.withResolvers<Response>();
this.pendingDeferredMap.set(seq, deferred);
this.worker.postMessage({ seq, ...request });
return deferred.promise;

View File

@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import EventEmitter from "events";
import { SimpleObservable } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
import { defer } from "matrix-js-sdk/src/utils";
import { UPDATE_EVENT } from "../stores/AsyncStore";
import { arrayFastResample } from "../utils/arrays";
@@ -158,7 +157,7 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
// 5mb
logger.log("Audio file too large: processing through <audio /> element");
this.element = document.createElement("AUDIO") as HTMLAudioElement;
const deferred = defer<unknown>();
const deferred = Promise.withResolvers<unknown>();
this.element.onloadeddata = deferred.resolve;
this.element.onerror = deferred.reject;
this.element.src = URL.createObjectURL(new Blob([this.buf]));

View File

@@ -19,7 +19,7 @@ import {
SyncStateData,
TimelineEvents,
} from "matrix-js-sdk/src/matrix";
import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
import { QueryDict } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { throttle } from "lodash";
import { CryptoEvent, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
@@ -217,7 +217,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
};
private firstSyncComplete = false;
private firstSyncPromise: IDeferred<void>;
private firstSyncPromise: PromiseWithResolvers<void>;
private screenAfterLogin?: IScreen;
private tokenLogin?: boolean;
@@ -255,7 +255,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// Used by _viewRoom before getting state from sync
this.firstSyncComplete = false;
this.firstSyncPromise = defer();
this.firstSyncPromise = Promise.withResolvers();
if (this.props.config.sync_timeline_limit) {
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
@@ -1503,7 +1503,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// since we're about to start the client and therefore about
// to do the first sync
this.firstSyncComplete = false;
this.firstSyncPromise = defer();
this.firstSyncPromise = Promise.withResolvers();
const cli = MatrixClientPeg.safeGet();
// Allow the JS SDK to reap timeline events. This reduces the amount of

View File

@@ -145,7 +145,7 @@ export function useMemberTileViewModel(props: MemberTileViewModelProps): MemberT
userLabel = _t(PowerLabel[powerStatus]);
}
if (props.member.isInvite) {
userLabel = _t("member_list|invited_label");
userLabel = `(${_t("member_list|invited_label")})`;
}
return {

View File

@@ -30,7 +30,7 @@ export function useThreePidTileViewModel(props: ThreePidTileViewModelProps): Thr
});
};
const userLabel = _t("member_list|invited_label");
const userLabel = `(${_t("member_list|invited_label")})`;
return {
name,

View File

@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { MatrixEvent, EventType, RelationType, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -58,7 +57,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
const eventId = this.props.mxEvent.getId()!;
const client = MatrixClientPeg.safeGet();
const { resolve, reject, promise } = defer<boolean>();
const { resolve, reject, promise } = Promise.withResolvers<boolean>();
let result: Awaited<ReturnType<MatrixClient["relations"]>>;
try {

View File

@@ -10,7 +10,6 @@ import React, { createRef, KeyboardEvent, RefObject } from "react";
import classNames from "classnames";
import { flatMap } from "lodash";
import { Room } from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import Autocompleter, { ICompletion, ISelectionRange, IProviderCompletions } from "../../../autocomplete/Autocompleter";
import SettingsStore from "../../../settings/SettingsStore";
@@ -173,7 +172,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
}
}
const deferred = defer<void>();
const deferred = Promise.withResolvers<void>();
this.setState(
{
completions,

View File

@@ -31,7 +31,8 @@ export function shouldShowQr(
): boolean {
const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"];
const deviceAuthorizationGrantSupported = oidcClientConfig?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
const deviceAuthorizationGrantSupported =
oidcClientConfig?.metadata?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
return (
!!deviceAuthorizationGrantSupported &&

View File

@@ -7,9 +7,8 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { lazy, Suspense, useCallback, useContext, useEffect, useRef, useState } from "react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { discoverAndValidateOIDCIssuerWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { defer } from "matrix-js-sdk/src/utils";
import { _t } from "../../../../../languageHandler";
import Modal from "../../../../../Modal";
@@ -98,7 +97,7 @@ const useSignOut = (
const url = getManageDeviceUrl(delegatedAuthAccountUrl, deviceId);
window.open(url, "_blank");
} else {
const deferredSuccess = defer<boolean>();
const deferredSuccess = Promise.withResolvers<boolean>();
await deleteDevicesWithInteractiveAuth(matrixClient, deviceIds, async (success) => {
deferredSuccess.resolve(success);
});
@@ -163,7 +162,10 @@ const SessionManagerTab: React.FC<{
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
const oidcClientConfig = useAsyncMemo(async () => {
try {
return await matrixClient?.getAuthMetadata();
const authIssuer = await matrixClient?.getAuthIssuer();
if (authIssuer) {
return discoverAndValidateOIDCIssuerWellKnown(authIssuer.issuer);
}
} catch (e) {
logger.error("Failed to discover OIDC metadata", e);
}

View File

@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
*/
import { AccountDataEvents, ClientEvent, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import { isEqual } from "lodash";
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
@@ -159,7 +158,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Attach a deferred *before* setting the account data to ensure we catch any requests
// which race between different lines.
const deferred = defer<void>();
const deferred = Promise.withResolvers<void>();
const handler = (event: MatrixEvent): void => {
if (event.getType() !== eventType || !isEqual(event.getContent<AccountDataEvents[K]>()[field], value))
return;

View File

@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
*/
import { MatrixClient, MatrixEvent, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import { objectClone, objectKeyChanges } from "../../utils/objects";
@@ -96,7 +95,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
await this.client.setRoomAccountData(roomId, eventType, content);
const deferred = defer<void>();
const deferred = Promise.withResolvers<void>();
const handler = (event: MatrixEvent, room: Room): void => {
if (room.roomId !== roomId || event.getType() !== eventType) return;
if (field !== null && event.getContent()[field] !== value) return;

View File

@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
*/
import { MatrixClient, MatrixEvent, RoomState, RoomStateEvent, StateEvents } from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import { objectClone, objectKeyChanges } from "../../utils/objects";
@@ -92,7 +91,7 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
const { event_id: eventId } = await this.client.sendStateEvent(roomId, eventType, content);
const deferred = defer<void>();
const deferred = Promise.withResolvers<void>();
const handler = (event: MatrixEvent): void => {
if (event.getId() !== eventId) return;
this.client.off(RoomStateEvent.Events, handler);

View File

@@ -50,8 +50,11 @@ export class OidcClientStore {
} else {
// We are not in OIDC Native mode, as we have no locally stored issuer. Check if the server delegates auth to OIDC.
try {
const authMetadata = await this.matrixClient.getAuthMetadata();
this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer);
const authIssuer = await this.matrixClient.getAuthIssuer();
const { accountManagementEndpoint, metadata } = await discoverAndValidateOIDCIssuerWellKnown(
authIssuer.issuer,
);
this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer);
} catch (e) {
console.log("Auth issuer not found", e);
}
@@ -150,11 +153,14 @@ export class OidcClientStore {
try {
const clientId = getStoredOidcClientId();
const authMetadata = await discoverAndValidateOIDCIssuerWellKnown(this.authenticatedIssuer);
this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer);
const { accountManagementEndpoint, metadata, signingKeys } = await discoverAndValidateOIDCIssuerWellKnown(
this.authenticatedIssuer,
);
this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer);
this.oidcClient = new OidcClient({
authority: authMetadata.issuer,
signingKeys: authMetadata.signingKeys ?? undefined,
...metadata,
authority: metadata.issuer,
signingKeys,
redirect_uri: PlatformPeg.get()!.getOidcCallbackUrl().href,
client_id: clientId,
});

View File

@@ -11,6 +11,7 @@ import {
AutoDiscovery,
AutoDiscoveryError,
ClientConfig,
discoverAndValidateOIDCIssuerWellKnown,
IClientWellKnown,
MatrixClient,
MatrixError,
@@ -292,7 +293,8 @@ export default class AutoDiscoveryUtils {
let delegatedAuthenticationError: Error | undefined;
try {
const tempClient = new MatrixClient({ baseUrl: preferredHomeserverUrl });
delegatedAuthentication = await tempClient.getAuthMetadata();
const { issuer } = await tempClient.getAuthIssuer();
delegatedAuthentication = await discoverAndValidateOIDCIssuerWellKnown(issuer);
} catch (e) {
if (e instanceof MatrixError && e.httpStatus === 404 && e.errcode === "M_UNRECOGNIZED") {
// 404 M_UNRECOGNIZED means the server does not support OIDC

View File

@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
import { MatrixError, MatrixClient, EventType } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { AddressType, getAddressType } from "../UserAddress";
@@ -51,7 +50,7 @@ export default class MultiInviter {
private _fatal = false;
private completionStates: CompletionStates = {}; // State of each address (invited or error)
private errors: Record<string, IError> = {}; // { address: {errorText, errcode} }
private deferred: IDeferred<CompletionStates> | null = null;
private deferred: PromiseWithResolvers<CompletionStates> | null = null;
private reason: string | undefined;
/**
@@ -93,7 +92,7 @@ export default class MultiInviter {
};
}
}
this.deferred = defer<CompletionStates>();
this.deferred = Promise.withResolvers<CompletionStates>();
this.inviteMore(0);
return this.deferred.promise;

View File

@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { IDeferred, defer } from "matrix-js-sdk/src/utils";
/**
A countdown timer, exposing a promise api.
A timer starts in a non-started state,
@@ -22,7 +20,7 @@ a new one through `clone()` or `cloneIfRun()`.
export default class Timer {
private timerHandle?: number;
private startTs?: number;
private deferred!: IDeferred<void>;
private deferred!: PromiseWithResolvers<void>;
public constructor(private timeout: number) {
this.setNotStarted();
@@ -31,7 +29,7 @@ export default class Timer {
private setNotStarted(): void {
this.timerHandle = undefined;
this.startTs = undefined;
this.deferred = defer();
this.deferred = Promise.withResolvers();
this.deferred.promise = this.deferred.promise.finally(() => {
this.timerHandle = undefined;
});

View File

@@ -13,7 +13,6 @@ import { renderToStaticMarkup } from "react-dom/server";
import { logger } from "matrix-js-sdk/src/logger";
import escapeHtml from "escape-html";
import { TooltipProvider } from "@vector-im/compound-web";
import { defer } from "matrix-js-sdk/src/utils";
import Exporter from "./Exporter";
import { mediaFromMxc } from "../../customisations/Media";
@@ -302,7 +301,7 @@ export default class HTMLExporter extends Exporter {
if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
// We have to wait for the component to be rendered before we can get the markup
// so pass a deferred as a ref to the component.
const deferred = defer<void>();
const deferred = Promise.withResolvers<void>();
const EventTile = this.getEventTile(mxEv, continuation, deferred.resolve);
let eventTileMarkup: string;

View File

@@ -39,7 +39,7 @@ export const startOidcLogin = async (
const prompt = isRegistration ? "create" : undefined;
const authorizationUrl = await generateOidcAuthorizationUrl({
metadata: delegatedAuthConfig,
metadata: delegatedAuthConfig.metadata,
redirectUri,
clientId,
homeserverUrl,

View File

@@ -15,6 +15,8 @@ import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
* @returns whether user registration is supported
*/
export const isUserRegistrationSupported = (delegatedAuthConfig: OidcClientConfig): boolean => {
const supportedPrompts = delegatedAuthConfig.prompt_values_supported;
// The OidcMetadata type from oidc-client-ts does not include `prompt_values_supported`
// even though it is part of the OIDC spec, so cheat TS here to access it
const supportedPrompts = (delegatedAuthConfig.metadata as Record<string, unknown>)["prompt_values_supported"];
return Array.isArray(supportedPrompts) && supportedPrompts?.includes("create");
};

View File

@@ -40,9 +40,9 @@ export const getOidcClientId = async (
delegatedAuthConfig: OidcClientConfig,
staticOidcClients?: IConfigOptions["oidc_static_clients"],
): Promise<string> => {
const staticClientId = getStaticOidcClientId(delegatedAuthConfig.issuer, staticOidcClients);
const staticClientId = getStaticOidcClientId(delegatedAuthConfig.metadata.issuer, staticOidcClients);
if (staticClientId) {
logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.issuer}`);
logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.metadata.issuer}`);
return staticClientId;
}
return await registerOidcClient(delegatedAuthConfig, await PlatformPeg.get()!.getOidcClientMetadata());

View File

@@ -49,6 +49,8 @@ function checkBrowserFeatures(): boolean {
window.Modernizr.addTest("promiseprototypefinally", () => typeof window.Promise?.prototype?.finally === "function");
// ES2020: http://262.ecma-international.org/#sec-promise.allsettled
window.Modernizr.addTest("promiseallsettled", () => typeof window.Promise?.allSettled === "function");
// ES2024: https://2ality.com/2024/05/proposal-promise-with-resolvers.html
window.Modernizr.addTest("promisewithresolvers", () => typeof window.Promise?.withResolvers === "function");
// ES2018: https://262.ecma-international.org/9.0/#sec-get-regexp.prototype.dotAll
window.Modernizr.addTest(
"regexpdotall",

View File

@@ -5,7 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { ElectronChannel } from "../../@types/global";
@@ -17,7 +16,7 @@ interface IPCPayload {
}
export class IPCManager {
private pendingIpcCalls: { [ipcCallId: number]: IDeferred<any> } = {};
private pendingIpcCalls: { [ipcCallId: number]: PromiseWithResolvers<any> } = {};
private nextIpcCallId = 0;
public constructor(
@@ -33,7 +32,7 @@ export class IPCManager {
public async call(name: string, ...args: any[]): Promise<any> {
// TODO this should be moved into the preload.js file.
const ipcCallId = ++this.nextIpcCallId;
const deferred = defer<any>();
const deferred = Promise.withResolvers<any>();
this.pendingIpcCalls[ipcCallId] = deferred;
// Maybe add a timeout to these? Probably not necessary.
window.electron!.send(this.sendChannel, { id: ipcCallId, name, args });

View File

@@ -6,4 +6,41 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
export { makeDelegatedAuthConfig, mockOpenIdConfiguration } from "matrix-js-sdk/src/testing";
import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
import { ValidatedIssuerMetadata } from "matrix-js-sdk/src/oidc/validate";
/**
* Makes a valid OidcClientConfig with minimum valid values
* @param issuer used as the base for all other urls
* @returns OidcClientConfig
*/
export const makeDelegatedAuthConfig = (issuer = "https://auth.org/"): OidcClientConfig => {
const metadata = mockOpenIdConfiguration(issuer);
return {
accountManagementEndpoint: issuer + "account",
registrationEndpoint: metadata.registration_endpoint,
authorizationEndpoint: metadata.authorization_endpoint,
tokenEndpoint: metadata.token_endpoint,
metadata,
};
};
/**
* Useful for mocking <issuer>/.well-known/openid-configuration
* @param issuer used as the base for all other urls
* @returns ValidatedIssuerMetadata
*/
export const mockOpenIdConfiguration = (issuer = "https://auth.org/"): ValidatedIssuerMetadata => ({
issuer,
revocation_endpoint: issuer + "revoke",
token_endpoint: issuer + "token",
authorization_endpoint: issuer + "auth",
registration_endpoint: issuer + "registration",
device_authorization_endpoint: issuer + "device",
jwks_uri: issuer + "jwks",
response_types_supported: ["code"],
grant_types_supported: ["authorization_code", "refresh_token"],
code_challenge_methods_supported: ["S256"],
account_management_uri: issuer + "account",
});

View File

@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import { mocked } from "jest-mock";
import { ISendEventResponse, MatrixClient, RelationType, UploadResponse } from "matrix-js-sdk/src/matrix";
import { ImageInfo } from "matrix-js-sdk/src/types";
import { defer } from "matrix-js-sdk/src/utils";
import encrypt, { IEncryptedFile } from "matrix-encrypt-attachment";
import ContentMessages, { UploadCanceledError, uploadFile } from "../../src/ContentMessages";
@@ -333,7 +332,7 @@ describe("ContentMessages", () => {
describe("cancelUpload", () => {
it("should cancel in-flight upload", async () => {
const deferred = defer<UploadResponse>();
const deferred = Promise.withResolvers<UploadResponse>();
mocked(client.uploadContent).mockReturnValue(deferred.promise);
const file1 = new File([], "file1");
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);

View File

@@ -749,8 +749,11 @@ describe("Lifecycle", () => {
"eyJhbGciOiJSUzI1NiIsImtpZCI6Imh4ZEhXb0Y5bW4ifQ.eyJzdWIiOiIwMUhQUDJGU0JZREU5UDlFTU04REQ3V1pIUiIsImlzcyI6Imh0dHBzOi8vYXV0aC1vaWRjLmxhYi5lbGVtZW50LmRldi8iLCJpYXQiOjE3MTUwNzE5ODUsImF1dGhfdGltZSI6MTcwNzk5MDMxMiwiY19oYXNoIjoidGt5R1RhUjU5aTk3YXoyTU4yMGdidyIsImV4cCI6MTcxNTA3NTU4NSwibm9uY2UiOiJxaXhwM0hFMmVaIiwiYXVkIjoiMDFIWDk0Mlg3QTg3REgxRUs2UDRaNjI4WEciLCJhdF9oYXNoIjoiNFlFUjdPRlVKTmRTeEVHV2hJUDlnZyJ9.HxODneXvSTfWB5Vc4cf7b8GiN2gdwUuTiyVqZuupWske2HkZiJZUt5Lsxg9BW3gz28POkE0Ln17snlkmy02B_AD3DQxKOOxQCzIIARHdfFvZxgGWsMdFcVQZDW7rtXcqgj-SpVaUQ_8acsgxSrz_DF2o0O4tto0PT6wVUiw8KlBmgWTscWPeAWe-39T-8EiQ8Wi16h6oSPcz2NzOQ7eOM_S9fDkOorgcBkRGLl1nrahrPSdWJSGAeruk5mX4YxN714YThFDyEA2t9YmKpjaiSQ2tT-Xkd7tgsZqeirNs2ni9mIiFX3bRX6t2AhUNzA7MaX9ZyizKGa6go3BESO_oDg";
beforeAll(() => {
fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig);
fetchMock.get(`${delegatedAuthConfig.issuer}jwks`, {
fetchMock.get(
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
delegatedAuthConfig.metadata,
);
fetchMock.get(`${delegatedAuthConfig.metadata.issuer}jwks`, {
status: 200,
headers: {
"Content-Type": "application/json",
@@ -769,7 +772,9 @@ describe("Lifecycle", () => {
await setLoggedIn(credentials);
// didn't try to initialise token refresher
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
expect(fetchMock).not.toHaveFetched(
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
);
});
it("should not try to create a token refresher without a deviceId", async () => {
@@ -780,7 +785,9 @@ describe("Lifecycle", () => {
});
// didn't try to initialise token refresher
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
expect(fetchMock).not.toHaveFetched(
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
);
});
it("should not try to create a token refresher without an issuer in session storage", async () => {
@@ -796,7 +803,9 @@ describe("Lifecycle", () => {
});
// didn't try to initialise token refresher
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
expect(fetchMock).not.toHaveFetched(
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
);
});
it("should create a client with a tokenRefreshFunction", async () => {

View File

@@ -21,7 +21,7 @@ import { completeAuthorizationCodeGrant } from "matrix-js-sdk/src/oidc/authorize
import { logger } from "matrix-js-sdk/src/logger";
import { OidcError } from "matrix-js-sdk/src/oidc/error";
import { BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
import { defer, IDeferred, sleep } from "matrix-js-sdk/src/utils";
import { sleep } from "matrix-js-sdk/src/utils";
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import MatrixChat from "../../../../src/components/structures/MatrixChat";
@@ -79,7 +79,7 @@ describe("<MatrixChat />", () => {
const deviceId = "qwertyui";
const accessToken = "abc123";
const refreshToken = "def456";
let bootstrapDeferred: IDeferred<void>;
let bootstrapDeferred: PromiseWithResolvers<void>;
// reused in createClient mock below
const getMockClientMethods = () => ({
...mockClientMethodsUser(userId),
@@ -245,7 +245,7 @@ describe("<MatrixChat />", () => {
{} as ValidatedServerConfig,
);
bootstrapDeferred = defer();
bootstrapDeferred = Promise.withResolvers();
await clearAllModals();
});
@@ -1439,7 +1439,7 @@ describe("<MatrixChat />", () => {
jest.spyOn(MatrixJs, "createClient").mockReturnValue(client);
// intercept initCrypto and have it block until we complete the deferred
const initCryptoCompleteDefer = defer();
const initCryptoCompleteDefer = Promise.withResolvers<void>();
const initCryptoCalled = new Promise<void>((resolve) => {
client.initRustCrypto.mockImplementation(() => {
resolve();

View File

@@ -18,7 +18,6 @@ import {
SearchResult,
ISearchResults,
} from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import { RoomSearchView } from "../../../../src/components/structures/RoomSearchView";
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
@@ -53,7 +52,7 @@ describe("<RoomSearchView/>", () => {
});
it("should show a spinner before the promise resolves", async () => {
const deferred = defer<ISearchResults>();
const deferred = Promise.withResolvers<ISearchResults>();
render(
<RoomSearchView
@@ -267,7 +266,7 @@ describe("<RoomSearchView/>", () => {
});
it("should handle resolutions after unmounting sanely", async () => {
const deferred = defer<ISearchResults>();
const deferred = Promise.withResolvers<ISearchResults>();
const { unmount } = render(
<MatrixClientContext.Provider value={client}>
@@ -291,7 +290,7 @@ describe("<RoomSearchView/>", () => {
});
it("should handle rejections after unmounting sanely", async () => {
const deferred = defer<ISearchResults>();
const deferred = Promise.withResolvers<ISearchResults>();
const { unmount } = render(
<MatrixClientContext.Provider value={client}>
@@ -315,7 +314,7 @@ describe("<RoomSearchView/>", () => {
});
it("should show modal if error is encountered", async () => {
const deferred = defer<ISearchResults>();
const deferred = Promise.withResolvers<ISearchResults>();
render(
<MatrixClientContext.Provider value={client}>

View File

@@ -35,7 +35,6 @@ import {
cleanup,
} from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { defer } from "matrix-js-sdk/src/utils";
import {
stubClient,
@@ -303,7 +302,7 @@ describe("RoomView", () => {
it("should not display the timeline when the room encryption is loading", async () => {
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
const deferred = defer<boolean>();
const deferred = Promise.withResolvers<boolean>();
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockImplementation(() => deferred.promise);
const { asFragment, container } = await mountRoomView();

View File

@@ -384,7 +384,7 @@ describe("Login", function () {
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
// didn't try to register
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint);
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint);
// continued with normal setup
expect(mockClient.loginFlows).toHaveBeenCalled();
// normal password login rendered
@@ -394,25 +394,25 @@ describe("Login", function () {
it("should attempt to register oidc client", async () => {
// dont mock, spy so we can check config values were correctly passed
jest.spyOn(registerClientUtils, "getOidcClientId");
fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 });
fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 });
getComponent(hsUrl, isUrl, delegatedAuth);
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
// tried to register
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object));
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object));
// called with values from config
expect(registerClientUtils.getOidcClientId).toHaveBeenCalledWith(delegatedAuth, oidcStaticClientsConfig);
});
it("should fallback to normal login when client registration fails", async () => {
fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 });
fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 });
getComponent(hsUrl, isUrl, delegatedAuth);
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
// tried to register
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object));
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object));
expect(logger.error).toHaveBeenCalledWith(new Error(OidcError.DynamicRegistrationFailed));
// continued with normal setup
@@ -423,7 +423,7 @@ describe("Login", function () {
// short term during active development, UI will be added in next PRs
it("should show continue button when oidc native flow is correctly configured", async () => {
fetchMock.post(delegatedAuth.registration_endpoint!, { client_id: "abc123" });
fetchMock.post(delegatedAuth.registrationEndpoint!, { client_id: "abc123" });
getComponent(hsUrl, isUrl, delegatedAuth);
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
@@ -455,7 +455,7 @@ describe("Login", function () {
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
// didn't try to register
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint);
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint);
// continued with normal setup
expect(mockClient.loginFlows).toHaveBeenCalled();
// oidc-aware 'continue' button displayed

View File

@@ -158,26 +158,24 @@ describe("Registration", function () {
describe("when delegated authentication is configured and enabled", () => {
const authConfig = makeDelegatedAuthConfig();
const clientId = "test-client-id";
authConfig.prompt_values_supported = ["create"];
// @ts-ignore
authConfig.metadata["prompt_values_supported"] = ["create"];
beforeEach(() => {
// mock a statically registered client to avoid dynamic registration
SdkConfig.put({
oidc_static_clients: {
[authConfig.issuer]: {
[authConfig.metadata.issuer]: {
client_id: clientId,
},
},
});
fetchMock.get(`${defaultHsUrl}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`, {
issuer: authConfig.issuer,
issuer: authConfig.metadata.issuer,
});
fetchMock.get("https://auth.org/.well-known/openid-configuration", {
...authConfig,
signingKeys: undefined,
});
fetchMock.get(authConfig.jwks_uri!, { keys: [] });
fetchMock.get("https://auth.org/.well-known/openid-configuration", authConfig.metadata);
fetchMock.get(authConfig.metadata.jwks_uri!, { keys: [] });
});
it("should display oidc-native continue button", async () => {

View File

@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { fireEvent, render, screen } from "jest-matrix-react";
import { defer } from "matrix-js-sdk/src/utils";
import PowerSelector from "../../../../../src/components/views/elements/PowerSelector";
@@ -70,7 +69,7 @@ describe("<PowerSelector />", () => {
});
it("should reset when onChange promise rejects", async () => {
const deferred = defer<void>();
const deferred = Promise.withResolvers<void>();
render(
<PowerSelector
value={25}

View File

@@ -12,7 +12,6 @@ import userEvent from "@testing-library/user-event";
import { Mocked, mocked } from "jest-mock";
import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType, Device } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { defer } from "matrix-js-sdk/src/utils";
import { EventEmitter } from "events";
import {
UserVerificationStatus,
@@ -795,7 +794,7 @@ describe("<DeviceItem />", () => {
});
it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", async () => {
const deferred = defer<DeviceVerificationStatus>();
const deferred = Promise.withResolvers<DeviceVerificationStatus>();
mockCrypto.getDeviceVerificationStatus.mockReturnValue(deferred.promise);
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
@@ -1058,7 +1057,7 @@ describe("<UserOptionsSection />", () => {
])(
"clicking »message« %s should start a DM",
async (test: string, member: RoomMember | User, expectedAvatarUrl: string | undefined) => {
const deferred = defer<string>();
const deferred = Promise.withResolvers<string>();
mocked(startDmOnFirstMessage).mockReturnValue(deferred.promise);
renderComponent({ member });

View File

@@ -228,7 +228,7 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
<div
class="mx_MemberTileView_userLabel"
>
Invited
(Invited)
</div>
<div
class="mx_Flex mx_InvitedIconView"

View File

@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { fireEvent, render, screen, within } from "jest-matrix-react";
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
import EventIndexPanel from "../../../../../src/components/views/settings/EventIndexPanel";
import EventIndexPeg from "../../../../../src/indexing/EventIndexPeg";
@@ -140,9 +139,9 @@ describe("<EventIndexPanel />", () => {
});
it("enables event indexing on enable button click", async () => {
jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
let deferredInitEventIndex: IDeferred<boolean> | undefined;
let deferredInitEventIndex: PromiseWithResolvers<boolean> | undefined;
jest.spyOn(EventIndexPeg, "initEventIndex").mockImplementation(() => {
deferredInitEventIndex = defer<boolean>();
deferredInitEventIndex = Promise.withResolvers<boolean>();
return deferredInitEventIndex.promise;
});

View File

@@ -21,7 +21,6 @@ import {
Visibility,
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
import {
clearAllModals,
@@ -160,7 +159,7 @@ describe("<JoinRuleSettings />", () => {
});
it(`upgrades room when changing join rule to ${joinRule}`, async () => {
const deferredInvites: IDeferred<any>[] = [];
const deferredInvites: PromiseWithResolvers<any>[] = [];
// room that doesn't support the join rule
const room = new Room(roomId, client, userId);
const parentSpace = new Room("!parentSpace:server.org", client, userId);
@@ -183,7 +182,7 @@ describe("<JoinRuleSettings />", () => {
// resolve invites by hand
// flushPromises is too blunt to test reliably
client.invite.mockImplementation(() => {
const p = defer<{}>();
const p = Promise.withResolvers<{}>();
deferredInvites.push(p);
return p.promise;
});

View File

@@ -11,7 +11,6 @@ import { fireEvent, getByRole, render, RenderResult, screen, waitFor } from "jes
import { MatrixClient, EventType, MatrixEvent, Room, RoomMember, ISendEventResponse } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked } from "jest-mock";
import { defer } from "matrix-js-sdk/src/utils";
import userEvent from "@testing-library/user-event";
import RolesRoomSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
@@ -202,7 +201,7 @@ describe("RolesRoomSettingsTab", () => {
});
it("should roll back power level change on error", async () => {
const deferred = defer<ISendEventResponse>();
const deferred = Promise.withResolvers<ISendEventResponse>();
mocked(cli.sendStateEvent).mockReturnValue(deferred.promise);
mocked(cli.getRoom).mockReturnValue(room);
// @ts-ignore - mocked doesn't support overloads properly

View File

@@ -19,7 +19,7 @@ import {
} from "jest-matrix-react";
import { logger } from "matrix-js-sdk/src/logger";
import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
import { defer, sleep } from "matrix-js-sdk/src/utils";
import { sleep } from "matrix-js-sdk/src/utils";
import {
ClientEvent,
Device,
@@ -57,7 +57,7 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore";
import { getClientInformationEventType } from "../../../../../../../src/utils/device/clientInformation";
import { SDKContext, SdkContextClass } from "../../../../../../../src/contexts/SDKContext";
import { OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
import { makeDelegatedAuthConfig } from "../../../../../../test-utils/oidc";
import { mockOpenIdConfiguration } from "../../../../../../test-utils/oidc";
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
mockPlatformPeg();
@@ -215,7 +215,7 @@ describe("<SessionManagerTab />", () => {
getPushers: jest.fn(),
setPusher: jest.fn(),
setLocalNotificationSettings: jest.fn(),
getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)),
getAuthIssuer: jest.fn().mockReturnValue(new Promise(() => {})),
});
jest.clearAllMocks();
jest.spyOn(logger, "error").mockRestore();
@@ -894,7 +894,7 @@ describe("<SessionManagerTab />", () => {
});
it("deletes a device when interactive auth is not required", async () => {
const deferredDeleteMultipleDevices = defer<{}>();
const deferredDeleteMultipleDevices = Promise.withResolvers<{}>();
mockClient.deleteMultipleDevices.mockReturnValue(deferredDeleteMultipleDevices.promise);
mockClient.getDevices.mockResolvedValue({
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
@@ -1103,7 +1103,7 @@ describe("<SessionManagerTab />", () => {
// get a handle for resolving the delete call
// because promise flushing after the confirm modal is resolving this too
// and we want to test the loading state here
const resolveDeleteRequest = defer<IAuthData>();
const resolveDeleteRequest = Promise.withResolvers<IAuthData>();
mockClient.deleteMultipleDevices.mockImplementation(() => {
return resolveDeleteRequest.promise;
});
@@ -1615,6 +1615,7 @@ describe("<SessionManagerTab />", () => {
describe("MSC4108 QR code login", () => {
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
const issuer = "https://issuer.org";
const openIdConfiguration = mockOpenIdConfiguration(issuer);
beforeEach(() => {
settingsValueSpy.mockClear().mockReturnValue(true);
@@ -1630,16 +1631,16 @@ describe("<SessionManagerTab />", () => {
enabled: true,
},
});
const delegatedAuthConfig = makeDelegatedAuthConfig(issuer);
mockClient.getAuthMetadata.mockResolvedValue({
...delegatedAuthConfig,
mockClient.getAuthIssuer.mockResolvedValue({ issuer });
mockCrypto.exportSecretsBundle = jest.fn();
fetchMock.mock(`${issuer}/.well-known/openid-configuration`, {
...openIdConfiguration,
grant_types_supported: [
...delegatedAuthConfig.grant_types_supported,
...openIdConfiguration.grant_types_supported,
"urn:ietf:params:oauth:grant-type:device_code",
],
});
mockCrypto.exportSecretsBundle = jest.fn();
fetchMock.mock(delegatedAuthConfig.jwks_uri!, {
fetchMock.mock(openIdConfiguration.jwks_uri!, {
status: 200,
headers: {
"Content-Type": "application/json",

View File

@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { defer } from "matrix-js-sdk/src/utils";
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions";
import { AsyncActionPayload } from "../../../src/dispatcher/payloads";
@@ -20,8 +18,8 @@ describe("MatrixDispatcher", () => {
});
it("should execute callbacks in registered order", async () => {
const deferred1 = defer<number>();
const deferred2 = defer<number>();
const deferred1 = Promise.withResolvers<number>();
const deferred2 = Promise.withResolvers<number>();
const fn1 = jest.fn(() => deferred1.resolve(1));
const fn2 = jest.fn(() => deferred2.resolve(2));
@@ -36,8 +34,8 @@ describe("MatrixDispatcher", () => {
});
it("should skip the queue for the given callback", async () => {
const deferred1 = defer<number>();
const deferred2 = defer<number>();
const deferred1 = Promise.withResolvers<number>();
const deferred2 = Promise.withResolvers<number>();
const fn1 = jest.fn(() => deferred1.resolve(1));
const fn2 = jest.fn(() => deferred2.resolve(2));

View File

@@ -6,7 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { defer } from "matrix-js-sdk/src/utils";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import ServerSupportUnstableFeatureController from "../../../../src/settings/controllers/ServerSupportUnstableFeatureController";
@@ -34,7 +33,7 @@ describe("ServerSupportUnstableFeatureController", () => {
controller,
};
const deferred = defer<any>();
const deferred = Promise.withResolvers<any>();
watchers.watchSetting(setting, null, deferred.resolve);
MatrixClientBackedController.matrixClient = cli;
await deferred.promise;

View File

@@ -20,7 +20,6 @@ import {
RoomState,
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { defer } from "matrix-js-sdk/src/utils";
import SpaceStore from "../../../src/stores/spaces/SpaceStore";
import {
@@ -1008,7 +1007,7 @@ describe("SpaceStore", () => {
await run();
const deferred = defer<boolean>();
const deferred = Promise.withResolvers<boolean>();
space.loadMembersIfNeeded.mockImplementation(() => {
const event = mkEvent({
event: true,

View File

@@ -15,7 +15,7 @@ import { OidcError } from "matrix-js-sdk/src/oidc/error";
import { OidcClientStore } from "../../../../src/stores/oidc/OidcClientStore";
import { flushPromises, getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils";
import { makeDelegatedAuthConfig } from "../../../test-utils/oidc";
import { mockOpenIdConfiguration } from "../../../test-utils/oidc";
jest.mock("matrix-js-sdk/src/matrix", () => ({
...jest.requireActual("matrix-js-sdk/src/matrix"),
@@ -24,30 +24,28 @@ jest.mock("matrix-js-sdk/src/matrix", () => ({
describe("OidcClientStore", () => {
const clientId = "test-client-id";
const authConfig = makeDelegatedAuthConfig();
const account = authConfig.issuer + "account";
const metadata = mockOpenIdConfiguration();
const account = metadata.issuer + "account";
const mockClient = getMockClientWithEventEmitter({
getAuthMetadata: jest.fn(),
getAuthIssuer: jest.fn(),
});
beforeEach(() => {
localStorage.clear();
localStorage.setItem("mx_oidc_client_id", clientId);
localStorage.setItem("mx_oidc_token_issuer", authConfig.issuer);
localStorage.setItem("mx_oidc_token_issuer", metadata.issuer);
mocked(discoverAndValidateOIDCIssuerWellKnown)
.mockClear()
.mockResolvedValue({
...authConfig,
account_management_uri: account,
authorization_endpoint: "authorization-endpoint",
token_endpoint: "token-endpoint",
});
mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
metadata,
accountManagementEndpoint: account,
authorizationEndpoint: "authorization-endpoint",
tokenEndpoint: "token-endpoint",
});
jest.spyOn(logger, "error").mockClear();
fetchMock.get(`${authConfig.issuer}.well-known/openid-configuration`, authConfig);
fetchMock.get(`${authConfig.issuer}jwks`, { keys: [] });
fetchMock.get(`${metadata.issuer}.well-known/openid-configuration`, metadata);
fetchMock.get(`${metadata.issuer}jwks`, { keys: [] });
mockPlatformPeg();
});
@@ -118,7 +116,7 @@ describe("OidcClientStore", () => {
const client = await store.getOidcClient();
expect(client?.settings.client_id).toEqual(clientId);
expect(client?.settings.authority).toEqual(authConfig.issuer);
expect(client?.settings.authority).toEqual(metadata.issuer);
});
it("should set account management endpoint when configured", async () => {
@@ -131,19 +129,17 @@ describe("OidcClientStore", () => {
});
it("should set account management endpoint to issuer when not configured", async () => {
mocked(discoverAndValidateOIDCIssuerWellKnown)
.mockClear()
.mockResolvedValue({
...authConfig,
account_management_uri: undefined,
authorization_endpoint: "authorization-endpoint",
token_endpoint: "token-endpoint",
});
mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
metadata,
accountManagementEndpoint: undefined,
authorizationEndpoint: "authorization-endpoint",
tokenEndpoint: "token-endpoint",
});
const store = new OidcClientStore(mockClient);
await store.readyPromise;
expect(store.accountManagementEndpoint).toEqual(authConfig.issuer);
expect(store.accountManagementEndpoint).toEqual(metadata.issuer);
});
it("should reuse initialised oidc client", async () => {
@@ -179,7 +175,7 @@ describe("OidcClientStore", () => {
fetchMock.resetHistory();
fetchMock.post(
authConfig.revocation_endpoint,
metadata.revocation_endpoint,
{
status: 200,
},
@@ -201,7 +197,7 @@ describe("OidcClientStore", () => {
await store.revokeTokens(accessToken, refreshToken);
expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint);
expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint);
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(refreshToken, "refresh_token");
});
@@ -210,14 +206,14 @@ describe("OidcClientStore", () => {
// fail once, then succeed
fetchMock
.postOnce(
authConfig.revocation_endpoint,
metadata.revocation_endpoint,
{
status: 404,
},
{ overwriteRoutes: true, sendAsJson: true },
)
.post(
authConfig.revocation_endpoint,
metadata.revocation_endpoint,
{
status: 200,
},
@@ -230,7 +226,7 @@ describe("OidcClientStore", () => {
"Failed to revoke tokens",
);
expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint);
expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint);
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
});
});
@@ -241,10 +237,7 @@ describe("OidcClientStore", () => {
});
it("should resolve account management endpoint", async () => {
mockClient.getAuthMetadata.mockResolvedValue({
...authConfig,
account_management_uri: account,
});
mockClient.getAuthIssuer.mockResolvedValue({ issuer: metadata.issuer });
const store = new OidcClientStore(mockClient);
await store.readyPromise;
expect(store.accountManagementEndpoint).toBe(account);

View File

@@ -355,19 +355,21 @@ describe("AutoDiscoveryUtils", () => {
hsNameIsDifferent: true,
hsName: serverName,
delegatedAuthentication: expect.objectContaining({
issuer,
account_management_actions_supported: [
accountManagementActionsSupported: [
"org.matrix.profile",
"org.matrix.sessions_list",
"org.matrix.session_view",
"org.matrix.session_end",
"org.matrix.cross_signing_reset",
],
account_management_uri: "https://auth.matrix.org/account/",
authorization_endpoint: "https://auth.matrix.org/auth",
registration_endpoint: "https://auth.matrix.org/registration",
accountManagementEndpoint: "https://auth.matrix.org/account/",
authorizationEndpoint: "https://auth.matrix.org/auth",
metadata: expect.objectContaining({
issuer,
}),
registrationEndpoint: "https://auth.matrix.org/registration",
signingKeys: [],
token_endpoint: "https://auth.matrix.org/token",
tokenEndpoint: "https://auth.matrix.org/token",
}),
warning: null,
});

View File

@@ -38,7 +38,7 @@ describe("TokenRefresher", () => {
};
beforeEach(() => {
fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig);
fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig.metadata);
fetchMock.get(`${issuer}jwks`, {
status: 200,
headers: {

View File

@@ -61,7 +61,10 @@ describe("OIDC authorization", () => {
});
beforeAll(() => {
fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig);
fetchMock.get(
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
delegatedAuthConfig.metadata,
);
});
afterAll(() => {

View File

@@ -58,7 +58,7 @@ describe("getOidcClientId()", () => {
const authConfigWithoutRegistration: OidcClientConfig = makeDelegatedAuthConfig(
"https://issuerWithoutStaticClientId.org/",
);
authConfigWithoutRegistration.registration_endpoint = undefined;
authConfigWithoutRegistration.registrationEndpoint = undefined;
await expect(getOidcClientId(authConfigWithoutRegistration, staticOidcClients)).rejects.toThrow(
OidcError.DynamicRegistrationNotSupported,
);
@@ -69,7 +69,7 @@ describe("getOidcClientId()", () => {
it("should handle when staticOidcClients object is falsy", async () => {
const authConfigWithoutRegistration: OidcClientConfig = {
...delegatedAuthConfig,
registration_endpoint: undefined,
registrationEndpoint: undefined,
};
await expect(getOidcClientId(authConfigWithoutRegistration)).rejects.toThrow(
OidcError.DynamicRegistrationNotSupported,
@@ -79,14 +79,14 @@ describe("getOidcClientId()", () => {
});
it("should make correct request to register client", async () => {
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
status: 200,
body: JSON.stringify({ client_id: dynamicClientId }),
});
expect(await getOidcClientId(delegatedAuthConfig)).toEqual(dynamicClientId);
// didn't try to register
expect(fetchMockJest).toHaveBeenCalledWith(
delegatedAuthConfig.registration_endpoint!,
delegatedAuthConfig.registrationEndpoint!,
expect.objectContaining({
headers: {
"Accept": "application/json",
@@ -111,14 +111,14 @@ describe("getOidcClientId()", () => {
});
it("should throw when registration request fails", async () => {
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
status: 500,
});
await expect(getOidcClientId(delegatedAuthConfig)).rejects.toThrow(OidcError.DynamicRegistrationFailed);
});
it("should throw when registration response is invalid", async () => {
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
status: 200,
// no clientId in response
body: "{}",

View File

@@ -14,7 +14,7 @@
"outDir": "./lib",
"declaration": true,
"jsx": "react",
"lib": ["es2022", "dom", "dom.iterable"],
"lib": ["es2022", "es2024.promise", "dom", "dom.iterable"],
"strict": true,
"paths": {
"jest-matrix-react": ["./test/test-utils/jest-matrix-react"]

1863
yarn.lock

File diff suppressed because it is too large Load Diff