Compare commits
23 Commits
v37.0.0
...
v37.1.0-rc.0
| Author | SHA1 | Date | |
|---|---|---|---|
| df7f69b6a2 | |||
| 71bffb6c1b | |||
| 72b997d1f3 | |||
| a3bbc49e02 | |||
| d81929de4c | |||
| 27002a0d8f | |||
| bb2a0bec8c | |||
| 502f0ec0eb | |||
| 1ddba3460f | |||
| 524cb65c5d | |||
| ea770282ea | |||
| bcaa7f63c7 | |||
| 8ab9025282 | |||
| e49a0a5013 | |||
| 5b939287cc | |||
| 2d381ade22 | |||
| 266475c37d | |||
| 597db17e59 | |||
| 9ea5b512e6 | |||
| 2ce57fd942 | |||
| 102fa91efe | |||
| 266e9257aa | |||
| f7015b35a0 |
@@ -154,5 +154,16 @@ module.exports = {
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Enable stricter promise rules for the MatrixRTC codebase
|
||||
files: ["src/matrixrtc/**/*.ts", "spec/unit/matrixrtc/*.ts"],
|
||||
rules: {
|
||||
// Encourage proper usage of Promises:
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -279,6 +279,9 @@ jobs:
|
||||
needs: release
|
||||
if: inputs.npm
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
|
||||
@@ -8,11 +8,14 @@ on:
|
||||
id:
|
||||
description: "The npm package@version string we published"
|
||||
value: ${{ jobs.npm.outputs.id }}
|
||||
permissions: {} # No permissions required
|
||||
permissions: {}
|
||||
jobs:
|
||||
npm:
|
||||
name: Publish to npm
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
outputs:
|
||||
id: ${{ steps.npm-publish.outputs.id }}
|
||||
steps:
|
||||
@@ -33,16 +36,12 @@ jobs:
|
||||
|
||||
- name: 🚀 Publish to npm
|
||||
id: npm-publish
|
||||
uses: JS-DevTools/npm-publish@19c28f1ef146469e409470805ea4279d47c3d35c # v3.1.1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
access: public
|
||||
tag: next
|
||||
ignore-scripts: false
|
||||
|
||||
- name: Check npm package was published
|
||||
if: steps.npm-publish.outputs.id == ''
|
||||
run: exit 1
|
||||
run: |
|
||||
npm publish --provenance --access public --tag next
|
||||
release=$(jq -r '"\(.name)@\(.version)"' package.json)
|
||||
echo "id=$release" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: 🎖️ Add `latest` dist-tag to final releases
|
||||
if: steps.npm-publish.outputs.id && !contains(steps.npm-publish.outputs.id, '-rc.')
|
||||
|
||||
@@ -29,6 +29,7 @@ jobs:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: read
|
||||
id-token: write
|
||||
secrets: inherit
|
||||
with:
|
||||
final: ${{ inputs.mode == 'final' }}
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
steps:
|
||||
# We create the status here and then update it to success/failure in the `report` stage
|
||||
# This provides an easy link to this workflow_run from the PR before Sonarcloud is done.
|
||||
- uses: guibranco/github-status-action-v2@119b3320db3f04d89e91df840844b92d57ce3468
|
||||
- uses: guibranco/github-status-action-v2@7ca807c2ba3401be532d29a876b93262108099fb
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: pending
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
revision: ${{ github.event.workflow_run.head_sha }}
|
||||
token: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
- uses: guibranco/github-status-action-v2@119b3320db3f04d89e91df840844b92d57ce3468
|
||||
- uses: guibranco/github-status-action-v2@7ca807c2ba3401be532d29a876b93262108099fb
|
||||
if: always()
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
steps:
|
||||
- name: Skip SonarCloud on merge queues
|
||||
if: env.ENABLE_COVERAGE == 'false'
|
||||
uses: guibranco/github-status-action-v2@119b3320db3f04d89e91df840844b92d57ce3468
|
||||
uses: guibranco/github-status-action-v2@7ca807c2ba3401be532d29a876b93262108099fb
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
|
||||
@@ -14,7 +14,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
operations-per-run: 100
|
||||
operations-per-run: 250
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 180
|
||||
days-before-pr-close: 0
|
||||
close-pr-message: "This PR has been automatically closed because it has been stale for 180 days. If you wish to continue working on this PR, please ping a maintainer to reopen it."
|
||||
|
||||
@@ -26,6 +26,7 @@ module.exports = {
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
["@babel/plugin-proposal-decorators", { version: "2023-11" }],
|
||||
"@babel/plugin-transform-numeric-separator",
|
||||
"@babel/plugin-transform-class-properties",
|
||||
"@babel/plugin-transform-object-rest-spread",
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "37.0.0",
|
||||
"version": "37.1.0-rc.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -50,7 +50,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^13.0.0",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^14.0.1",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"another-json": "^0.2.0",
|
||||
"bs58": "^6.0.0",
|
||||
@@ -72,6 +72,7 @@
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/eslint-parser": "^7.12.10",
|
||||
"@babel/eslint-plugin": "^7.12.10",
|
||||
"@babel/plugin-proposal-decorators": "^7.25.9",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-class-properties": "^7.12.1",
|
||||
"@babel/plugin-transform-numeric-separator": "^7.12.7",
|
||||
@@ -82,7 +83,6 @@
|
||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||
"@peculiar/webcrypto": "^1.4.5",
|
||||
"@stylistic/eslint-plugin": "^3.0.0",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/jest": "^29.0.0",
|
||||
@@ -117,7 +117,7 @@
|
||||
"lint-staged": "^15.0.2",
|
||||
"matrix-mock-request": "^2.5.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"prettier": "3.4.2",
|
||||
"prettier": "3.5.1",
|
||||
"rimraf": "^6.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.27.0",
|
||||
|
||||
@@ -20,6 +20,7 @@ import { FetchHttpApi } from "../../../src/http-api/fetch";
|
||||
import { TypedEventEmitter } from "../../../src/models/typed-event-emitter";
|
||||
import {
|
||||
ClientPrefix,
|
||||
ConnectionError,
|
||||
HttpApiEvent,
|
||||
type HttpApiEventHandlerMap,
|
||||
IdentityPrefix,
|
||||
@@ -125,7 +126,7 @@ describe("FetchHttpApi", () => {
|
||||
).resolves.toBe(text);
|
||||
});
|
||||
|
||||
it("should send token via query params if useAuthorizationHeader=false", () => {
|
||||
it("should send token via query params if useAuthorizationHeader=false", async () => {
|
||||
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||
baseUrl,
|
||||
@@ -134,11 +135,11 @@ describe("FetchHttpApi", () => {
|
||||
accessToken: "token",
|
||||
useAuthorizationHeader: false,
|
||||
});
|
||||
api.authedRequest(Method.Get, "/path");
|
||||
await api.authedRequest(Method.Get, "/path");
|
||||
expect(fetchFn.mock.calls[0][0].searchParams.get("access_token")).toBe("token");
|
||||
});
|
||||
|
||||
it("should send token via headers by default", () => {
|
||||
it("should send token via headers by default", async () => {
|
||||
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||
baseUrl,
|
||||
@@ -146,7 +147,7 @@ describe("FetchHttpApi", () => {
|
||||
fetchFn,
|
||||
accessToken: "token",
|
||||
});
|
||||
api.authedRequest(Method.Get, "/path");
|
||||
await api.authedRequest(Method.Get, "/path");
|
||||
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBe("Bearer token");
|
||||
});
|
||||
|
||||
@@ -163,7 +164,7 @@ describe("FetchHttpApi", () => {
|
||||
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should ensure no token is leaked out via query params if sending via headers", () => {
|
||||
it("should ensure no token is leaked out via query params if sending via headers", async () => {
|
||||
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||
baseUrl,
|
||||
@@ -172,12 +173,12 @@ describe("FetchHttpApi", () => {
|
||||
accessToken: "token",
|
||||
useAuthorizationHeader: true,
|
||||
});
|
||||
api.authedRequest(Method.Get, "/path", { access_token: "123" });
|
||||
await api.authedRequest(Method.Get, "/path", { access_token: "123" });
|
||||
expect(fetchFn.mock.calls[0][0].searchParams.get("access_token")).toBeFalsy();
|
||||
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBe("Bearer token");
|
||||
});
|
||||
|
||||
it("should not override manually specified access token via query params", () => {
|
||||
it("should not override manually specified access token via query params", async () => {
|
||||
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||
baseUrl,
|
||||
@@ -186,11 +187,11 @@ describe("FetchHttpApi", () => {
|
||||
accessToken: "token",
|
||||
useAuthorizationHeader: false,
|
||||
});
|
||||
api.authedRequest(Method.Get, "/path", { access_token: "RealToken" });
|
||||
await api.authedRequest(Method.Get, "/path", { access_token: "RealToken" });
|
||||
expect(fetchFn.mock.calls[0][0].searchParams.get("access_token")).toBe("RealToken");
|
||||
});
|
||||
|
||||
it("should not override manually specified access token via header", () => {
|
||||
it("should not override manually specified access token via header", async () => {
|
||||
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||
baseUrl,
|
||||
@@ -199,16 +200,16 @@ describe("FetchHttpApi", () => {
|
||||
accessToken: "token",
|
||||
useAuthorizationHeader: true,
|
||||
});
|
||||
api.authedRequest(Method.Get, "/path", undefined, undefined, {
|
||||
await api.authedRequest(Method.Get, "/path", undefined, undefined, {
|
||||
headers: { Authorization: "Bearer RealToken" },
|
||||
});
|
||||
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBe("Bearer RealToken");
|
||||
});
|
||||
|
||||
it("should not override Accept header", () => {
|
||||
it("should not override Accept header", async () => {
|
||||
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn });
|
||||
api.authedRequest(Method.Get, "/path", undefined, undefined, {
|
||||
await api.authedRequest(Method.Get, "/path", undefined, undefined, {
|
||||
headers: { Accept: "text/html" },
|
||||
});
|
||||
expect(fetchFn.mock.calls[0][1].headers["Accept"]).toBe("text/html");
|
||||
@@ -288,7 +289,7 @@ describe("FetchHttpApi", () => {
|
||||
|
||||
describe("with a tokenRefreshFunction", () => {
|
||||
it("should emit logout and throw when token refresh fails", async () => {
|
||||
const error = new Error("uh oh");
|
||||
const error = new MatrixError();
|
||||
const tokenRefreshFunction = jest.fn().mockRejectedValue(error);
|
||||
const fetchFn = jest.fn().mockResolvedValue(unknownTokenResponse);
|
||||
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
|
||||
@@ -308,6 +309,27 @@ describe("FetchHttpApi", () => {
|
||||
expect(emitter.emit).toHaveBeenCalledWith(HttpApiEvent.SessionLoggedOut, unknownTokenErr);
|
||||
});
|
||||
|
||||
it("should not emit logout but still throw when token refresh fails due to transitive fault", async () => {
|
||||
const error = new ConnectionError("transitive fault");
|
||||
const tokenRefreshFunction = jest.fn().mockRejectedValue(error);
|
||||
const fetchFn = jest.fn().mockResolvedValue(unknownTokenResponse);
|
||||
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
|
||||
jest.spyOn(emitter, "emit");
|
||||
const api = new FetchHttpApi(emitter, {
|
||||
baseUrl,
|
||||
prefix,
|
||||
fetchFn,
|
||||
tokenRefreshFunction,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
});
|
||||
await expect(api.authedRequest(Method.Post, "/account/password")).rejects.toThrow(
|
||||
unknownTokenErr,
|
||||
);
|
||||
expect(tokenRefreshFunction).toHaveBeenCalledWith(refreshToken);
|
||||
expect(emitter.emit).not.toHaveBeenCalledWith(HttpApiEvent.SessionLoggedOut, unknownTokenErr);
|
||||
});
|
||||
|
||||
it("should refresh token and retry request", async () => {
|
||||
const newAccessToken = "new-access-token";
|
||||
const newRefreshToken = "new-refresh-token";
|
||||
@@ -468,4 +490,61 @@ describe("FetchHttpApi", () => {
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it("should not make multiple concurrent refresh token requests", async () => {
|
||||
const tokenInactiveError = new MatrixError({ errcode: "M_UNKNOWN_TOKEN", error: "Token is not active" }, 401);
|
||||
|
||||
const deferredTokenRefresh = defer<{ accessToken: string; refreshToken: string }>();
|
||||
const fetchFn = jest.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: tokenInactiveError.httpStatus,
|
||||
async text() {
|
||||
return JSON.stringify(tokenInactiveError.data);
|
||||
},
|
||||
async json() {
|
||||
return tokenInactiveError.data;
|
||||
},
|
||||
headers: {
|
||||
get: jest.fn().mockReturnValue("application/json"),
|
||||
},
|
||||
});
|
||||
const tokenRefreshFunction = jest.fn().mockReturnValue(deferredTokenRefresh.promise);
|
||||
|
||||
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||
baseUrl,
|
||||
prefix,
|
||||
fetchFn,
|
||||
doNotAttemptTokenRefresh: false,
|
||||
tokenRefreshFunction,
|
||||
accessToken: "ACCESS_TOKEN",
|
||||
refreshToken: "REFRESH_TOKEN",
|
||||
});
|
||||
|
||||
const prom1 = api.authedRequest(Method.Get, "/path1");
|
||||
const prom2 = api.authedRequest(Method.Get, "/path2");
|
||||
|
||||
await jest.advanceTimersByTimeAsync(10); // wait for requests to fire
|
||||
expect(fetchFn).toHaveBeenCalledTimes(2);
|
||||
fetchFn.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
async text() {
|
||||
return "{}";
|
||||
},
|
||||
async json() {
|
||||
return {};
|
||||
},
|
||||
headers: {
|
||||
get: jest.fn().mockReturnValue("application/json"),
|
||||
},
|
||||
});
|
||||
deferredTokenRefresh.resolve({ accessToken: "NEW_ACCESS_TOKEN", refreshToken: "NEW_REFRESH_TOKEN" });
|
||||
|
||||
await prom1;
|
||||
await prom2;
|
||||
expect(fetchFn).toHaveBeenCalledTimes(4); // 2 original calls + 2 retries
|
||||
expect(tokenRefreshFunction).toHaveBeenCalledTimes(1);
|
||||
expect(api.opts.accessToken).toBe("NEW_ACCESS_TOKEN");
|
||||
expect(api.opts.refreshToken).toBe("NEW_REFRESH_TOKEN");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,11 +59,12 @@ describe("MatrixHttpApi", () => {
|
||||
xhr.onreadystatechange?.(new Event("test"));
|
||||
});
|
||||
|
||||
it("should fall back to `fetch` where xhr is unavailable", () => {
|
||||
it("should fall back to `fetch` where xhr is unavailable", async () => {
|
||||
globalThis.XMLHttpRequest = undefined!;
|
||||
const fetchFn = jest.fn().mockResolvedValue({ ok: true, json: jest.fn().mockResolvedValue({}) });
|
||||
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn });
|
||||
upload = api.uploadContent({} as File);
|
||||
await upload;
|
||||
expect(fetchFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -2753,12 +2753,13 @@ describe("MatrixClient", function () {
|
||||
// WHEN we call `setAccountData` ...
|
||||
const setProm = client.setAccountData(eventType, content);
|
||||
|
||||
await jest.advanceTimersByTimeAsync(10);
|
||||
// THEN, the REST call should have happened, and had the correct content
|
||||
const lastCall = fetchMock.lastCall("put-account-data");
|
||||
expect(lastCall).toBeDefined();
|
||||
expect(lastCall?.[1]?.body).toEqual(JSON.stringify(content));
|
||||
|
||||
// Even after waiting a bit, the method should not yet have returned
|
||||
// Even after waiting a bit more, the method should not yet have returned
|
||||
await jest.advanceTimersByTimeAsync(10);
|
||||
let finished = false;
|
||||
setProm.finally(() => (finished = true));
|
||||
|
||||
@@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { encodeBase64, EventType, MatrixClient, MatrixError, type MatrixEvent, type Room } from "../../../src";
|
||||
import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEvent, type Room } from "../../../src";
|
||||
import { KnownMembership } from "../../../src/@types/membership";
|
||||
import { DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
|
||||
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
|
||||
import { type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
|
||||
import { secureRandomString } from "../../../src/randomstring";
|
||||
import { flushPromises } from "../../test-utils/flushPromises";
|
||||
import { makeMockRoom, makeMockRoomState, membershipTemplate } from "./mocks";
|
||||
|
||||
const mockFocus = { type: "mock" };
|
||||
@@ -37,10 +36,10 @@ describe("MatrixRTCSession", () => {
|
||||
client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
client.stopClient();
|
||||
client.matrixRTC.stop();
|
||||
if (sess) sess.stop();
|
||||
if (sess) await sess.stop();
|
||||
sess = undefined;
|
||||
});
|
||||
|
||||
@@ -322,11 +321,9 @@ describe("MatrixRTCSession", () => {
|
||||
let sendStateEventMock: jest.Mock;
|
||||
let sendDelayedStateMock: jest.Mock;
|
||||
let sendEventMock: jest.Mock;
|
||||
let updateDelayedEventMock: jest.Mock;
|
||||
|
||||
let sentStateEvent: Promise<void>;
|
||||
let sentDelayedState: Promise<void>;
|
||||
let updatedDelayedEvent: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
sentStateEvent = new Promise((resolve) => {
|
||||
@@ -340,15 +337,12 @@ describe("MatrixRTCSession", () => {
|
||||
};
|
||||
});
|
||||
});
|
||||
updatedDelayedEvent = new Promise((r) => {
|
||||
updateDelayedEventMock = jest.fn(r);
|
||||
});
|
||||
sendEventMock = jest.fn();
|
||||
client.sendStateEvent = sendStateEventMock;
|
||||
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
|
||||
client.sendEvent = sendEventMock;
|
||||
|
||||
client._unstable_updateDelayedEvent = updateDelayedEventMock;
|
||||
client._unstable_updateDelayedEvent = jest.fn();
|
||||
|
||||
mockRoom = makeMockRoom([]);
|
||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
||||
@@ -432,120 +426,6 @@ describe("MatrixRTCSession", () => {
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe("calls", () => {
|
||||
const activeFocusConfig = { type: "livekit", livekit_service_url: "https://active.url" };
|
||||
const activeFocus = { type: "livekit", focus_selection: "oldest_membership" };
|
||||
|
||||
async function testJoin(useOwnedStateEvents: boolean): Promise<void> {
|
||||
if (useOwnedStateEvents) {
|
||||
mockRoom.getVersion = jest.fn().mockReturnValue("org.matrix.msc3757.default");
|
||||
}
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
// preparing the delayed disconnect should handle the delay being too long
|
||||
const sendDelayedStateExceedAttempt = new Promise<void>((resolve) => {
|
||||
const error = new MatrixError({
|
||||
"errcode": "M_UNKNOWN",
|
||||
"org.matrix.msc4140.errcode": "M_MAX_DELAY_EXCEEDED",
|
||||
"org.matrix.msc4140.max_delay": 7500,
|
||||
});
|
||||
sendDelayedStateMock.mockImplementationOnce(() => {
|
||||
resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
const userStateKey = `${!useOwnedStateEvents ? "_" : ""}@alice:example.org_AAAAAAA`;
|
||||
// preparing the delayed disconnect should handle ratelimiting
|
||||
const sendDelayedStateAttempt = new Promise<void>((resolve) => {
|
||||
const error = new MatrixError({ errcode: "M_LIMIT_EXCEEDED" });
|
||||
sendDelayedStateMock.mockImplementationOnce(() => {
|
||||
resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
// setting the membership state should handle ratelimiting (also with a retry-after value)
|
||||
const sendStateEventAttempt = new Promise<void>((resolve) => {
|
||||
const error = new MatrixError(
|
||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||
429,
|
||||
undefined,
|
||||
undefined,
|
||||
new Headers({ "Retry-After": "1" }),
|
||||
);
|
||||
sendStateEventMock.mockImplementationOnce(() => {
|
||||
resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
sess!.joinRoomSession([activeFocusConfig], activeFocus, {
|
||||
membershipServerSideExpiryTimeout: 9000,
|
||||
});
|
||||
|
||||
await sendDelayedStateExceedAttempt.then(); // needed to resolve after the send attempt catches
|
||||
await sendDelayedStateAttempt;
|
||||
const callProps = (d: number) => {
|
||||
return [mockRoom!.roomId, { delay: d }, "org.matrix.msc3401.call.member", {}, userStateKey];
|
||||
};
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenNthCalledWith(1, ...callProps(9000));
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenNthCalledWith(2, ...callProps(7500));
|
||||
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
await sendStateEventAttempt.then(); // needed to resolve after resendIfRateLimited catches
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
await sentStateEvent;
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
mockRoom!.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
{
|
||||
application: "m.call",
|
||||
scope: "m.room",
|
||||
call_id: "",
|
||||
expires: 14400000,
|
||||
device_id: "AAAAAAA",
|
||||
foci_preferred: [activeFocusConfig],
|
||||
focus_active: activeFocus,
|
||||
} satisfies SessionMembershipData,
|
||||
userStateKey,
|
||||
);
|
||||
await sentDelayedState;
|
||||
|
||||
// should have prepared the heartbeat to keep delaying the leave event while still connected
|
||||
await updatedDelayedEvent;
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// ensures that we reach the code that schedules the timeout for the next delay update before we advance the timers.
|
||||
await flushPromises();
|
||||
jest.advanceTimersByTime(5000);
|
||||
// should update delayed disconnect
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(2);
|
||||
|
||||
jest.useRealTimers();
|
||||
}
|
||||
|
||||
it("sends a membership event with session payload when joining a call", async () => {
|
||||
await testJoin(false);
|
||||
});
|
||||
|
||||
it("does not prefix the state key with _ for rooms that support user-owned state events", async () => {
|
||||
await testJoin(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("does nothing if join called when already joined", async () => {
|
||||
sess!.joinRoomSession([mockFocus], mockFocus);
|
||||
await sentStateEvent;
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
sess!.joinRoomSession([mockFocus], mockFocus);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("onMembershipsChanged", () => {
|
||||
@@ -616,9 +496,9 @@ describe("MatrixRTCSession", () => {
|
||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
// stop the timers
|
||||
sess!.leaveRoomSession();
|
||||
await sess!.leaveRoomSession();
|
||||
});
|
||||
|
||||
it("creates a key when joining", () => {
|
||||
@@ -715,7 +595,7 @@ describe("MatrixRTCSession", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("cancels key send event that fail", async () => {
|
||||
it("cancels key send event that fail", () => {
|
||||
const eventSentinel = {} as unknown as MatrixEvent;
|
||||
|
||||
client.cancelPendingEvent = jest.fn();
|
||||
|
||||
@@ -32,7 +32,7 @@ import { makeMockRoom, makeMockRoomState, membershipTemplate } from "./mocks";
|
||||
describe("MatrixRTCSessionManager", () => {
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
client = new MatrixClient({ baseUrl: "base_url" });
|
||||
client.matrixRTC.start();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,606 @@
|
||||
/**
|
||||
* @jest-environment ./spec/unit/matrixrtc/memberManagerTestEnvironment.ts
|
||||
*/
|
||||
/*
|
||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type MockedFunction, type Mock } from "jest-mock";
|
||||
|
||||
import { EventType, HTTPError, MatrixError, type Room } from "../../../src";
|
||||
import { type Focus, type LivekitFocusActive, type SessionMembershipData } from "../../../src/matrixrtc";
|
||||
import { LegacyMembershipManager } from "../../../src/matrixrtc/MembershipManager";
|
||||
import { makeMockClient, makeMockRoom, membershipTemplate, mockCallMembership, type MockClient } from "./mocks";
|
||||
import { defer } from "../../../src/utils";
|
||||
|
||||
function waitForMockCall(method: MockedFunction<any>, returnVal?: Promise<any>) {
|
||||
return new Promise<void>((resolve) => {
|
||||
method.mockImplementation(() => {
|
||||
resolve();
|
||||
return returnVal ?? Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createAsyncHandle(method: MockedFunction<any>) {
|
||||
const { reject, resolve, promise } = defer();
|
||||
method.mockImplementation(() => promise);
|
||||
return { reject, resolve };
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests different MembershipManager implementations. Some tests don't apply to `LegacyMembershipManager`
|
||||
* use !FailsForLegacy to skip those. See: testEnvironment for more details.
|
||||
*/
|
||||
describe.each([
|
||||
{ TestMembershipManager: LegacyMembershipManager, description: "LegacyMembershipManager" },
|
||||
// { TestMembershipManager: MembershipManager, description: "MembershipManager" },
|
||||
])("$description", ({ TestMembershipManager }) => {
|
||||
let client: MockClient;
|
||||
let room: Room;
|
||||
const focusActive: LivekitFocusActive = {
|
||||
focus_selection: "oldest_membership",
|
||||
type: "livekit",
|
||||
};
|
||||
const focus: Focus = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://active.url",
|
||||
livekit_alias: "!active:active.url",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Default to fake timers.
|
||||
jest.useFakeTimers();
|
||||
client = makeMockClient("@alice:example.org", "AAAAAAA");
|
||||
room = makeMockRoom(membershipTemplate);
|
||||
// Provide a default mock that is like the default "non error" server behaviour.
|
||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockResolvedValue({ delay_id: "id" });
|
||||
(client._unstable_updateDelayedEvent as Mock<any>).mockResolvedValue(undefined);
|
||||
(client.sendStateEvent as Mock<any>).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
// There is no need to clean up mocks since we will recreate the client.
|
||||
});
|
||||
|
||||
describe("isJoined()", () => {
|
||||
it("defaults to false", () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
expect(manager.isJoined()).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns true after join()", () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([]);
|
||||
expect(manager.isJoined()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("join()", () => {
|
||||
describe("sends a membership event", () => {
|
||||
it("sends a membership event and schedules delayed leave when joining a call", async () => {
|
||||
// Spys/Mocks
|
||||
|
||||
const updateDelayedEventHandle = createAsyncHandle(client._unstable_updateDelayedEvent as Mock);
|
||||
|
||||
// Test
|
||||
const memberManager = new TestMembershipManager(undefined, room, client, () => undefined);
|
||||
memberManager.join([focus], focusActive);
|
||||
// expects
|
||||
await waitForMockCall(client.sendStateEvent);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
"org.matrix.msc3401.call.member",
|
||||
{
|
||||
application: "m.call",
|
||||
call_id: "",
|
||||
device_id: "AAAAAAA",
|
||||
expires: 14400000,
|
||||
foci_preferred: [focus],
|
||||
focus_active: focusActive,
|
||||
scope: "m.room",
|
||||
},
|
||||
"_@alice:example.org_AAAAAAA",
|
||||
);
|
||||
updateDelayedEventHandle.resolve?.();
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
{ delay: 8000 },
|
||||
"org.matrix.msc3401.call.member",
|
||||
{},
|
||||
"_@alice:example.org_AAAAAAA",
|
||||
);
|
||||
});
|
||||
|
||||
describe("does not prefix the state key with _ for rooms that support user-owned state events", () => {
|
||||
async function testJoin(useOwnedStateEvents: boolean): Promise<void> {
|
||||
// TODO: this test does quiet a bit. Its more a like a test story summarizing to:
|
||||
// - send delay with too long timeout and get server error (test delayedEventTimeout gets overwritten)
|
||||
// - run into rate limit for sending delayed event
|
||||
// - run into rate limit when setting membership state.
|
||||
if (useOwnedStateEvents) {
|
||||
room.getVersion = jest.fn().mockReturnValue("org.matrix.msc3757.default");
|
||||
}
|
||||
const updatedDelayedEvent = waitForMockCall(client._unstable_updateDelayedEvent);
|
||||
const sentDelayedState = waitForMockCall(
|
||||
client._unstable_sendDelayedStateEvent,
|
||||
Promise.resolve({
|
||||
delay_id: "id",
|
||||
}),
|
||||
);
|
||||
|
||||
// preparing the delayed disconnect should handle the delay being too long
|
||||
const sendDelayedStateExceedAttempt = new Promise<void>((resolve) => {
|
||||
const error = new MatrixError({
|
||||
"errcode": "M_UNKNOWN",
|
||||
"org.matrix.msc4140.errcode": "M_MAX_DELAY_EXCEEDED",
|
||||
"org.matrix.msc4140.max_delay": 7500,
|
||||
});
|
||||
(client._unstable_sendDelayedStateEvent as Mock).mockImplementationOnce(() => {
|
||||
resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
const userStateKey = `${!useOwnedStateEvents ? "_" : ""}@alice:example.org_AAAAAAA`;
|
||||
// preparing the delayed disconnect should handle ratelimiting
|
||||
const sendDelayedStateAttempt = new Promise<void>((resolve) => {
|
||||
const error = new MatrixError({ errcode: "M_LIMIT_EXCEEDED" });
|
||||
(client._unstable_sendDelayedStateEvent as Mock).mockImplementationOnce(() => {
|
||||
resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
// setting the membership state should handle ratelimiting (also with a retry-after value)
|
||||
const sendStateEventAttempt = new Promise<void>((resolve) => {
|
||||
const error = new MatrixError(
|
||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||
429,
|
||||
undefined,
|
||||
undefined,
|
||||
new Headers({ "Retry-After": "1" }),
|
||||
);
|
||||
(client.sendStateEvent as Mock).mockImplementationOnce(() => {
|
||||
resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
const manager = new TestMembershipManager(
|
||||
{
|
||||
membershipServerSideExpiryTimeout: 9000,
|
||||
},
|
||||
room,
|
||||
client,
|
||||
() => undefined,
|
||||
);
|
||||
manager.join([focus], focusActive);
|
||||
|
||||
await sendDelayedStateExceedAttempt.then(); // needed to resolve after the send attempt catches
|
||||
await sendDelayedStateAttempt;
|
||||
const callProps = (d: number) => {
|
||||
return [room!.roomId, { delay: d }, "org.matrix.msc3401.call.member", {}, userStateKey];
|
||||
};
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenNthCalledWith(1, ...callProps(9000));
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenNthCalledWith(2, ...callProps(7500));
|
||||
|
||||
await jest.advanceTimersByTimeAsync(5000);
|
||||
|
||||
await sendStateEventAttempt.then(); // needed to resolve after resendIfRateLimited catches
|
||||
|
||||
await jest.advanceTimersByTimeAsync(1000);
|
||||
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
room!.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
{
|
||||
application: "m.call",
|
||||
scope: "m.room",
|
||||
call_id: "",
|
||||
expires: 14400000,
|
||||
device_id: "AAAAAAA",
|
||||
foci_preferred: [focus],
|
||||
focus_active: focusActive,
|
||||
} satisfies SessionMembershipData,
|
||||
userStateKey,
|
||||
);
|
||||
await sentDelayedState;
|
||||
|
||||
// should have prepared the heartbeat to keep delaying the leave event while still connected
|
||||
await updatedDelayedEvent;
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// ensures that we reach the code that schedules the timeout for the next delay update before we advance the timers.
|
||||
await jest.advanceTimersByTimeAsync(5000);
|
||||
// should update delayed disconnect
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
it("sends a membership event after rate limits during delayed event setup when joining a call", async () => {
|
||||
await testJoin(false);
|
||||
});
|
||||
|
||||
it("does not prefix the state key with _ for rooms that support user-owned state events", async () => {
|
||||
await testJoin(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("delayed leave event", () => {
|
||||
it("does not try again to schedule a delayed leave event if not supported", () => {
|
||||
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
delayedHandle.reject?.(Error("Server does not support the delayed events API"));
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("does try to schedule a delayed leave event again if rate limited", async () => {
|
||||
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
delayedHandle.reject?.(new HTTPError("rate limited", 429, undefined));
|
||||
await jest.advanceTimersByTimeAsync(5000);
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
it("uses membershipServerSideExpiryTimeout from config", () => {
|
||||
const manager = new TestMembershipManager(
|
||||
{ membershipServerSideExpiryTimeout: 123456 },
|
||||
room,
|
||||
client,
|
||||
() => undefined,
|
||||
);
|
||||
manager.join([focus], focusActive);
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
{ delay: 123456 },
|
||||
"org.matrix.msc3401.call.member",
|
||||
{},
|
||||
"_@alice:example.org_AAAAAAA",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("uses membershipExpiryTimeout from config", async () => {
|
||||
const manager = new TestMembershipManager(
|
||||
{ membershipExpiryTimeout: 1234567 },
|
||||
room,
|
||||
client,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
manager.join([focus], focusActive);
|
||||
await waitForMockCall(client.sendStateEvent);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
{
|
||||
application: "m.call",
|
||||
scope: "m.room",
|
||||
call_id: "",
|
||||
device_id: "AAAAAAA",
|
||||
expires: 1234567,
|
||||
foci_preferred: [focus],
|
||||
focus_active: {
|
||||
focus_selection: "oldest_membership",
|
||||
type: "livekit",
|
||||
},
|
||||
},
|
||||
"_@alice:example.org_AAAAAAA",
|
||||
);
|
||||
});
|
||||
|
||||
it("does nothing if join called when already joined", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
await waitForMockCall(client.sendStateEvent);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
manager.join([focus], focusActive);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("leave()", () => {
|
||||
// TODO add rate limit cases.
|
||||
it("resolves delayed leave event when leave is called", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
await manager.leave();
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenLastCalledWith("id", "send");
|
||||
expect(client.sendStateEvent).toHaveBeenCalled();
|
||||
});
|
||||
it("send leave event when leave is called and resolving delayed leave fails", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue("unknown");
|
||||
await manager.leave();
|
||||
// We send a normal leave event since we failed using updateDelayedEvent with the "send" action.
|
||||
expect(client.sendStateEvent).toHaveBeenLastCalledWith(
|
||||
room.roomId,
|
||||
"org.matrix.msc3401.call.member",
|
||||
{},
|
||||
"_@alice:example.org_AAAAAAA",
|
||||
);
|
||||
});
|
||||
// FailsForLegacy because legacy implementation always sends the empty state event even though it isn't needed
|
||||
it("does nothing if not joined !FailsForLegacy", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
await manager.leave();
|
||||
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getsActiveFocus", () => {
|
||||
it("gets the correct active focus with oldest_membership", () => {
|
||||
const getOldestMembership = jest.fn();
|
||||
const manager = new TestMembershipManager({}, room, client, getOldestMembership);
|
||||
// Before joining the active focus should be undefined (see FocusInUse on MatrixRTCSession)
|
||||
expect(manager.getActiveFocus()).toBe(undefined);
|
||||
manager.join([focus], focusActive);
|
||||
// After joining we want our own focus to be the one we select.
|
||||
getOldestMembership.mockReturnValue(
|
||||
mockCallMembership(
|
||||
{
|
||||
...membershipTemplate,
|
||||
foci_preferred: [
|
||||
{
|
||||
livekit_alias: "!active:active.url",
|
||||
livekit_service_url: "https://active.url",
|
||||
type: "livekit",
|
||||
},
|
||||
],
|
||||
device_id: client.getDeviceId(),
|
||||
created_ts: 1000,
|
||||
},
|
||||
room.roomId,
|
||||
client.getUserId()!,
|
||||
),
|
||||
);
|
||||
expect(manager.getActiveFocus()).toStrictEqual(focus);
|
||||
getOldestMembership.mockReturnValue(
|
||||
mockCallMembership(
|
||||
Object.assign({}, membershipTemplate, { device_id: "old", created_ts: 1000 }),
|
||||
room.roomId,
|
||||
),
|
||||
);
|
||||
// If there is an older member we use its focus.
|
||||
expect(manager.getActiveFocus()).toBe(membershipTemplate.foci_preferred[0]);
|
||||
});
|
||||
|
||||
it("does not provide focus if the selection method is unknown", () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], Object.assign(focusActive, { type: "unknown_type" }));
|
||||
expect(manager.getActiveFocus()).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("onRTCSessionMemberUpdate()", () => {
|
||||
it("does nothing if not joined", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
||||
await jest.advanceTimersToNextTimerAsync();
|
||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
||||
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
it("does nothing if own membership still present", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
const myMembership = (client.sendStateEvent as Mock).mock.calls[0][2];
|
||||
// reset all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
||||
(client.sendStateEvent as Mock).mockClear();
|
||||
(client._unstable_updateDelayedEvent as Mock).mockClear();
|
||||
(client._unstable_sendDelayedStateEvent as Mock).mockClear();
|
||||
|
||||
await manager.onRTCSessionMemberUpdate([
|
||||
mockCallMembership(membershipTemplate, room.roomId),
|
||||
mockCallMembership(myMembership as SessionMembershipData, room.roomId, client.getUserId() ?? undefined),
|
||||
]);
|
||||
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
|
||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
||||
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
it("recreates membership if it is missing", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
||||
(client.sendStateEvent as Mock).mockClear();
|
||||
(client._unstable_updateDelayedEvent as Mock).mockClear();
|
||||
(client._unstable_sendDelayedStateEvent as Mock).mockClear();
|
||||
|
||||
// Our own membership is removed:
|
||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
expect(client.sendStateEvent).toHaveBeenCalled();
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalled();
|
||||
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Not sure about this name
|
||||
describe("background timers", () => {
|
||||
it("sends only one keep-alive for delayed leave event per `membershipKeepAlivePeriod`", async () => {
|
||||
const manager = new TestMembershipManager(
|
||||
{ membershipKeepAlivePeriod: 10_000, membershipServerSideExpiryTimeout: 30_000 },
|
||||
room,
|
||||
client,
|
||||
() => undefined,
|
||||
);
|
||||
manager.join([focus], focusActive);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The first call is from checking id the server deleted the delayed event
|
||||
// so it does not need a `advanceTimersByTime`
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1);
|
||||
// TODO: Check that update delayed event is called with the correct HTTP request timeout
|
||||
// expect(client._unstable_updateDelayedEvent).toHaveBeenLastCalledWith("id", 10_000, { localTimeoutMs: 20_000 });
|
||||
|
||||
for (let i = 2; i <= 12; i++) {
|
||||
// flush promises before advancing the timers to make sure schedulers are setup
|
||||
await jest.advanceTimersByTimeAsync(10_000);
|
||||
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(i);
|
||||
// TODO: Check that update delayed event is called with the correct HTTP request timeout
|
||||
// expect(client._unstable_updateDelayedEvent).toHaveBeenLastCalledWith("id", 10_000, { localTimeoutMs: 20_000 });
|
||||
}
|
||||
});
|
||||
|
||||
// !FailsForLegacy because the expires logic was removed for the legacy call manager.
|
||||
// Delayed events should replace it entirely but before they have wide adoption
|
||||
// the expiration logic still makes sense.
|
||||
// TODO: add git commit when we removed it.
|
||||
it("extends `expires` when call still active !FailsForLegacy", async () => {
|
||||
const manager = new TestMembershipManager(
|
||||
{ membershipExpiryTimeout: 10_000 },
|
||||
room,
|
||||
client,
|
||||
() => undefined,
|
||||
);
|
||||
manager.join([focus], focusActive);
|
||||
await waitForMockCall(client.sendStateEvent);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
const sentMembership = (client.sendStateEvent as Mock).mock.calls[0][2] as SessionMembershipData;
|
||||
expect(sentMembership.expires).toBe(10_000);
|
||||
for (let i = 2; i <= 12; i++) {
|
||||
await jest.advanceTimersByTimeAsync(10_000);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(i);
|
||||
const sentMembership = (client.sendStateEvent as Mock).mock.lastCall![2] as SessionMembershipData;
|
||||
expect(sentMembership.expires).toBe(10_000 * i);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("server error handling", () => {
|
||||
// Types of server error: 429 rate limit with no retry-after header, 429 with retry-after, 50x server error (maybe retry every second), connection/socket timeout
|
||||
describe("retries sending delayed leave event", () => {
|
||||
it("sends retry if call membership event is still valid at time of retry", async () => {
|
||||
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
handle.reject?.(
|
||||
new MatrixError(
|
||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||
429,
|
||||
undefined,
|
||||
undefined,
|
||||
new Headers({ "Retry-After": "1" }),
|
||||
),
|
||||
);
|
||||
await jest.advanceTimersByTimeAsync(1000);
|
||||
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
// FailsForLegacy as implementation does not re-check membership before retrying.
|
||||
it("abandons retry loop and sends new own membership if not present anymore !FailsForLegacy", async () => {
|
||||
(client._unstable_sendDelayedStateEvent as any).mockRejectedValue(
|
||||
new MatrixError(
|
||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||
429,
|
||||
undefined,
|
||||
undefined,
|
||||
new Headers({ "Retry-After": "1" }),
|
||||
),
|
||||
);
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
// Should call _unstable_sendDelayedStateEvent but not sendStateEvent because of the
|
||||
// RateLimit error.
|
||||
manager.join([focus], focusActive);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockResolvedValue({ delay_id: "id" });
|
||||
// Remove our own membership so that there is no reason the send the delayed leave anymore.
|
||||
// the membership is no longer present on the homeserver
|
||||
await manager.onRTCSessionMemberUpdate([]);
|
||||
// Wait for all timers to be setup
|
||||
await jest.advanceTimersByTimeAsync(1000);
|
||||
// We should send the first own membership and a new delayed event after the rate limit timeout.
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
// FailsForLegacy as implementation does not re-check membership before retrying.
|
||||
it("abandons retry loop if leave() was called !FailsForLegacy", async () => {
|
||||
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
handle.reject?.(
|
||||
new MatrixError(
|
||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||
429,
|
||||
undefined,
|
||||
undefined,
|
||||
new Headers({ "Retry-After": "1" }),
|
||||
),
|
||||
);
|
||||
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
// the user terminated the call locally
|
||||
await manager.leave();
|
||||
|
||||
// Wait for all timers to be setup
|
||||
// await flushPromises();
|
||||
await jest.advanceTimersByTimeAsync(1000);
|
||||
|
||||
// No new events should have been sent:
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
describe("retries sending update delayed leave event restart", () => {
|
||||
it("resends the initial check delayed update event !FailsForLegacy", async () => {
|
||||
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue(
|
||||
new MatrixError(
|
||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||
429,
|
||||
undefined,
|
||||
undefined,
|
||||
new Headers({ "Retry-After": "1" }),
|
||||
),
|
||||
);
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([focus], focusActive);
|
||||
|
||||
// Hit rate limit
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Hit second rate limit.
|
||||
await jest.advanceTimersByTimeAsync(1000);
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Setup resolve
|
||||
(client._unstable_updateDelayedEvent as Mock<any>).mockResolvedValue(undefined);
|
||||
await jest.advanceTimersByTimeAsync(1000);
|
||||
|
||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(3);
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
This file adds a custom test environment for the MembershipManager.spec.ts
|
||||
It can be used with the comment at the top of the file:
|
||||
|
||||
@jest-environment ./spec/unit/matrixrtc/memberManagerTestEnvironment.ts
|
||||
|
||||
It is very specific to the MembershipManager.spec.ts file and introduces the following behaviour:
|
||||
- The describe each block in the MembershipManager.spec.ts will go through describe block names `LegacyMembershipManager` and `MembershipManager`
|
||||
- It will check all tests that are a child or indirect child of the `LegacyMembershipManager` block and skip the ones which include "!FailsForLegacy"
|
||||
in their test name.
|
||||
*/
|
||||
|
||||
import { TestEnvironment } from "jest-environment-jsdom";
|
||||
|
||||
import { logger as rootLogger } from "../../../src/logger";
|
||||
const logger = rootLogger.getChild("MatrixRTCSession");
|
||||
|
||||
class MemberManagerTestEnvironment extends TestEnvironment {
|
||||
handleTestEvent(event: any) {
|
||||
if (event.name === "test_start" && event.test.name.includes("!FailsForLegacy")) {
|
||||
let parent = event.test.parent;
|
||||
let isLegacy = false;
|
||||
while (parent) {
|
||||
if (parent.name === "LegacyMembershipManager") {
|
||||
isLegacy = true;
|
||||
break;
|
||||
} else {
|
||||
parent = parent.parent;
|
||||
}
|
||||
}
|
||||
if (isLegacy) {
|
||||
logger.info("skip test: ", event.test.name);
|
||||
event.test.mode = "skip";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = MemberManagerTestEnvironment;
|
||||
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventType, type MatrixEvent, type Room } from "../../../src";
|
||||
import { type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
|
||||
import { EventType, type MatrixClient, type MatrixEvent, type Room } from "../../../src";
|
||||
import { CallMembership, type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
|
||||
import { secureRandomString } from "../../../src/randomstring";
|
||||
|
||||
type MembershipData = SessionMembershipData[] | SessionMembershipData | {};
|
||||
@@ -40,6 +40,31 @@ export const membershipTemplate: SessionMembershipData = {
|
||||
],
|
||||
};
|
||||
|
||||
export type MockClient = Pick<
|
||||
MatrixClient,
|
||||
| "getUserId"
|
||||
| "getDeviceId"
|
||||
| "sendEvent"
|
||||
| "sendStateEvent"
|
||||
| "_unstable_sendDelayedStateEvent"
|
||||
| "_unstable_updateDelayedEvent"
|
||||
| "cancelPendingEvent"
|
||||
>;
|
||||
/**
|
||||
* Mocks a object that has all required methods for a MatrixRTC session client.
|
||||
*/
|
||||
export function makeMockClient(userId: string, deviceId: string): MockClient {
|
||||
return {
|
||||
getDeviceId: () => deviceId,
|
||||
getUserId: () => userId,
|
||||
sendEvent: jest.fn(),
|
||||
sendStateEvent: jest.fn(),
|
||||
cancelPendingEvent: jest.fn(),
|
||||
_unstable_updateDelayedEvent: jest.fn(),
|
||||
_unstable_sendDelayedStateEvent: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
export function makeMockRoom(membershipData: MembershipData): Room {
|
||||
const roomId = secureRandomString(8);
|
||||
// Caching roomState here so it does not get recreated when calling `getLiveTimeline.getState()`
|
||||
@@ -88,16 +113,17 @@ export function makeMockRoomState(membershipData: MembershipData, roomId: string
|
||||
};
|
||||
}
|
||||
|
||||
export function mockRTCEvent(membershipData: MembershipData, roomId: string): MatrixEvent {
|
||||
export function mockRTCEvent(membershipData: MembershipData, roomId: string, customSender?: string): MatrixEvent {
|
||||
const sender = customSender ?? "@mock:user.example";
|
||||
return {
|
||||
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
|
||||
getContent: jest.fn().mockReturnValue(membershipData),
|
||||
getSender: jest.fn().mockReturnValue("@mock:user.example"),
|
||||
getSender: jest.fn().mockReturnValue(sender),
|
||||
getTs: jest.fn().mockReturnValue(Date.now()),
|
||||
getRoomId: jest.fn().mockReturnValue(roomId),
|
||||
sender: {
|
||||
userId: "@mock:user.example",
|
||||
},
|
||||
isDecryptionFailure: jest.fn().mockReturnValue(false),
|
||||
} as unknown as MatrixEvent;
|
||||
}
|
||||
export function mockCallMembership(membershipData: MembershipData, roomId: string, sender?: string): CallMembership {
|
||||
return new CallMembership(mockRTCEvent(membershipData, roomId, sender), membershipData);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
SignatureUploadRequest,
|
||||
UploadSigningKeysRequest,
|
||||
ToDeviceRequest,
|
||||
type OutgoingRequest,
|
||||
} from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
@@ -271,7 +272,7 @@ describe("OutgoingRequestProcessor", () => {
|
||||
});
|
||||
|
||||
it("does not explode with unknown requests", async () => {
|
||||
const outgoingRequest = { id: "5678", type: 987 };
|
||||
const outgoingRequest = { id: "5678", type: 987 } as unknown as OutgoingRequest;
|
||||
const markSentCallPromise = awaitCallToMarkAsSent();
|
||||
await Promise.all([processor.makeOutgoingRequest(outgoingRequest), markSentCallPromise]);
|
||||
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("5678", 987, "");
|
||||
|
||||
@@ -16,8 +16,9 @@ limitations under the License.
|
||||
|
||||
import { type Mocked } from "jest-mock";
|
||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import { type OutgoingRequest } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
|
||||
import { type OutgoingRequest, type OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import { type OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import { OutgoingRequestsManager } from "../../../src/rust-crypto/OutgoingRequestsManager";
|
||||
import { defer, type IDeferred } from "../../../src/utils";
|
||||
import { logger } from "../../../src/logger";
|
||||
@@ -72,7 +73,7 @@ describe("OutgoingRequestsManager", () => {
|
||||
const firstOutgoingRequestDefer = defer<OutgoingRequest[]>();
|
||||
|
||||
olmMachine.outgoingRequests
|
||||
.mockImplementationOnce(async (): Promise<OutgoingRequest[]> => {
|
||||
.mockImplementationOnce(async () => {
|
||||
return firstOutgoingRequestDefer.promise;
|
||||
})
|
||||
.mockImplementationOnce(async () => {
|
||||
|
||||
@@ -500,8 +500,10 @@ describe("RustCrypto", () => {
|
||||
const someRoomKeys = testData.MEGOLM_SESSION_DATA_ARRAY;
|
||||
let importTotal = 0;
|
||||
const opt: ImportRoomKeysOpts = {
|
||||
progressCallback: (stage) => {
|
||||
importTotal = stage.total ?? 0;
|
||||
progressCallback: (progress) => {
|
||||
if (progress.stage === "load_keys") {
|
||||
importTotal = progress.total;
|
||||
}
|
||||
},
|
||||
};
|
||||
await rustCrypto.importRoomKeys(someRoomKeys, opt);
|
||||
@@ -523,8 +525,10 @@ describe("RustCrypto", () => {
|
||||
const someRoomKeys = testData.MEGOLM_SESSION_DATA_ARRAY;
|
||||
let importTotal = 0;
|
||||
const opt: ImportRoomKeysOpts = {
|
||||
progressCallback: (stage) => {
|
||||
importTotal = stage.total ?? 0;
|
||||
progressCallback: (progress) => {
|
||||
if (progress.stage === "load_keys") {
|
||||
importTotal = progress.total;
|
||||
}
|
||||
},
|
||||
};
|
||||
await rustCrypto.importRoomKeysAsJson(JSON.stringify(someRoomKeys), opt);
|
||||
@@ -2054,6 +2058,41 @@ describe("RustCrypto", () => {
|
||||
expect(await secretStorage.get("org.matrix.msc3814")).not.toEqual(origDehydrationKey);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors when deleting a dehydrated device", async () => {
|
||||
const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi());
|
||||
const dehydratedDeviceManager = rustCrypto["dehydratedDeviceManager"];
|
||||
fetchMock.config.overwriteRoutes = true;
|
||||
// if the server doesn't support dehydrated devices, delete should succeed without throwing an error
|
||||
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
|
||||
status: 404,
|
||||
body: {
|
||||
errcode: "M_UNRECOGNIZED",
|
||||
error: "Unknown endpoint",
|
||||
},
|
||||
});
|
||||
await dehydratedDeviceManager.delete();
|
||||
|
||||
// if there is no dehydrated device, delete should succeed without throwing an error
|
||||
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
|
||||
status: 404,
|
||||
body: {
|
||||
errcode: "M_NOT_FOUND",
|
||||
error: "Not found",
|
||||
},
|
||||
});
|
||||
await dehydratedDeviceManager.delete();
|
||||
|
||||
// for any other error response, delete should throw an error
|
||||
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
|
||||
status: 400,
|
||||
body: {
|
||||
errcode: "M_UNKNOWN",
|
||||
error: "Unknown error",
|
||||
},
|
||||
});
|
||||
await expect(dehydratedDeviceManager.delete()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("import & export secrets bundle", () => {
|
||||
@@ -2227,7 +2266,7 @@ describe("RustCrypto", () => {
|
||||
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {});
|
||||
});
|
||||
|
||||
it("reset should reset 4S, backup and cross-signing", async () => {
|
||||
it("reset should reset 4S, backup, cross-signing, and dehydrated device", async () => {
|
||||
// When we will delete the key backup
|
||||
let backupIsDeleted = false;
|
||||
fetchMock.delete("path:/_matrix/client/v3/room_keys/version/1", () => {
|
||||
@@ -2239,6 +2278,12 @@ describe("RustCrypto", () => {
|
||||
return backupIsDeleted ? {} : testData.SIGNED_BACKUP_DATA;
|
||||
});
|
||||
|
||||
let dehydratedDeviceIsDeleted = false;
|
||||
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", () => {
|
||||
dehydratedDeviceIsDeleted = true;
|
||||
return { device_id: "ADEVICEID" };
|
||||
});
|
||||
|
||||
// A new key backup should be created after the reset
|
||||
let newKeyBackupInfo!: KeyBackupInfo;
|
||||
fetchMock.post("path:/_matrix/client/v3/room_keys/version", (res, options) => {
|
||||
@@ -2269,6 +2314,8 @@ describe("RustCrypto", () => {
|
||||
expect(newKeyBackupInfo.auth_data).toBeTruthy();
|
||||
// The new cross signing keys should be uploaded
|
||||
expect(authUploadDeviceSigningKeys).toHaveBeenCalledWith(expect.any(Function));
|
||||
// The dehydrated device was deleted
|
||||
expect(dehydratedDeviceIsDeleted).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import { type Mocked } from "jest-mock";
|
||||
import { type OutgoingRequest } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
|
||||
import {
|
||||
isVerificationEvent,
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
type Verifier,
|
||||
VerifierEvent,
|
||||
} from "../../../src/crypto-api/verification";
|
||||
import { type OutgoingRequest, type OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import { type OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import { type IDeviceKeys } from "../../../src/@types/crypto";
|
||||
import { EventType, MatrixEvent, MsgType } from "../../../src";
|
||||
|
||||
|
||||
@@ -138,6 +138,8 @@ export enum EventType {
|
||||
RoomKeyRequest = "m.room_key_request",
|
||||
ForwardedRoomKey = "m.forwarded_room_key",
|
||||
Dummy = "m.dummy",
|
||||
SecretRequest = "m.secret.request",
|
||||
SecretSend = "m.secret.send",
|
||||
|
||||
// Group call events
|
||||
GroupCallPrefix = "org.matrix.msc3401.call",
|
||||
|
||||
-10
@@ -17,16 +17,6 @@ limitations under the License.
|
||||
import type * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
|
||||
declare module "@matrix-org/matrix-sdk-crypto-wasm" {
|
||||
interface OlmMachine {
|
||||
importSecretsBundle(bundle: RustSdkCryptoJs.SecretsBundle): Promise<void>;
|
||||
exportSecretsBundle(): Promise<RustSdkCryptoJs.SecretsBundle>;
|
||||
importCrossSigningKeys(
|
||||
master_key?: string,
|
||||
self_signing_key?: string,
|
||||
user_signing_key?: string,
|
||||
): Promise<RustSdkCryptoJs.CrossSigningStatus>;
|
||||
}
|
||||
|
||||
interface SecretsBundle {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
to_json(): Promise<{
|
||||
|
||||
+6
-1
@@ -3414,7 +3414,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
public async _unstable_updateDelayedEvent(delayId: string, action: UpdateDelayedEventAction): Promise<EmptyObject> {
|
||||
public async _unstable_updateDelayedEvent(
|
||||
delayId: string,
|
||||
action: UpdateDelayedEventAction,
|
||||
requestOptions: IRequestOpts = {},
|
||||
): Promise<EmptyObject> {
|
||||
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
|
||||
throw Error("Server does not support the delayed events API");
|
||||
}
|
||||
@@ -3426,6 +3430,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
action,
|
||||
};
|
||||
return await this.http.authedRequest(Method.Post, path, undefined, data, {
|
||||
...requestOptions,
|
||||
prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,
|
||||
});
|
||||
}
|
||||
|
||||
+66
-6
@@ -436,11 +436,17 @@ export interface CryptoApi {
|
||||
|
||||
/**
|
||||
* Reset the encryption of the user by going through the following steps:
|
||||
* - Remove the dehydrated device and stop the periodic creation of dehydrated devices.
|
||||
* - Disable backing up room keys and delete any existing backups.
|
||||
* - Remove the default secret storage key from the account data (ie: the recovery key).
|
||||
* - Reset the cross-signing keys.
|
||||
* - Create a new key backup.
|
||||
*
|
||||
* Note that the dehydrated device will be removed, but will not be replaced
|
||||
* and it will not schedule creating new dehydrated devices. To do this,
|
||||
* {@link startDehydration} should be called after a new secret storage key
|
||||
* is created.
|
||||
*
|
||||
* @param authUploadDeviceSigningKeys - Callback to authenticate the upload of device signing keys.
|
||||
* Used when resetting the cross signing keys.
|
||||
* See {@link BootstrapCrossSigningOpts#authUploadDeviceSigningKeys}.
|
||||
@@ -1000,18 +1006,72 @@ export class DeviceVerificationStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum representing the different stages of importing room keys.
|
||||
*
|
||||
* This is the type of the `stage` property of {@link ImportRoomKeyProgressData}.
|
||||
*/
|
||||
export enum ImportRoomKeyStage {
|
||||
/**
|
||||
* The stage where room keys are being fetched.
|
||||
*
|
||||
* @see {@link ImportRoomKeyFetchProgress}.
|
||||
*/
|
||||
Fetch = "fetch",
|
||||
/**
|
||||
* The stage where room keys are being loaded.
|
||||
*
|
||||
* @see {@link ImportRoomKeyLoadProgress}.
|
||||
*/
|
||||
LoadKeys = "load_keys",
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing the progress during the 'fetch' stage of the room key import process.
|
||||
*
|
||||
* @see {@link ImportRoomKeyProgressData}.
|
||||
*/
|
||||
export type ImportRoomKeyFetchProgress = {
|
||||
/**
|
||||
* The current stage of the import process.
|
||||
*/
|
||||
stage: ImportRoomKeyStage.Fetch;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type representing the progress during the 'load_keys' stage of the room key import process.
|
||||
*
|
||||
* @see {@link ImportRoomKeyProgressData}.
|
||||
*/
|
||||
export type ImportRoomKeyLoadProgress = {
|
||||
/**
|
||||
* The current stage of the import process.
|
||||
*/
|
||||
stage: ImportRoomKeyStage.LoadKeys;
|
||||
|
||||
/**
|
||||
* The number of successfully loaded room keys so far.
|
||||
*/
|
||||
successes: number;
|
||||
|
||||
/**
|
||||
* The number of room keys that failed to load so far.
|
||||
*/
|
||||
failures: number;
|
||||
|
||||
/**
|
||||
* The total number of room keys being loaded.
|
||||
*/
|
||||
total: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Room key import progress report.
|
||||
* Used when calling {@link CryptoApi#importRoomKeys},
|
||||
* {@link CryptoApi#importRoomKeysAsJson} or {@link CryptoApi#restoreKeyBackup} as the parameter of
|
||||
* the progressCallback. Used to display feedback.
|
||||
*/
|
||||
export interface ImportRoomKeyProgressData {
|
||||
stage: string; // TODO: Enum
|
||||
successes?: number;
|
||||
failures?: number;
|
||||
total?: number;
|
||||
}
|
||||
export type ImportRoomKeyProgressData = ImportRoomKeyFetchProgress | ImportRoomKeyLoadProgress;
|
||||
|
||||
/**
|
||||
* Options object for {@link CryptoApi#importRoomKeys} and
|
||||
|
||||
+2
-8
@@ -25,7 +25,8 @@ export interface MapperOpts {
|
||||
preventReEmit?: boolean;
|
||||
// decrypt event proactively
|
||||
decrypt?: boolean;
|
||||
// the event is a to_device event
|
||||
|
||||
/** @deprecated no longer used */
|
||||
toDevice?: boolean;
|
||||
}
|
||||
|
||||
@@ -34,10 +35,6 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
|
||||
const decrypt = options.decrypt !== false;
|
||||
|
||||
function mapper(plainOldJsObject: Partial<IEvent>): MatrixEvent {
|
||||
if (options.toDevice) {
|
||||
delete plainOldJsObject.room_id;
|
||||
}
|
||||
|
||||
const room = client.getRoom(plainOldJsObject.room_id);
|
||||
|
||||
let event: MatrixEvent | undefined;
|
||||
@@ -74,9 +71,6 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
|
||||
event.setThread(thread);
|
||||
}
|
||||
|
||||
// TODO: once we get rid of the old libolm-backed crypto, we can restrict this to room events (rather than
|
||||
// to-device events), because the rust implementation decrypts to-device messages at a higher level.
|
||||
// Generally we probably want to use a different eventMapper implementation for to-device events because
|
||||
if (event.isEncrypted()) {
|
||||
if (!preventReEmit) {
|
||||
client.reEmitter.reEmit(event, [MatrixEventEvent.Decrypted]);
|
||||
|
||||
@@ -197,3 +197,18 @@ export class ConnectionError extends Error {
|
||||
return "ConnectionError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TokenRefreshError. This indicates that a request failed due to the token being expired,
|
||||
* and attempting to refresh said token also failed but in a way which was not indicative of token invalidation.
|
||||
* Assumed to be a temporary failure.
|
||||
*/
|
||||
export class TokenRefreshError extends Error {
|
||||
public constructor(cause?: Error) {
|
||||
super(cause?.message ?? "");
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return "TokenRefreshError";
|
||||
}
|
||||
}
|
||||
|
||||
+48
-17
@@ -18,10 +18,12 @@ limitations under the License.
|
||||
* This is an internal module. See {@link MatrixHttpApi} for the public class.
|
||||
*/
|
||||
|
||||
import { ErrorResponse as OidcAuthError } from "oidc-client-ts";
|
||||
|
||||
import { checkObjectHasKeys, encodeParams } from "../utils.ts";
|
||||
import { type TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
import { Method } from "./method.ts";
|
||||
import { ConnectionError, type MatrixError } from "./errors.ts";
|
||||
import { ConnectionError, MatrixError, TokenRefreshError } from "./errors.ts";
|
||||
import {
|
||||
HttpApiEvent,
|
||||
type HttpApiEventHandlerMap,
|
||||
@@ -31,6 +33,7 @@ import {
|
||||
} from "./interface.ts";
|
||||
import { anySignal, parseErrorResponse, timeoutSignal } from "./utils.ts";
|
||||
import { type QueryDict } from "../utils.ts";
|
||||
import { singleAsyncExecution } from "../utils/decorators.ts";
|
||||
|
||||
interface TypedResponse<T> extends Response {
|
||||
json(): Promise<T>;
|
||||
@@ -42,6 +45,12 @@ export type ResponseType<T, O extends IHttpOpts> = O extends undefined
|
||||
? T
|
||||
: TypedResponse<T>;
|
||||
|
||||
const enum TokenRefreshOutcome {
|
||||
Success = "success",
|
||||
Failure = "failure",
|
||||
Logout = "logout",
|
||||
}
|
||||
|
||||
export class FetchHttpApi<O extends IHttpOpts> {
|
||||
private abortController = new AbortController();
|
||||
|
||||
@@ -106,6 +115,12 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
return this.requestOtherUrl(method, fullUri, body, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise used to block authenticated requests during a token refresh to avoid repeated expected errors.
|
||||
* @private
|
||||
*/
|
||||
private tokenRefreshPromise?: Promise<unknown>;
|
||||
|
||||
/**
|
||||
* Perform an authorised request to the homeserver.
|
||||
* @param method - The HTTP method e.g. "GET".
|
||||
@@ -162,29 +177,41 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
}
|
||||
|
||||
try {
|
||||
// Await any ongoing token refresh
|
||||
await this.tokenRefreshPromise;
|
||||
const response = await this.request<T>(method, path, queryParams, body, opts);
|
||||
return response;
|
||||
} catch (error) {
|
||||
const err = error as MatrixError;
|
||||
if (!(error instanceof MatrixError)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (err.errcode === "M_UNKNOWN_TOKEN" && !opts.doNotAttemptTokenRefresh) {
|
||||
const shouldRetry = await this.tryRefreshToken();
|
||||
// if we got a new token retry the request
|
||||
if (shouldRetry) {
|
||||
if (error.errcode === "M_UNKNOWN_TOKEN" && !opts.doNotAttemptTokenRefresh) {
|
||||
const tokenRefreshPromise = this.tryRefreshToken();
|
||||
this.tokenRefreshPromise = Promise.allSettled([tokenRefreshPromise]);
|
||||
const outcome = await tokenRefreshPromise;
|
||||
|
||||
if (outcome === TokenRefreshOutcome.Success) {
|
||||
// if we got a new token retry the request
|
||||
return this.authedRequest(method, path, queryParams, body, {
|
||||
...paramOpts,
|
||||
doNotAttemptTokenRefresh: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
// otherwise continue with error handling
|
||||
if (err.errcode == "M_UNKNOWN_TOKEN" && !opts?.inhibitLogoutEmit) {
|
||||
this.eventEmitter.emit(HttpApiEvent.SessionLoggedOut, err);
|
||||
} else if (err.errcode == "M_CONSENT_NOT_GIVEN") {
|
||||
this.eventEmitter.emit(HttpApiEvent.NoConsent, err.message, err.data.consent_uri);
|
||||
if (outcome === TokenRefreshOutcome.Failure) {
|
||||
throw new TokenRefreshError(error);
|
||||
}
|
||||
// Fall through to SessionLoggedOut handler below
|
||||
}
|
||||
|
||||
throw err;
|
||||
// otherwise continue with error handling
|
||||
if (error.errcode == "M_UNKNOWN_TOKEN" && !opts?.inhibitLogoutEmit) {
|
||||
this.eventEmitter.emit(HttpApiEvent.SessionLoggedOut, error);
|
||||
} else if (error.errcode == "M_CONSENT_NOT_GIVEN") {
|
||||
this.eventEmitter.emit(HttpApiEvent.NoConsent, error.message, error.data.consent_uri);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,9 +220,10 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
* On success, sets new access and refresh tokens in opts.
|
||||
* @returns Promise that resolves to a boolean - true when token was refreshed successfully
|
||||
*/
|
||||
private async tryRefreshToken(): Promise<boolean> {
|
||||
@singleAsyncExecution
|
||||
private async tryRefreshToken(): Promise<TokenRefreshOutcome> {
|
||||
if (!this.opts.refreshToken || !this.opts.tokenRefreshFunction) {
|
||||
return false;
|
||||
return TokenRefreshOutcome.Logout;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -203,10 +231,13 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
this.opts.accessToken = accessToken;
|
||||
this.opts.refreshToken = refreshToken;
|
||||
// successfully got new tokens
|
||||
return true;
|
||||
return TokenRefreshOutcome.Success;
|
||||
} catch (error) {
|
||||
this.opts.logger?.warn("Failed to refresh token", error);
|
||||
return false;
|
||||
if (error instanceof OidcAuthError || error instanceof MatrixError) {
|
||||
return TokenRefreshOutcome.Logout;
|
||||
}
|
||||
return TokenRefreshOutcome.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export type Statistics = {
|
||||
export interface IEncryptionManager {
|
||||
join(joinConfig: EncryptionConfig | undefined): void;
|
||||
leave(): void;
|
||||
onMembershipsUpdate(oldMemberships: CallMembership[]): Promise<void>;
|
||||
onMembershipsUpdate(oldMemberships: CallMembership[]): void;
|
||||
/**
|
||||
* Process `m.call.encryption_keys` events to track the encryption keys for call participants.
|
||||
* This should be called each time the relevant event is received from a room timeline.
|
||||
@@ -150,7 +150,7 @@ export class EncryptionManager implements IEncryptionManager {
|
||||
private isMyMembership = (m: CallMembership): boolean =>
|
||||
m.sender === this.client.getUserId() && m.deviceId === this.client.getDeviceId();
|
||||
|
||||
public async onMembershipsUpdate(oldMemberships: CallMembership[]): Promise<void> {
|
||||
public onMembershipsUpdate(oldMemberships: CallMembership[]): void {
|
||||
if (this.manageMediaKeys && this.joined) {
|
||||
const oldMembershipIds = new Set(
|
||||
oldMemberships.filter((m) => !this.isMyMembership(m)).map(getParticipantIdFromMembership),
|
||||
@@ -231,14 +231,14 @@ export class EncryptionManager implements IEncryptionManager {
|
||||
logger.info("Last encryption key event sent too recently: postponing");
|
||||
if (this.keysEventUpdateTimeout === undefined) {
|
||||
this.keysEventUpdateTimeout = setTimeout(
|
||||
this.sendEncryptionKeysEvent,
|
||||
() => void this.sendEncryptionKeysEvent(),
|
||||
this.updateEncryptionKeyThrottle,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendEncryptionKeysEvent();
|
||||
void this.sendEncryptionKeysEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,7 +318,7 @@ export class EncryptionManager implements IEncryptionManager {
|
||||
if (this.keysEventUpdateTimeout === undefined) {
|
||||
const resendDelay = safeGetRetryAfterMs(matrixError, 5000);
|
||||
logger.warn(`Failed to send m.call.encryption_key, retrying in ${resendDelay}`, error);
|
||||
this.keysEventUpdateTimeout = setTimeout(this.sendEncryptionKeysEvent, resendDelay);
|
||||
this.keysEventUpdateTimeout = setTimeout(() => void this.sendEncryptionKeysEvent(), resendDelay);
|
||||
} else {
|
||||
logger.info("Not scheduling key resend as another re-send is already pending");
|
||||
}
|
||||
@@ -488,7 +488,7 @@ export class EncryptionManager implements IEncryptionManager {
|
||||
const newKeyIndex = this.makeNewSenderKey(true);
|
||||
// send immediately: if we're about to start sending with a new key, it's
|
||||
// important we get it out to others as soon as we can.
|
||||
this.sendEncryptionKeysEvent(newKeyIndex);
|
||||
void this.sendEncryptionKeysEvent(newKeyIndex);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ export interface MembershipConfig {
|
||||
/**
|
||||
* The timeout (in milliseconds) after we joined the call, that our membership should expire
|
||||
* unless we have explicitly updated it.
|
||||
*
|
||||
* This is what goes into the m.rtc.member event expiry field and is typically set to a number of hours.
|
||||
*/
|
||||
membershipExpiryTimeout?: number;
|
||||
|
||||
@@ -89,6 +91,7 @@ export interface MembershipConfig {
|
||||
*/
|
||||
callMemberEventRetryJitter?: number;
|
||||
}
|
||||
|
||||
export interface EncryptionConfig {
|
||||
/**
|
||||
* If true, generate and share a media key for this participant,
|
||||
@@ -153,7 +156,9 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
/**
|
||||
* Returns all the call memberships for a room, oldest first
|
||||
*/
|
||||
public static callMembershipsForRoom(room: Room): CallMembership[] {
|
||||
public static callMembershipsForRoom(
|
||||
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState">,
|
||||
): CallMembership[] {
|
||||
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
if (!roomState) {
|
||||
logger.warn("Couldn't get state for room " + room.roomId);
|
||||
@@ -225,20 +230,51 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
return new MatrixRTCSession(client, room, callMemberships);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly client: MatrixClient,
|
||||
public readonly room: Room,
|
||||
/**
|
||||
* WARN: this can in theory only be a subset of the room with the properties required by
|
||||
* this class.
|
||||
* Outside of tests this most likely will be a full room, however.
|
||||
* @deprecated Relying on a full Room object being available here is an anti-pattern. You should be tracking
|
||||
* the room object in your own code and passing it in when needed.
|
||||
*/
|
||||
public get room(): Room {
|
||||
return this.roomSubset as Room;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructs a room session. When using MatrixRTC inside the js-sdk this is expected
|
||||
* to be used with the MatrixRTCSessionManager exclusively.
|
||||
*
|
||||
* In cases where you don't use the js-sdk but build on top of another Matrix stack this class can be used standalone
|
||||
* to manage a joined MatrixRTC session.
|
||||
*
|
||||
* @param client A subset of the {@link MatrixClient} that lets the session interact with the Matrix room.
|
||||
* @param roomSubset The room this session is attached to. A subset of a js-sdk Room that the session needs.
|
||||
* @param memberships The list of memberships this session currently has.
|
||||
*/
|
||||
public constructor(
|
||||
private readonly client: Pick<
|
||||
MatrixClient,
|
||||
| "getUserId"
|
||||
| "getDeviceId"
|
||||
| "sendStateEvent"
|
||||
| "_unstable_sendDelayedStateEvent"
|
||||
| "_unstable_updateDelayedEvent"
|
||||
| "sendEvent"
|
||||
| "cancelPendingEvent"
|
||||
>,
|
||||
private roomSubset: Pick<Room, "getLiveTimeline" | "roomId" | "getVersion" | "hasMembershipState">,
|
||||
public memberships: CallMembership[],
|
||||
) {
|
||||
super();
|
||||
this._callId = memberships[0]?.callId;
|
||||
const roomState = this.room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
const roomState = this.roomSubset.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
// TODO: double check if this is actually needed. Should be covered by refreshRoom in MatrixRTCSessionManager
|
||||
roomState?.on(RoomStateEvent.Members, this.onRoomMemberUpdate);
|
||||
this.setExpiryTimer();
|
||||
this.encryptionManager = new EncryptionManager(
|
||||
this.client,
|
||||
this.room,
|
||||
this.roomSubset,
|
||||
() => this.memberships,
|
||||
(keyBin: Uint8Array<ArrayBufferLike>, encryptionKeyIndex: number, participantId: string) => {
|
||||
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
||||
@@ -263,7 +299,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
clearTimeout(this.expiryTimeout);
|
||||
this.expiryTimeout = undefined;
|
||||
}
|
||||
const roomState = this.room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
const roomState = this.roomSubset.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
roomState?.off(RoomStateEvent.Members, this.onRoomMemberUpdate);
|
||||
}
|
||||
|
||||
@@ -284,10 +320,10 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
public joinRoomSession(fociPreferred: Focus[], fociActive?: Focus, joinConfig?: JoinSessionConfig): void {
|
||||
// create MembershipManager
|
||||
if (this.isJoined()) {
|
||||
logger.info(`Already joined to session in room ${this.room.roomId}: ignoring join call`);
|
||||
logger.info(`Already joined to session in room ${this.roomSubset.roomId}: ignoring join call`);
|
||||
return;
|
||||
} else {
|
||||
this.membershipManager = new LegacyMembershipManager(joinConfig, this.room, this.client, () =>
|
||||
this.membershipManager = new LegacyMembershipManager(joinConfig, this.roomSubset, this.client, () =>
|
||||
this.getOldestMembership(),
|
||||
);
|
||||
}
|
||||
@@ -311,11 +347,11 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
*/
|
||||
public async leaveRoomSession(timeout: number | undefined = undefined): Promise<boolean> {
|
||||
if (!this.isJoined()) {
|
||||
logger.info(`Not joined to session in room ${this.room.roomId}: ignoring leave call`);
|
||||
logger.info(`Not joined to session in room ${this.roomSubset.roomId}: ignoring leave call`);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info(`Leaving call session in room ${this.room.roomId}`);
|
||||
logger.info(`Leaving call session in room ${this.roomSubset.roomId}`);
|
||||
|
||||
this.encryptionManager.leave();
|
||||
|
||||
@@ -455,14 +491,14 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
oldMemberships.some((m, i) => !CallMembership.equal(m, this.memberships[i]));
|
||||
|
||||
if (changed) {
|
||||
logger.info(`Memberships for call in room ${this.room.roomId} have changed: emitting`);
|
||||
logger.info(`Memberships for call in room ${this.roomSubset.roomId} have changed: emitting`);
|
||||
this.emit(MatrixRTCSessionEvent.MembershipsChanged, oldMemberships, this.memberships);
|
||||
|
||||
this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
|
||||
void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
|
||||
}
|
||||
// This also needs to be done if `changed` = false
|
||||
// A member might have updated their fingerprint (created_ts)
|
||||
this.encryptionManager.onMembershipsUpdate(oldMemberships);
|
||||
void this.encryptionManager.onMembershipsUpdate(oldMemberships);
|
||||
|
||||
this.setExpiryTimer();
|
||||
};
|
||||
|
||||
@@ -71,7 +71,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
||||
|
||||
public stop(): void {
|
||||
for (const sess of this.roomSessions.values()) {
|
||||
sess.stop();
|
||||
void sess.stop();
|
||||
}
|
||||
this.roomSessions.clear();
|
||||
|
||||
@@ -108,7 +108,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
||||
`Decryption failed for event ${event.getId()}: ${event.decryptionFailureReason} will retry once only`,
|
||||
);
|
||||
// retry after 1 second. After this we give up.
|
||||
setTimeout(() => this.consumeCallEncryptionEvent(event, true), 1000);
|
||||
setTimeout(() => void this.consumeCallEncryptionEvent(event, true), 1000);
|
||||
} else {
|
||||
logger.warn(`Decryption failed for event ${event.getId()}: ${event.decryptionFailureReason}`);
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
||||
this.getRoomSession(room).onCallEncryption(event);
|
||||
}
|
||||
private onTimeline = (event: MatrixEvent): void => {
|
||||
this.consumeCallEncryptionEvent(event);
|
||||
void this.consumeCallEncryptionEvent(event);
|
||||
};
|
||||
|
||||
private onRoom = (room: Room): void => {
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface IMembershipManager {
|
||||
* @returns It resolves with true in case the leave was sent successfully.
|
||||
* It resolves with false in case we hit the timeout before sending successfully.
|
||||
*/
|
||||
leave(timeout: number | undefined): Promise<boolean>;
|
||||
leave(timeout?: number): Promise<boolean>;
|
||||
/**
|
||||
* Call this if the MatrixRTC session members have changed.
|
||||
*/
|
||||
@@ -117,7 +117,6 @@ export class LegacyMembershipManager implements IMembershipManager {
|
||||
| "getUserId"
|
||||
| "getDeviceId"
|
||||
| "sendStateEvent"
|
||||
| "_unstable_sendDelayedEvent"
|
||||
| "_unstable_sendDelayedStateEvent"
|
||||
| "_unstable_updateDelayedEvent"
|
||||
>,
|
||||
@@ -134,7 +133,7 @@ export class LegacyMembershipManager implements IMembershipManager {
|
||||
this.relativeExpiry = this.membershipExpiryTimeout;
|
||||
// We don't wait for this, mostly because it may fail and schedule a retry, so this
|
||||
// function returning doesn't really mean anything at all.
|
||||
this.triggerCallMembershipEventUpdate();
|
||||
void this.triggerCallMembershipEventUpdate();
|
||||
}
|
||||
|
||||
public async leave(timeout: number | undefined = undefined): Promise<boolean> {
|
||||
@@ -312,6 +311,7 @@ export class LegacyMembershipManager implements IMembershipManager {
|
||||
if (this.disconnectDelayId !== undefined) {
|
||||
this.scheduleDelayDisconnection();
|
||||
}
|
||||
// TODO throw or log an error if this.disconnectDelayId === undefined
|
||||
} else {
|
||||
// Not joined
|
||||
let sentDelayedDisconnect = false;
|
||||
@@ -351,7 +351,7 @@ export class LegacyMembershipManager implements IMembershipManager {
|
||||
}
|
||||
|
||||
private scheduleDelayDisconnection(): void {
|
||||
this.memberEventTimeout = setTimeout(this.delayDisconnection, this.membershipKeepAlivePeriod);
|
||||
this.memberEventTimeout = setTimeout(() => void this.delayDisconnection(), this.membershipKeepAlivePeriod);
|
||||
}
|
||||
|
||||
private readonly delayDisconnection = async (): Promise<void> => {
|
||||
|
||||
@@ -340,6 +340,35 @@ export class DehydratedDeviceManager extends TypedEventEmitter<DehydratedDevices
|
||||
this.intervalId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current dehydrated device and stop the dehydrated device manager.
|
||||
*/
|
||||
public async delete(): Promise<void> {
|
||||
this.stop();
|
||||
try {
|
||||
await this.http.authedRequest(
|
||||
Method.Delete,
|
||||
"/dehydrated_device",
|
||||
undefined,
|
||||
{},
|
||||
{
|
||||
prefix: UnstablePrefix,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
const err = error as MatrixError;
|
||||
// If dehydrated devices aren't supported, or no dehydrated device
|
||||
// is found, we don't consider it an error, because we we'll end up
|
||||
// with no dehydrated device.
|
||||
if (err.errcode === "M_UNRECOGNIZED") {
|
||||
return;
|
||||
} else if (err.errcode === "M_NOT_FOUND") {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
KeysQueryRequest,
|
||||
KeysUploadRequest,
|
||||
type OlmMachine,
|
||||
type OutgoingRequest,
|
||||
PutDehydratedDeviceRequest,
|
||||
RoomMessageRequest,
|
||||
SignatureUploadRequest,
|
||||
@@ -34,16 +35,6 @@ import { type AuthDict, type UIAuthCallback } from "../interactive-auth.ts";
|
||||
import { ToDeviceMessageId } from "../@types/event.ts";
|
||||
import { UnstablePrefix as DehydrationUnstablePrefix } from "./DehydratedDeviceManager.ts";
|
||||
|
||||
/**
|
||||
* Common interface for all the request types returned by `OlmMachine.outgoingRequests`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface OutgoingRequest {
|
||||
readonly id: string | undefined;
|
||||
readonly type: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* OutgoingRequestManager: turns `OutgoingRequest`s from the rust sdk into HTTP requests
|
||||
*
|
||||
|
||||
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type OlmMachine } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import { type OlmMachine, type OutgoingRequest } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
|
||||
import { type OutgoingRequest, type OutgoingRequestProcessor } from "./OutgoingRequestProcessor.ts";
|
||||
import { type OutgoingRequestProcessor } from "./OutgoingRequestProcessor.ts";
|
||||
import { type Logger } from "../logger.ts";
|
||||
import { defer, type IDeferred, logDuration } from "../utils.ts";
|
||||
|
||||
|
||||
@@ -35,7 +35,12 @@ import { encodeUri, logDuration } from "../utils.ts";
|
||||
import { type OutgoingRequestProcessor } from "./OutgoingRequestProcessor.ts";
|
||||
import { sleep } from "../utils.ts";
|
||||
import { type BackupDecryptor } from "../common-crypto/CryptoBackend.ts";
|
||||
import { type ImportRoomKeyProgressData, type ImportRoomKeysOpts, CryptoEvent } from "../crypto-api/index.ts";
|
||||
import {
|
||||
type ImportRoomKeyProgressData,
|
||||
type ImportRoomKeysOpts,
|
||||
CryptoEvent,
|
||||
ImportRoomKeyStage,
|
||||
} from "../crypto-api/index.ts";
|
||||
import { type AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
import { type IMegolmSessionData } from "../@types/crypto.ts";
|
||||
|
||||
@@ -235,7 +240,7 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
|
||||
const importOpt: ImportRoomKeyProgressData = {
|
||||
total: Number(total),
|
||||
successes: Number(progress),
|
||||
stage: "load_keys",
|
||||
stage: ImportRoomKeyStage.LoadKeys,
|
||||
failures: 0,
|
||||
};
|
||||
opts?.progressCallback?.(importOpt);
|
||||
@@ -264,7 +269,7 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
|
||||
const importOpt: ImportRoomKeyProgressData = {
|
||||
total: Number(total),
|
||||
successes: Number(progress),
|
||||
stage: "load_keys",
|
||||
stage: ImportRoomKeyStage.LoadKeys,
|
||||
failures: Number(failures),
|
||||
};
|
||||
opts?.progressCallback?.(importOpt);
|
||||
@@ -619,9 +624,6 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
|
||||
opts?: KeyBackupRestoreOpts,
|
||||
): Promise<KeyBackupRestoreResult> {
|
||||
const keyBackup = await this.downloadKeyBackup(backupVersion);
|
||||
opts?.progressCallback?.({
|
||||
stage: "load_keys",
|
||||
});
|
||||
|
||||
return this.importKeyBackup(keyBackup, backupVersion, backupDecryptor, opts);
|
||||
}
|
||||
@@ -672,6 +674,13 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
|
||||
let totalImported = 0;
|
||||
let totalFailures = 0;
|
||||
|
||||
opts?.progressCallback?.({
|
||||
total: totalKeyCount,
|
||||
successes: totalImported,
|
||||
stage: ImportRoomKeyStage.LoadKeys,
|
||||
failures: totalFailures,
|
||||
});
|
||||
|
||||
/**
|
||||
* This method is called when we have enough chunks to decrypt.
|
||||
* It will decrypt the chunks and try to import the room keys.
|
||||
@@ -704,7 +713,7 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
|
||||
opts?.progressCallback?.({
|
||||
total: totalKeyCount,
|
||||
successes: totalImported,
|
||||
stage: "load_keys",
|
||||
stage: ImportRoomKeyStage.LoadKeys,
|
||||
failures: totalFailures,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
type KeyBackupRestoreOpts,
|
||||
type KeyBackupRestoreResult,
|
||||
type StartDehydrationOpts,
|
||||
ImportRoomKeyStage,
|
||||
} from "../crypto-api/index.ts";
|
||||
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts";
|
||||
import { type IDownloadKeyResult, type IQueryKeysRequest } from "../client.ts";
|
||||
@@ -1340,7 +1341,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
||||
|
||||
try {
|
||||
opts?.progressCallback?.({
|
||||
stage: "fetch",
|
||||
stage: ImportRoomKeyStage.Fetch,
|
||||
});
|
||||
|
||||
return await this.backupManager.restoreKeyBackup(backupVersion, backupDecryptor, opts);
|
||||
@@ -1439,6 +1440,10 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
||||
public async resetEncryption(authUploadDeviceSigningKeys: UIAuthCallback<void>): Promise<void> {
|
||||
this.logger.debug("resetEncryption: resetting encryption");
|
||||
|
||||
// Delete the dehydrated device, since any existing one will be signed
|
||||
// by the wrong cross-signing key
|
||||
this.dehydratedDeviceManager.delete();
|
||||
|
||||
// Disable backup, and delete all the backups from the server
|
||||
await this.backupManager.deleteAllKeyBackupVersions();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import { QrState } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import { type OutgoingRequest, QrState } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
|
||||
import {
|
||||
type GeneratedSas,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
type VerifierEventHandlerMap,
|
||||
} from "../crypto-api/verification.ts";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
import { type OutgoingRequest, type OutgoingRequestProcessor } from "./OutgoingRequestProcessor.ts";
|
||||
import { type OutgoingRequestProcessor } from "./OutgoingRequestProcessor.ts";
|
||||
import { TypedReEmitter } from "../ReEmitter.ts";
|
||||
import { type MatrixEvent } from "../models/event.ts";
|
||||
import { EventType, MsgType } from "../@types/event.ts";
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
defaultClientOpts,
|
||||
defaultSyncApiOpts,
|
||||
type SetPresence,
|
||||
mapToDeviceEvent,
|
||||
} from "./sync.ts";
|
||||
import { type MatrixEvent } from "./models/event.ts";
|
||||
import {
|
||||
@@ -151,7 +152,7 @@ class ExtensionToDevice implements Extension<ExtensionToDeviceRequest, Extension
|
||||
events = await this.cryptoCallbacks.preprocessToDeviceMessages(events);
|
||||
}
|
||||
events
|
||||
.map(this.client.getEventMapper())
|
||||
.map(mapToDeviceEvent)
|
||||
.map((toDeviceEvent) => {
|
||||
// map is a cheap inline forEach
|
||||
// We want to flag m.key.verification.start events as cancelled
|
||||
|
||||
+8
-2
@@ -54,7 +54,7 @@ import {
|
||||
type ITimeline,
|
||||
type IToDeviceEvent,
|
||||
} from "./sync-accumulator.ts";
|
||||
import { type MatrixEvent } from "./models/event.ts";
|
||||
import { MatrixEvent, type IEvent } from "./models/event.ts";
|
||||
import { type MatrixError, Method } from "./http-api/index.ts";
|
||||
import { type ISavedSync } from "./store/index.ts";
|
||||
import { EventType } from "./@types/event.ts";
|
||||
@@ -1152,7 +1152,7 @@ export class SyncApi {
|
||||
|
||||
const cancelledKeyVerificationTxns: string[] = [];
|
||||
toDeviceMessages
|
||||
.map(client.getEventMapper({ toDevice: true }))
|
||||
.map(mapToDeviceEvent)
|
||||
.map((toDeviceEvent) => {
|
||||
// map is a cheap inline forEach
|
||||
// We want to flag m.key.verification.start events as cancelled
|
||||
@@ -1945,3 +1945,9 @@ export function _createAndReEmitRoom(client: MatrixClient, roomId: string, opts:
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
export function mapToDeviceEvent(plainOldJsObject: Partial<IEvent>): MatrixEvent {
|
||||
// to-device events should not have a `room_id` property, but let's be sure
|
||||
delete plainOldJsObject.room_id;
|
||||
return new MatrixEvent(plainOldJsObject);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Method decorator to ensure that only one instance of the method is running at a time,
|
||||
* and any concurrent calls will return the same promise as the original call.
|
||||
* After execution is complete a new call will be able to run the method again.
|
||||
*/
|
||||
export function singleAsyncExecution<This, Args extends unknown[], Return>(
|
||||
target: (this: This, ...args: Args) => Promise<Return>,
|
||||
): (this: This, ...args: Args) => Promise<Return> {
|
||||
let promise: Promise<Return> | undefined;
|
||||
|
||||
async function replacementMethod(this: This, ...args: Args): Promise<Return> {
|
||||
if (promise) return promise;
|
||||
try {
|
||||
promise = target.call(this, ...args);
|
||||
await promise;
|
||||
return promise;
|
||||
} finally {
|
||||
promise = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return replacementMethod;
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"noEmit": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "./lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"experimentalDecorators": true,
|
||||
"experimentalDecorators": false,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3"
|
||||
chokidar "^3.6.0"
|
||||
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2":
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2":
|
||||
version "7.26.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
|
||||
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
|
||||
@@ -55,10 +55,10 @@
|
||||
"@babel/highlight" "^7.24.7"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5":
|
||||
version "7.26.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7"
|
||||
integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==
|
||||
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5", "@babel/compat-data@^7.26.8":
|
||||
version "7.26.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367"
|
||||
integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9":
|
||||
version "7.26.0"
|
||||
@@ -82,20 +82,20 @@
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/core@^7.12.10":
|
||||
version "7.26.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.7.tgz#0439347a183b97534d52811144d763a17f9d2b24"
|
||||
integrity sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.9.tgz#71838542a4b1e49dfed353d7acbc6eb89f4a76f2"
|
||||
integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==
|
||||
dependencies:
|
||||
"@ampproject/remapping" "^2.2.0"
|
||||
"@babel/code-frame" "^7.26.2"
|
||||
"@babel/generator" "^7.26.5"
|
||||
"@babel/generator" "^7.26.9"
|
||||
"@babel/helper-compilation-targets" "^7.26.5"
|
||||
"@babel/helper-module-transforms" "^7.26.0"
|
||||
"@babel/helpers" "^7.26.7"
|
||||
"@babel/parser" "^7.26.7"
|
||||
"@babel/template" "^7.25.9"
|
||||
"@babel/traverse" "^7.26.7"
|
||||
"@babel/types" "^7.26.7"
|
||||
"@babel/helpers" "^7.26.9"
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/traverse" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
convert-source-map "^2.0.0"
|
||||
debug "^4.1.0"
|
||||
gensync "^1.0.0-beta.2"
|
||||
@@ -103,9 +103,9 @@
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/eslint-parser@^7.12.10":
|
||||
version "7.26.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz#aa669f4d873f9cd617050cf3c40c19cd96307efb"
|
||||
integrity sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==
|
||||
version "7.26.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.8.tgz#55c4f4aae4970ae127f7a12369182ed6250e6f09"
|
||||
integrity sha512-3tBctaHRW6xSub26z7n8uyOTwwUsCdvIug/oxBH9n6yCO5hMj2vwDJAo7RbBMKrM7P+W2j61zLKviJQFGOYKMg==
|
||||
dependencies:
|
||||
"@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
|
||||
eslint-visitor-keys "^2.1.0"
|
||||
@@ -118,7 +118,7 @@
|
||||
dependencies:
|
||||
eslint-rule-composer "^0.3.0"
|
||||
|
||||
"@babel/generator@^7.26.0", "@babel/generator@^7.26.5":
|
||||
"@babel/generator@^7.26.0":
|
||||
version "7.26.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458"
|
||||
integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==
|
||||
@@ -129,6 +129,17 @@
|
||||
"@jridgewell/trace-mapping" "^0.3.25"
|
||||
jsesc "^3.0.2"
|
||||
|
||||
"@babel/generator@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.9.tgz#75a9482ad3d0cc7188a537aa4910bc59db67cbca"
|
||||
integrity sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
"@jridgewell/gen-mapping" "^0.3.5"
|
||||
"@jridgewell/trace-mapping" "^0.3.25"
|
||||
jsesc "^3.0.2"
|
||||
|
||||
"@babel/generator@^7.7.2":
|
||||
version "7.24.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d"
|
||||
@@ -158,16 +169,16 @@
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-create-class-features-plugin@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83"
|
||||
integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
|
||||
integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==
|
||||
dependencies:
|
||||
"@babel/helper-annotate-as-pure" "^7.25.9"
|
||||
"@babel/helper-member-expression-to-functions" "^7.25.9"
|
||||
"@babel/helper-optimise-call-expression" "^7.25.9"
|
||||
"@babel/helper-replace-supers" "^7.25.9"
|
||||
"@babel/helper-replace-supers" "^7.26.5"
|
||||
"@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
|
||||
"@babel/traverse" "^7.25.9"
|
||||
"@babel/traverse" "^7.26.9"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9":
|
||||
@@ -241,7 +252,7 @@
|
||||
"@babel/helper-wrap-function" "^7.25.9"
|
||||
"@babel/traverse" "^7.25.9"
|
||||
|
||||
"@babel/helper-replace-supers@^7.25.9":
|
||||
"@babel/helper-replace-supers@^7.25.9", "@babel/helper-replace-supers@^7.26.5":
|
||||
version "7.26.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d"
|
||||
integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
|
||||
@@ -292,7 +303,7 @@
|
||||
"@babel/traverse" "^7.25.9"
|
||||
"@babel/types" "^7.25.9"
|
||||
|
||||
"@babel/helpers@^7.26.0", "@babel/helpers@^7.26.7":
|
||||
"@babel/helpers@^7.26.0":
|
||||
version "7.26.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4"
|
||||
integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==
|
||||
@@ -300,6 +311,14 @@
|
||||
"@babel/template" "^7.25.9"
|
||||
"@babel/types" "^7.26.7"
|
||||
|
||||
"@babel/helpers@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.9.tgz#28f3fb45252fc88ef2dc547c8a911c255fc9fef6"
|
||||
integrity sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==
|
||||
dependencies:
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
|
||||
"@babel/highlight@^7.24.7":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.9.tgz#8141ce68fc73757946f983b343f1231f4691acc6"
|
||||
@@ -322,13 +341,20 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.25.2"
|
||||
|
||||
"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.5", "@babel/parser@^7.26.7":
|
||||
"@babel/parser@^7.26.0":
|
||||
version "7.26.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.7.tgz#e114cd099e5f7d17b05368678da0fb9f69b3385c"
|
||||
integrity sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==
|
||||
dependencies:
|
||||
"@babel/types" "^7.26.7"
|
||||
|
||||
"@babel/parser@^7.26.5", "@babel/parser@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5"
|
||||
integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==
|
||||
dependencies:
|
||||
"@babel/types" "^7.26.9"
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe"
|
||||
@@ -368,6 +394,15 @@
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/traverse" "^7.25.9"
|
||||
|
||||
"@babel/plugin-proposal-decorators@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz#8680707f943d1a3da2cd66b948179920f097e254"
|
||||
integrity sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==
|
||||
dependencies:
|
||||
"@babel/helper-create-class-features-plugin" "^7.25.9"
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/plugin-syntax-decorators" "^7.25.9"
|
||||
|
||||
"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2":
|
||||
version "7.21.0-placeholder-for-preset-env.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703"
|
||||
@@ -394,6 +429,13 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.12.13"
|
||||
|
||||
"@babel/plugin-syntax-decorators@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz#986b4ca8b7b5df3f67cee889cedeffc2e2bf14b3"
|
||||
integrity sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/plugin-syntax-dynamic-import@^7.8.3":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
|
||||
@@ -521,14 +563,14 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/plugin-transform-async-generator-functions@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz#1b18530b077d18a407c494eb3d1d72da505283a2"
|
||||
integrity sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==
|
||||
"@babel/plugin-transform-async-generator-functions@^7.26.8":
|
||||
version "7.26.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz#5e3991135e3b9c6eaaf5eff56d1ae5a11df45ff8"
|
||||
integrity sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/helper-plugin-utils" "^7.26.5"
|
||||
"@babel/helper-remap-async-to-generator" "^7.25.9"
|
||||
"@babel/traverse" "^7.25.9"
|
||||
"@babel/traverse" "^7.26.8"
|
||||
|
||||
"@babel/plugin-transform-async-to-generator@^7.25.9":
|
||||
version "7.25.9"
|
||||
@@ -640,12 +682,12 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/plugin-transform-for-of@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz#4bdc7d42a213397905d89f02350c5267866d5755"
|
||||
integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==
|
||||
"@babel/plugin-transform-for-of@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz#27231f79d5170ef33b5111f07fe5cafeb2c96a56"
|
||||
integrity sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/helper-plugin-utils" "^7.26.5"
|
||||
"@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
|
||||
|
||||
"@babel/plugin-transform-function-name@^7.25.9":
|
||||
@@ -835,12 +877,12 @@
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/plugin-transform-runtime@^7.12.10":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz#62723ea3f5b31ffbe676da9d6dae17138ae580ea"
|
||||
integrity sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.9.tgz#ea8be19ef134668e98f7b54daf7c4f853859dc44"
|
||||
integrity sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.25.9"
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/helper-plugin-utils" "^7.26.5"
|
||||
babel-plugin-polyfill-corejs2 "^0.4.10"
|
||||
babel-plugin-polyfill-corejs3 "^0.10.6"
|
||||
babel-plugin-polyfill-regenerator "^0.6.1"
|
||||
@@ -868,12 +910,12 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/plugin-transform-template-literals@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz#6dbd4a24e8fad024df76d1fac6a03cf413f60fe1"
|
||||
integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==
|
||||
"@babel/plugin-transform-template-literals@^7.26.8":
|
||||
version "7.26.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz#966b15d153a991172a540a69ad5e1845ced990b5"
|
||||
integrity sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/helper-plugin-utils" "^7.26.5"
|
||||
|
||||
"@babel/plugin-transform-typeof-symbol@^7.26.7":
|
||||
version "7.26.7"
|
||||
@@ -925,11 +967,11 @@
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/preset-env@^7.12.11":
|
||||
version "7.26.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.7.tgz#24d38e211f4570b8d806337035cc3ae798e0c36d"
|
||||
integrity sha512-Ycg2tnXwixaXOVb29rana8HNPgLVBof8qqtNQ9LE22IoyZboQbGSxI6ZySMdW3K5nAe6gu35IaJefUJflhUFTQ==
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.9.tgz#2ec64e903d0efe743699f77a10bdf7955c2123c3"
|
||||
integrity sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==
|
||||
dependencies:
|
||||
"@babel/compat-data" "^7.26.5"
|
||||
"@babel/compat-data" "^7.26.8"
|
||||
"@babel/helper-compilation-targets" "^7.26.5"
|
||||
"@babel/helper-plugin-utils" "^7.26.5"
|
||||
"@babel/helper-validator-option" "^7.25.9"
|
||||
@@ -943,7 +985,7 @@
|
||||
"@babel/plugin-syntax-import-attributes" "^7.26.0"
|
||||
"@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
|
||||
"@babel/plugin-transform-arrow-functions" "^7.25.9"
|
||||
"@babel/plugin-transform-async-generator-functions" "^7.25.9"
|
||||
"@babel/plugin-transform-async-generator-functions" "^7.26.8"
|
||||
"@babel/plugin-transform-async-to-generator" "^7.25.9"
|
||||
"@babel/plugin-transform-block-scoped-functions" "^7.26.5"
|
||||
"@babel/plugin-transform-block-scoping" "^7.25.9"
|
||||
@@ -958,7 +1000,7 @@
|
||||
"@babel/plugin-transform-dynamic-import" "^7.25.9"
|
||||
"@babel/plugin-transform-exponentiation-operator" "^7.26.3"
|
||||
"@babel/plugin-transform-export-namespace-from" "^7.25.9"
|
||||
"@babel/plugin-transform-for-of" "^7.25.9"
|
||||
"@babel/plugin-transform-for-of" "^7.26.9"
|
||||
"@babel/plugin-transform-function-name" "^7.25.9"
|
||||
"@babel/plugin-transform-json-strings" "^7.25.9"
|
||||
"@babel/plugin-transform-literals" "^7.25.9"
|
||||
@@ -986,7 +1028,7 @@
|
||||
"@babel/plugin-transform-shorthand-properties" "^7.25.9"
|
||||
"@babel/plugin-transform-spread" "^7.25.9"
|
||||
"@babel/plugin-transform-sticky-regex" "^7.25.9"
|
||||
"@babel/plugin-transform-template-literals" "^7.25.9"
|
||||
"@babel/plugin-transform-template-literals" "^7.26.8"
|
||||
"@babel/plugin-transform-typeof-symbol" "^7.26.7"
|
||||
"@babel/plugin-transform-unicode-escapes" "^7.25.9"
|
||||
"@babel/plugin-transform-unicode-property-regex" "^7.25.9"
|
||||
@@ -994,9 +1036,9 @@
|
||||
"@babel/plugin-transform-unicode-sets-regex" "^7.25.9"
|
||||
"@babel/preset-modules" "0.1.6-no-external-plugins"
|
||||
babel-plugin-polyfill-corejs2 "^0.4.10"
|
||||
babel-plugin-polyfill-corejs3 "^0.10.6"
|
||||
babel-plugin-polyfill-corejs3 "^0.11.0"
|
||||
babel-plugin-polyfill-regenerator "^0.6.1"
|
||||
core-js-compat "^3.38.1"
|
||||
core-js-compat "^3.40.0"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/preset-modules@0.1.6-no-external-plugins":
|
||||
@@ -1027,20 +1069,20 @@
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
|
||||
version "7.26.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.7.tgz#f4e7fe527cd710f8dc0618610b61b4b060c3c341"
|
||||
integrity sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433"
|
||||
integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
|
||||
integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==
|
||||
"@babel/template@^7.25.9", "@babel/template@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
|
||||
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.25.9"
|
||||
"@babel/parser" "^7.25.9"
|
||||
"@babel/types" "^7.25.9"
|
||||
"@babel/code-frame" "^7.26.2"
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
|
||||
"@babel/template@^7.3.3":
|
||||
version "7.24.7"
|
||||
@@ -1051,16 +1093,16 @@
|
||||
"@babel/parser" "^7.24.7"
|
||||
"@babel/types" "^7.24.7"
|
||||
|
||||
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.7":
|
||||
version "7.26.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb"
|
||||
integrity sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==
|
||||
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a"
|
||||
integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.26.2"
|
||||
"@babel/generator" "^7.26.5"
|
||||
"@babel/parser" "^7.26.7"
|
||||
"@babel/template" "^7.25.9"
|
||||
"@babel/types" "^7.26.7"
|
||||
"@babel/generator" "^7.26.9"
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
debug "^4.3.1"
|
||||
globals "^11.1.0"
|
||||
|
||||
@@ -1091,7 +1133,15 @@
|
||||
"@babel/helper-validator-identifier" "^7.24.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.5", "@babel/types@^7.26.7", "@babel/types@^7.4.4":
|
||||
"@babel/types@^7.25.9", "@babel/types@^7.26.5", "@babel/types@^7.26.7", "@babel/types@^7.26.9", "@babel/types@^7.4.4":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce"
|
||||
integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
|
||||
"@babel/types@^7.26.0":
|
||||
version "7.26.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.7.tgz#5e2b89c0768e874d4d061961f3a5a153d71dc17a"
|
||||
integrity sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==
|
||||
@@ -1174,12 +1224,12 @@
|
||||
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
|
||||
|
||||
"@gerrit0/mini-shiki@^1.24.0":
|
||||
version "1.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@gerrit0/mini-shiki/-/mini-shiki-1.26.1.tgz#b59884bd6013976ca66dec197492a1387fdbea52"
|
||||
integrity sha512-gHFUvv9f1fU2Piou/5Y7Sx5moYxcERbC7CXc6rkDLQTUBg5Dgg9L4u29/nHqfoQ3Y9R0h0BcOhd14uOEZIBP7Q==
|
||||
version "1.27.2"
|
||||
resolved "https://registry.yarnpkg.com/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz#cf2a9fcb08a6581c78fc94821f0c854ec4b9f899"
|
||||
integrity sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==
|
||||
dependencies:
|
||||
"@shikijs/engine-oniguruma" "^1.26.1"
|
||||
"@shikijs/types" "^1.26.1"
|
||||
"@shikijs/engine-oniguruma" "^1.27.2"
|
||||
"@shikijs/types" "^1.27.2"
|
||||
"@shikijs/vscode-textmate" "^10.0.1"
|
||||
|
||||
"@humanwhocodes/config-array@^0.13.0":
|
||||
@@ -1487,10 +1537,10 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^13.0.0":
|
||||
version "13.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-13.0.0.tgz#658bed951e4c8a06a6dd545575a79cf32022d4ba"
|
||||
integrity sha512-2gtpjnxL42sdJAgkwitpMMI4cw7Gcjf5sW0MXoe+OAlXPlxIzyM+06F5JJ8ENvBeHkuV2RqtFIRrh8i90HLsMw==
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^14.0.1":
|
||||
version "14.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-14.0.1.tgz#e258ef84bcc7889f0e7eb3a7dbecf0830a6dd606"
|
||||
integrity sha512-CgLpHs6nTw5pjSsMBi9xbQnBXf2l8YhImQP9cv8nbGSCYdYjFI0FilMXffzjWV5HThpNHri/3pF20ahZtuS3VA==
|
||||
|
||||
"@matrix-org/olm@3.2.15":
|
||||
version "3.2.15"
|
||||
@@ -1613,26 +1663,26 @@
|
||||
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
|
||||
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
|
||||
|
||||
"@shikijs/engine-oniguruma@^1.26.1":
|
||||
version "1.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.26.1.tgz#f9de733e2473e693b3d10bff32bb9761746c1d71"
|
||||
integrity sha512-F5XuxN1HljLuvfXv7d+mlTkV7XukC1cawdtOo+7pKgPD83CAB1Sf8uHqP3PK0u7njFH0ZhoXE1r+0JzEgAQ+kg==
|
||||
"@shikijs/engine-oniguruma@^1.27.2":
|
||||
version "1.29.2"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz#d879717ced61d44e78feab16f701f6edd75434f1"
|
||||
integrity sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==
|
||||
dependencies:
|
||||
"@shikijs/types" "1.26.1"
|
||||
"@shikijs/types" "1.29.2"
|
||||
"@shikijs/vscode-textmate" "^10.0.1"
|
||||
|
||||
"@shikijs/types@1.26.1", "@shikijs/types@^1.26.1":
|
||||
version "1.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.26.1.tgz#b5ece69e21000f53d65d15ddae33d9ad9c3763ad"
|
||||
integrity sha512-d4B00TKKAMaHuFYgRf3L0gwtvqpW4hVdVwKcZYbBfAAQXspgkbWqnFfuFl3MDH6gLbsubOcr+prcnsqah3ny7Q==
|
||||
"@shikijs/types@1.29.2", "@shikijs/types@^1.27.2":
|
||||
version "1.29.2"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.29.2.tgz#a93fdb410d1af8360c67bf5fc1d1a68d58e21c4f"
|
||||
integrity sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==
|
||||
dependencies:
|
||||
"@shikijs/vscode-textmate" "^10.0.1"
|
||||
"@types/hast" "^3.0.4"
|
||||
|
||||
"@shikijs/vscode-textmate@^10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz#d06d45b67ac5e9b0088e3f67ebd3f25c6c3d711a"
|
||||
integrity sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
|
||||
integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
|
||||
|
||||
"@sinclair/typebox@^0.24.1":
|
||||
version "0.24.51"
|
||||
@@ -1668,9 +1718,9 @@
|
||||
p-map "^4.0.0"
|
||||
|
||||
"@stylistic/eslint-plugin@^3.0.0":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-3.0.1.tgz#ec85816a3b4e75c34293160b2784e6a4bdce6664"
|
||||
integrity sha512-rQ3tcT5N2cynofJfbjUsnL4seoewTaOVBLyUEwtNldo7iNMPo3h/GUQk+Cl3iHEWwRxjq2wuH6q0FufQrbVL1A==
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-3.1.0.tgz#a9f655c518f76bfc5feb46b467d0f06e511b289d"
|
||||
integrity sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==
|
||||
dependencies:
|
||||
"@typescript-eslint/utils" "^8.13.0"
|
||||
eslint-visitor-keys "^4.2.0"
|
||||
@@ -1736,14 +1786,6 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.20.7"
|
||||
|
||||
"@types/bs58@^4.0.1":
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/bs58/-/bs58-4.0.4.tgz#49fbcb0c7db5f7cea26f0e0f61dc4a41a2445aab"
|
||||
integrity sha512-0IEpMFXXQi2zXaXl9GJ3sRwQo0uEkD+yFOv+FnAU5lkPtcu6h61xb7jc2CFPEZ5BUOaiP13ThuGc9HD4R8lR5g==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
base-x "^3.0.6"
|
||||
|
||||
"@types/content-type@^1.1.5":
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.8.tgz#319644d07ee6b4bfc734483008393b89b99f0219"
|
||||
@@ -1834,9 +1876,9 @@
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/node@18":
|
||||
version "18.19.75"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.75.tgz#be932799d1ab40779ffd16392a2b2300f81b565d"
|
||||
integrity sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==
|
||||
version "18.19.76"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.76.tgz#7991658e0ba41ad30cc8be01c9bbe580d58f2112"
|
||||
integrity sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
@@ -1888,29 +1930,29 @@
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^8.0.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz#7745f4e3e4a7ae5f6f73fefcd856fd6a074189b7"
|
||||
integrity sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz#d104c2a6212304c649105b18af2c110b4a1dd4ae"
|
||||
integrity sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.10.0"
|
||||
"@typescript-eslint/scope-manager" "8.23.0"
|
||||
"@typescript-eslint/type-utils" "8.23.0"
|
||||
"@typescript-eslint/utils" "8.23.0"
|
||||
"@typescript-eslint/visitor-keys" "8.23.0"
|
||||
"@typescript-eslint/scope-manager" "8.24.1"
|
||||
"@typescript-eslint/type-utils" "8.24.1"
|
||||
"@typescript-eslint/utils" "8.24.1"
|
||||
"@typescript-eslint/visitor-keys" "8.24.1"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.3.1"
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
"@typescript-eslint/parser@^8.0.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.23.0.tgz#57acb3b65fce48d12b70d119436e145842a30081"
|
||||
integrity sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.24.1.tgz#67965c2d2ddd7eadb2f094c395695db8334ef9a2"
|
||||
integrity sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.23.0"
|
||||
"@typescript-eslint/types" "8.23.0"
|
||||
"@typescript-eslint/typescript-estree" "8.23.0"
|
||||
"@typescript-eslint/visitor-keys" "8.23.0"
|
||||
"@typescript-eslint/scope-manager" "8.24.1"
|
||||
"@typescript-eslint/types" "8.24.1"
|
||||
"@typescript-eslint/typescript-estree" "8.24.1"
|
||||
"@typescript-eslint/visitor-keys" "8.24.1"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.21.0":
|
||||
@@ -1921,21 +1963,21 @@
|
||||
"@typescript-eslint/types" "8.21.0"
|
||||
"@typescript-eslint/visitor-keys" "8.21.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.23.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz#ee3bb7546421ca924b9b7a8b62a77d388193ddec"
|
||||
integrity sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==
|
||||
"@typescript-eslint/scope-manager@8.24.1":
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz#1e1e76ec4560aa85077ab36deb9b2bead4ae124e"
|
||||
integrity sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.23.0"
|
||||
"@typescript-eslint/visitor-keys" "8.23.0"
|
||||
"@typescript-eslint/types" "8.24.1"
|
||||
"@typescript-eslint/visitor-keys" "8.24.1"
|
||||
|
||||
"@typescript-eslint/type-utils@8.23.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz#271e1eecece072d92679dfda5ccfceac3faa9f76"
|
||||
integrity sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==
|
||||
"@typescript-eslint/type-utils@8.24.1":
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz#99113e1df63d1571309d87eef68967344c78dd65"
|
||||
integrity sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "8.23.0"
|
||||
"@typescript-eslint/utils" "8.23.0"
|
||||
"@typescript-eslint/typescript-estree" "8.24.1"
|
||||
"@typescript-eslint/utils" "8.24.1"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
@@ -1944,10 +1986,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.21.0.tgz#58f30aec8db8212fd886835dc5969cdf47cb29f5"
|
||||
integrity sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==
|
||||
|
||||
"@typescript-eslint/types@8.23.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.23.0.tgz#3355f6bcc5ebab77ef6dcbbd1113ec0a683a234a"
|
||||
integrity sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==
|
||||
"@typescript-eslint/types@8.24.1":
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.24.1.tgz#8777a024f3afc4ace5e48f9a804309c6dd38f95a"
|
||||
integrity sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.21.0":
|
||||
version "8.21.0"
|
||||
@@ -1963,13 +2005,13 @@
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.0.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.23.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz#f633ef08efa656e386bc44b045ffcf9537cc6924"
|
||||
integrity sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==
|
||||
"@typescript-eslint/typescript-estree@8.24.1":
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz#3bb479401f8bd471b3c6dd3db89e7256977c54db"
|
||||
integrity sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.23.0"
|
||||
"@typescript-eslint/visitor-keys" "8.23.0"
|
||||
"@typescript-eslint/types" "8.24.1"
|
||||
"@typescript-eslint/visitor-keys" "8.24.1"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
@@ -1977,17 +2019,17 @@
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
"@typescript-eslint/utils@8.23.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.23.0.tgz#b269cbdc77129fd6e0e600b168b5ef740a625554"
|
||||
integrity sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==
|
||||
"@typescript-eslint/utils@8.24.1", "@typescript-eslint/utils@^8.13.0":
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.24.1.tgz#08d14eac33cfb3456feeee5a275b8ad3349e52ed"
|
||||
integrity sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "8.23.0"
|
||||
"@typescript-eslint/types" "8.23.0"
|
||||
"@typescript-eslint/typescript-estree" "8.23.0"
|
||||
"@typescript-eslint/scope-manager" "8.24.1"
|
||||
"@typescript-eslint/types" "8.24.1"
|
||||
"@typescript-eslint/typescript-estree" "8.24.1"
|
||||
|
||||
"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/utils@^8.13.0":
|
||||
"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0":
|
||||
version "8.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.21.0.tgz#bc4874fbc30feb3298b926e3b03d94570b3999c5"
|
||||
integrity sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==
|
||||
@@ -2005,12 +2047,12 @@
|
||||
"@typescript-eslint/types" "8.21.0"
|
||||
eslint-visitor-keys "^4.2.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.23.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz#40405fd26a61d23f5f4c2ed0f016a47074781df8"
|
||||
integrity sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==
|
||||
"@typescript-eslint/visitor-keys@8.24.1":
|
||||
version "8.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz#8bdfe47a89195344b34eb21ef61251562148202b"
|
||||
integrity sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.23.0"
|
||||
"@typescript-eslint/types" "8.24.1"
|
||||
eslint-visitor-keys "^4.2.0"
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
@@ -2309,6 +2351,14 @@ babel-plugin-polyfill-corejs3@^0.10.6:
|
||||
"@babel/helper-define-polyfill-provider" "^0.6.2"
|
||||
core-js-compat "^3.38.0"
|
||||
|
||||
babel-plugin-polyfill-corejs3@^0.11.0:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz#4e4e182f1bb37c7ba62e2af81d8dd09df31344f6"
|
||||
integrity sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==
|
||||
dependencies:
|
||||
"@babel/helper-define-polyfill-provider" "^0.6.3"
|
||||
core-js-compat "^3.40.0"
|
||||
|
||||
babel-plugin-polyfill-regenerator@^0.6.1:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz#abeb1f3f1c762eace37587f42548b08b57789bc8"
|
||||
@@ -2352,13 +2402,6 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base-x@^3.0.6:
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.10.tgz#62de58653f8762b5d6f8d9fe30fa75f7b2585a75"
|
||||
integrity sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
base-x@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b"
|
||||
@@ -2452,9 +2495,9 @@ camelcase@^6.2.0:
|
||||
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
||||
|
||||
caniuse-lite@^1.0.30001688:
|
||||
version "1.0.30001697"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz#040bbbb54463c4b4b3377c716b34a322d16e6fc7"
|
||||
integrity sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==
|
||||
version "1.0.30001700"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz#26cd429cf09b4fd4e745daf4916039c794d720f6"
|
||||
integrity sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==
|
||||
|
||||
chalk@5.2.0:
|
||||
version "5.2.0"
|
||||
@@ -2640,7 +2683,7 @@ convert-source-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
core-js-compat@^3.38.0, core-js-compat@^3.38.1:
|
||||
core-js-compat@^3.38.0, core-js-compat@^3.38.1, core-js-compat@^3.40.0:
|
||||
version "3.40.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38"
|
||||
integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==
|
||||
@@ -2864,9 +2907,9 @@ easy-table@1.2.0:
|
||||
wcwidth "^1.0.1"
|
||||
|
||||
electron-to-chromium@^1.5.73:
|
||||
version "1.5.91"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.91.tgz#cf5567f6853062493242133aefd4dc8dc8440abd"
|
||||
integrity sha512-sNSHHyq048PFmZY4S90ax61q+gLCs0X0YmcOII9wG9S2XwbVr+h4VW2wWhnbp/Eys3cCwTxVF292W3qPaxIapQ==
|
||||
version "1.5.102"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz#81a452ace8e2c3fa7fba904ea4fed25052c53d3f"
|
||||
integrity sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==
|
||||
|
||||
emittery@^0.13.1:
|
||||
version "0.13.1"
|
||||
@@ -2888,18 +2931,10 @@ emoji-regex@^9.2.2:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
enhanced-resolve@^5.15.0:
|
||||
version "5.17.1"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
|
||||
integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.4"
|
||||
tapable "^2.2.0"
|
||||
|
||||
enhanced-resolve@^5.18.0:
|
||||
version "5.18.0"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz#91eb1db193896b9801251eeff1c6980278b1e404"
|
||||
integrity sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==
|
||||
enhanced-resolve@^5.15.0, enhanced-resolve@^5.18.0:
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf"
|
||||
integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.4"
|
||||
tapable "^2.2.0"
|
||||
@@ -3078,18 +3113,17 @@ eslint-import-resolver-node@^0.3.9:
|
||||
resolve "^1.22.4"
|
||||
|
||||
eslint-import-resolver-typescript@^3.5.1:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz#e69925936a771a9cb2de418ccebc4cdf6c0818aa"
|
||||
integrity sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.8.1.tgz#43aab1e51080eb157d4a35fb3db9d737ea79e2f6"
|
||||
integrity sha512-qw5TPA12HTmb9CkcuiNrFtwhM1ae2FWysLeRrTbQ+/JKS///gbL3fQ5LRhAZnzkcqkScOvkB5Y5o+xgyQz1VVg==
|
||||
dependencies:
|
||||
"@nolyfill/is-core-module" "1.0.39"
|
||||
debug "^4.3.7"
|
||||
enhanced-resolve "^5.15.0"
|
||||
fast-glob "^3.3.2"
|
||||
get-tsconfig "^4.7.5"
|
||||
get-tsconfig "^4.10.0"
|
||||
is-bun-module "^1.0.2"
|
||||
is-glob "^4.0.3"
|
||||
stable-hash "^0.0.4"
|
||||
tinyglobby "^0.2.10"
|
||||
|
||||
eslint-module-utils@^2.12.0:
|
||||
version "2.12.0"
|
||||
@@ -3466,6 +3500,11 @@ fb-watchman@^2.0.0:
|
||||
dependencies:
|
||||
bser "2.1.1"
|
||||
|
||||
fdir@^6.4.3:
|
||||
version "6.4.3"
|
||||
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72"
|
||||
integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==
|
||||
|
||||
fetch-mock-jest@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz#0e13df990d286d9239e284f12b279ed509bf53cd"
|
||||
@@ -3653,10 +3692,10 @@ get-symbol-description@^1.0.2:
|
||||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.4"
|
||||
|
||||
get-tsconfig@^4.7.5:
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471"
|
||||
integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==
|
||||
get-tsconfig@^4.10.0:
|
||||
version "4.10.0"
|
||||
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.0.tgz#403a682b373a823612475a4c2928c7326fc0f6bb"
|
||||
integrity sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==
|
||||
dependencies:
|
||||
resolve-pkg-maps "^1.0.0"
|
||||
|
||||
@@ -4744,9 +4783,9 @@ kleur@^3.0.3:
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
knip@^5.0.0:
|
||||
version "5.43.6"
|
||||
resolved "https://registry.yarnpkg.com/knip/-/knip-5.43.6.tgz#7faed2462ddc27e5b2f47a9224b7f51150b71f9b"
|
||||
integrity sha512-bUCFlg44imdV5vayYxu0pIAB373S8Ufjda0qaI9oRZDH6ltJFwUoAO2j7nafxDmo5G0ZeP4IiLAHqlc3wYIONQ==
|
||||
version "5.44.1"
|
||||
resolved "https://registry.yarnpkg.com/knip/-/knip-5.44.1.tgz#712cb670af20185a1133eb4d33302105e062836e"
|
||||
integrity sha512-YJrz7K6AA3SwXJ5LL9ZlzQX9wcihMNwKoGBAaY5kUn4rLNOZ5ehsjFV+xm8gQuIJdjsFIKcFsy0K3U3yM/mabw==
|
||||
dependencies:
|
||||
"@nodelib/fs.walk" "3.0.1"
|
||||
"@snyk/github-codeowners" "1.1.0"
|
||||
@@ -5376,10 +5415,10 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prettier@3.4.2:
|
||||
version "3.4.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f"
|
||||
integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==
|
||||
prettier@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.1.tgz#22fac9d0b18c0b92055ac8fb619ac1c7bef02fb7"
|
||||
integrity sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==
|
||||
|
||||
pretty-format@^28.1.3:
|
||||
version "28.1.3"
|
||||
@@ -5688,11 +5727,6 @@ safe-array-concat@^1.1.2:
|
||||
has-symbols "^1.0.3"
|
||||
isarray "^2.0.5"
|
||||
|
||||
safe-buffer@^5.0.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-regex-test@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
|
||||
@@ -6096,6 +6130,14 @@ text-table@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||
|
||||
tinyglobby@^0.2.10:
|
||||
version "0.2.11"
|
||||
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.11.tgz#9182cff655a0e272aad850d1a84c5e8e0f700426"
|
||||
integrity sha512-32TmKeeKUahv0Go8WmQgiEp9Y21NuxjwjqiRC1nrUB51YacfSwuB44xgXD+HdIppmMRgjQNPdrHyA6vIybYZ+g==
|
||||
dependencies:
|
||||
fdir "^6.4.3"
|
||||
picomatch "^4.0.2"
|
||||
|
||||
tmpl@1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
|
||||
@@ -6142,12 +6184,7 @@ tr46@~0.0.3:
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
ts-api-utils@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.0.tgz#b9d7d5f7ec9f736f4d0f09758b8607979044a900"
|
||||
integrity sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==
|
||||
|
||||
ts-api-utils@^2.0.1:
|
||||
ts-api-utils@^2.0.0, ts-api-utils@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd"
|
||||
integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==
|
||||
@@ -6273,9 +6310,9 @@ typedoc-plugin-coverage@^3.0.0:
|
||||
integrity sha512-V23DAwinAMpGMGcL1R1s8Snr26hrjfIdwGf+4jR/65ZdmeAN+yRX0onfx5JlembTQhR6AePQ/parfQNS0AZ64A==
|
||||
|
||||
typedoc-plugin-mdn-links@^4.0.0:
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-4.0.11.tgz#004d55fcaea10b97925b6fce4f730da18d47c344"
|
||||
integrity sha512-xx9at8P8ABWYG9CLi04Y/uMcy3URIIzA+vy/JGN15Hn/VqDeapRIglwStS1MaQKwCixwZzoUpIoDaGcHdcoFWg==
|
||||
version "4.0.13"
|
||||
resolved "https://registry.yarnpkg.com/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-4.0.13.tgz#bf78dc341d63bc556ec26e76cd63c828862aee6f"
|
||||
integrity sha512-vbfQHlPGU0AXuyEeMpKDEMS5FuF5EN5aVSPqxK0vbyhbC7vrE8H2AFwI2avWdPgo9crpld2EtT1IUhkAogAefw==
|
||||
|
||||
typedoc-plugin-missing-exports@^3.0.0:
|
||||
version "3.1.0"
|
||||
@@ -6283,9 +6320,9 @@ typedoc-plugin-missing-exports@^3.0.0:
|
||||
integrity sha512-Sogbaj+qDa21NjB3SlIw4JXSwmcl/WOjwiPNaVEcPhpNG/MiRTtpwV81cT7h1cbu9StpONFPbddYWR0KV/fTWA==
|
||||
|
||||
typedoc@^0.27.0:
|
||||
version "0.27.6"
|
||||
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.27.6.tgz#7e8d067bd5386b7908afcb12c9054a83e8bb326b"
|
||||
integrity sha512-oBFRoh2Px6jFx366db0lLlihcalq/JzyCVp7Vaq1yphL/tbgx2e+bkpkCgJPunaPvPwoTOXSwasfklWHm7GfAw==
|
||||
version "0.27.7"
|
||||
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.27.7.tgz#09047ffb5c845f45765de26c68b77260867fe967"
|
||||
integrity sha512-K/JaUPX18+61W3VXek1cWC5gwmuLvYTOXJzBvD9W7jFvbPnefRnCHQCEPw7MSNrP/Hj7JJrhZtDDLKdcYm6ucg==
|
||||
dependencies:
|
||||
"@gerrit0/mini-shiki" "^1.24.0"
|
||||
lunr "^2.3.9"
|
||||
@@ -6642,6 +6679,6 @@ zod-validation-error@^3.0.3:
|
||||
integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==
|
||||
|
||||
zod@^3.22.4:
|
||||
version "3.24.1"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee"
|
||||
integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==
|
||||
version "3.24.2"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3"
|
||||
integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==
|
||||
|
||||
Reference in New Issue
Block a user