Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d53d2dff45 | |||
| 3657eb61d8 | |||
| 1def88eb35 | |||
| 5a65c8436d | |||
| 2090319bdd | |||
| f4de8837fd | |||
| 1e92c13a75 | |||
| 8061fa924d | |||
| 0760c5f64c | |||
| e2bdb2f057 | |||
| fd47a189e0 | |||
| c233334f27 | |||
| 029ab5f109 | |||
| 366f686827 | |||
| 26387287af | |||
| ba0bbe1cba | |||
| 3c37d7b0fb |
@@ -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@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc
|
||||
- uses: guibranco/github-status-action-v2@fe98467f9071758c7fc214af9dbac7f301bd23d4
|
||||
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@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc
|
||||
- uses: guibranco/github-status-action-v2@fe98467f9071758c7fc214af9dbac7f301bd23d4
|
||||
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@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc
|
||||
uses: guibranco/github-status-action-v2@fe98467f9071758c7fc214af9dbac7f301bd23d4
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "37.2.0",
|
||||
"version": "37.3.0-rc.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -97,11 +97,11 @@
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.1",
|
||||
"eslint-import-resolver-typescript": "^4.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jest": "^28.0.0",
|
||||
"eslint-plugin-jsdoc": "^50.0.0",
|
||||
"eslint-plugin-matrix-org": "^2.1.0",
|
||||
"eslint-plugin-matrix-org": "2.1.0",
|
||||
"eslint-plugin-n": "^14.0.0",
|
||||
"eslint-plugin-tsdoc": "^0.4.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
@@ -117,13 +117,13 @@
|
||||
"lint-staged": "^15.0.2",
|
||||
"matrix-mock-request": "^2.5.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"prettier": "3.5.1",
|
||||
"prettier": "3.5.3",
|
||||
"rimraf": "^6.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.27.0",
|
||||
"typedoc": "^0.28.1",
|
||||
"typedoc-plugin-coverage": "^3.0.0",
|
||||
"typedoc-plugin-mdn-links": "^5.0.0",
|
||||
"typedoc-plugin-missing-exports": "^3.0.0",
|
||||
"typedoc-plugin-missing-exports": "^4.0.0",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"@casualbot/jest-sonar-reporter": {
|
||||
|
||||
@@ -157,6 +157,30 @@ describe("MatrixClient", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("mediaConfig", function () {
|
||||
it("should get media config on unauthenticated media call", async () => {
|
||||
httpBackend.when("GET", "/_matrix/media/v3/config").respond(200, '{"m.upload.size": 50000000}', true);
|
||||
|
||||
const prom = client.getMediaConfig();
|
||||
|
||||
httpBackend.flushAllExpected();
|
||||
|
||||
expect((await prom)["m.upload.size"]).toEqual(50000000);
|
||||
});
|
||||
|
||||
it("should get media config on authenticated media call", async () => {
|
||||
httpBackend
|
||||
.when("GET", "/_matrix/client/v1/media/config")
|
||||
.respond(200, '{"m.upload.size": 50000000}', true);
|
||||
|
||||
const prom = client.getMediaConfig(true);
|
||||
|
||||
httpBackend.flushAllExpected();
|
||||
|
||||
expect((await prom)["m.upload.size"]).toEqual(50000000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("joinRoom", function () {
|
||||
it("should no-op given the ID of a room you've already joined", async () => {
|
||||
const roomId = "!foo:bar";
|
||||
|
||||
@@ -649,11 +649,13 @@ describe("SlidingSyncSdk", () => {
|
||||
ext = findExtension("e2ee");
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
it("gets enabled all the time", async () => {
|
||||
expect(await ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(await ext.onRequest(false)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("can update device lists", () => {
|
||||
@@ -695,11 +697,13 @@ describe("SlidingSyncSdk", () => {
|
||||
ext = findExtension("account_data");
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
it("gets enabled all the time", async () => {
|
||||
expect(await ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(await ext.onRequest(false)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("processes global account data", async () => {
|
||||
@@ -823,8 +827,12 @@ describe("SlidingSyncSdk", () => {
|
||||
ext = findExtension("to_device");
|
||||
});
|
||||
|
||||
it("gets enabled with a limit on the initial request only", () => {
|
||||
const reqJson: any = ext.onRequest(true);
|
||||
it("gets enabled all the time", async () => {
|
||||
let reqJson: any = await ext.onRequest(true);
|
||||
expect(reqJson.enabled).toEqual(true);
|
||||
expect(reqJson.limit).toBeGreaterThan(0);
|
||||
expect(reqJson.since).toBeUndefined();
|
||||
reqJson = await ext.onRequest(false);
|
||||
expect(reqJson.enabled).toEqual(true);
|
||||
expect(reqJson.limit).toBeGreaterThan(0);
|
||||
expect(reqJson.since).toBeUndefined();
|
||||
@@ -835,7 +843,7 @@ describe("SlidingSyncSdk", () => {
|
||||
next_batch: "12345",
|
||||
events: [],
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual({
|
||||
expect(await ext.onRequest(false)).toMatchObject({
|
||||
since: "12345",
|
||||
});
|
||||
});
|
||||
@@ -919,11 +927,13 @@ describe("SlidingSyncSdk", () => {
|
||||
ext = findExtension("typing");
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
it("gets enabled all the time", async () => {
|
||||
expect(await ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(await ext.onRequest(false)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("processes typing notifications", async () => {
|
||||
@@ -1042,11 +1052,13 @@ describe("SlidingSyncSdk", () => {
|
||||
ext = findExtension("receipts");
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
it("gets enabled all the time", async () => {
|
||||
expect(await ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(await ext.onRequest(false)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("processes receipts", async () => {
|
||||
|
||||
+30
-747
@@ -41,7 +41,7 @@ describe("SlidingSync", () => {
|
||||
const selfUserId = "@alice:localhost";
|
||||
const selfAccessToken = "aseukfgwef";
|
||||
const proxyBaseUrl = "http://localhost:8008";
|
||||
const syncUrl = proxyBaseUrl + "/_matrix/client/unstable/org.matrix.msc3575/sync";
|
||||
const syncUrl = proxyBaseUrl + "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync";
|
||||
|
||||
// assign client/httpBackend globals
|
||||
const setupClient = () => {
|
||||
@@ -103,8 +103,8 @@ describe("SlidingSync", () => {
|
||||
};
|
||||
const ext: Extension<any, any> = {
|
||||
name: () => "custom_extension",
|
||||
onRequest: (initial) => {
|
||||
return { initial: initial };
|
||||
onRequest: async (_) => {
|
||||
return { initial: true };
|
||||
},
|
||||
onResponse: async (res) => {
|
||||
return;
|
||||
@@ -143,18 +143,16 @@ describe("SlidingSync", () => {
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
|
||||
// expect nothing but ranges and non-initial extensions to be sent
|
||||
// expect all params to be sent TODO: check MSC4186
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check(function (req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toBeFalsy();
|
||||
expect(body.lists["a"]).toEqual({
|
||||
ranges: [[0, 10]],
|
||||
});
|
||||
expect(body.lists["a"]).toEqual(listInfo);
|
||||
expect(body.extensions).toBeTruthy();
|
||||
expect(body.extensions["custom_extension"]).toEqual({ initial: false });
|
||||
expect(body.extensions["custom_extension"]).toEqual({ initial: true });
|
||||
expect(req.queryParams!["pos"]).toEqual("11");
|
||||
})
|
||||
.respond(200, function () {
|
||||
@@ -332,6 +330,7 @@ describe("SlidingSync", () => {
|
||||
await p;
|
||||
});
|
||||
|
||||
// TODO: this does not exist in MSC4186
|
||||
it("should be able to unsubscribe from a room", async () => {
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
@@ -389,18 +388,19 @@ describe("SlidingSync", () => {
|
||||
[3, 5],
|
||||
];
|
||||
|
||||
// request first 3 rooms
|
||||
const listReq = {
|
||||
ranges: [[0, 2]],
|
||||
sort: ["by_name"],
|
||||
timeline_limit: 1,
|
||||
required_state: [["m.room.topic", ""]],
|
||||
filters: {
|
||||
is_dm: true,
|
||||
},
|
||||
};
|
||||
|
||||
let slidingSync: SlidingSync;
|
||||
it("should be possible to subscribe to a list", async () => {
|
||||
// request first 3 rooms
|
||||
const listReq = {
|
||||
ranges: [[0, 2]],
|
||||
sort: ["by_name"],
|
||||
timeline_limit: 1,
|
||||
required_state: [["m.room.topic", ""]],
|
||||
filters: {
|
||||
is_dm: true,
|
||||
},
|
||||
};
|
||||
slidingSync = new SlidingSync(proxyBaseUrl, new Map([["a", listReq]]), {}, client!, 1);
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
@@ -452,11 +452,6 @@ describe("SlidingSync", () => {
|
||||
expect(slidingSync.getListData("b")).toBeNull();
|
||||
const syncData = slidingSync.getListData("a")!;
|
||||
expect(syncData.joinedCount).toEqual(500); // from previous test
|
||||
expect(syncData.roomIndexToRoomId).toEqual({
|
||||
0: roomA,
|
||||
1: roomB,
|
||||
2: roomC,
|
||||
});
|
||||
});
|
||||
|
||||
it("should be possible to adjust list ranges", async () => {
|
||||
@@ -467,10 +462,9 @@ describe("SlidingSync", () => {
|
||||
const body = req.data;
|
||||
logger.log("next ranges", body.lists["a"].ranges);
|
||||
expect(body.lists).toBeTruthy();
|
||||
expect(body.lists["a"]).toEqual({
|
||||
// only the ranges should be sent as the rest are unchanged and sticky
|
||||
ranges: newRanges,
|
||||
});
|
||||
// list range should be changed
|
||||
listReq.ranges = newRanges;
|
||||
expect(body.lists["a"]).toEqual(listReq); // resend all values TODO: check MSC4186
|
||||
})
|
||||
.respond(200, {
|
||||
pos: "b",
|
||||
@@ -495,7 +489,9 @@ describe("SlidingSync", () => {
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
// setListRanges for an invalid list key returns an error
|
||||
await expect(slidingSync.setListRanges("idontexist", newRanges)).rejects.toBeTruthy();
|
||||
expect(() => {
|
||||
slidingSync.setListRanges("idontexist", newRanges);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should be possible to add an extra list", async () => {
|
||||
@@ -513,10 +509,7 @@ describe("SlidingSync", () => {
|
||||
const body = req.data;
|
||||
logger.log("extra list", body);
|
||||
expect(body.lists).toBeTruthy();
|
||||
expect(body.lists["a"]).toEqual({
|
||||
// only the ranges should be sent as the rest are unchanged and sticky
|
||||
ranges: newRanges,
|
||||
});
|
||||
expect(body.lists["a"]).toEqual(listReq); // resend all values TODO: check MSC4186
|
||||
expect(body.lists["b"]).toEqual(extraListReq);
|
||||
})
|
||||
.respond(200, {
|
||||
@@ -537,16 +530,6 @@ describe("SlidingSync", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
listenUntil(slidingSync, "SlidingSync.List", (listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("b");
|
||||
expect(joinedCount).toEqual(50);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomA,
|
||||
1: roomB,
|
||||
2: roomC,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
@@ -554,706 +537,6 @@ describe("SlidingSync", () => {
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
});
|
||||
|
||||
it("should be possible to get list DELETE/INSERTs", async () => {
|
||||
// move C (2) to A (0)
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "e",
|
||||
lists: {
|
||||
a: {
|
||||
count: 500,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 2,
|
||||
},
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 0,
|
||||
room_id: roomC,
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
let listPromise = listenUntil(
|
||||
slidingSync,
|
||||
"SlidingSync.List",
|
||||
(listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(500);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
1: roomA,
|
||||
2: roomB,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
let responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
|
||||
// move C (0) back to A (2)
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "f",
|
||||
lists: {
|
||||
a: {
|
||||
count: 500,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 2,
|
||||
room_id: roomC,
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
listPromise = listenUntil(slidingSync, "SlidingSync.List", (listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(500);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomA,
|
||||
1: roomB,
|
||||
2: roomC,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
it("should ignore invalid list indexes", async () => {
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "e",
|
||||
lists: {
|
||||
a: {
|
||||
count: 500,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 2324324,
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
const listPromise = listenUntil(
|
||||
slidingSync,
|
||||
"SlidingSync.List",
|
||||
(listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(500);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomA,
|
||||
1: roomB,
|
||||
2: roomC,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
it("should be possible to update a list", async () => {
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "g",
|
||||
lists: {
|
||||
a: {
|
||||
count: 42,
|
||||
ops: [
|
||||
{
|
||||
op: "INVALIDATE",
|
||||
range: [0, 2],
|
||||
},
|
||||
{
|
||||
op: "SYNC",
|
||||
range: [0, 1],
|
||||
room_ids: [roomB, roomC],
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
// update the list with a new filter
|
||||
slidingSync.setList("a", {
|
||||
filters: {
|
||||
is_encrypted: true,
|
||||
},
|
||||
ranges: [[0, 100]],
|
||||
});
|
||||
const listPromise = listenUntil(
|
||||
slidingSync,
|
||||
"SlidingSync.List",
|
||||
(listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(42);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomB,
|
||||
1: roomC,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
// this refers to a set of operations where the end result is no change.
|
||||
it("should handle net zero operations correctly", async () => {
|
||||
const indexToRoomId = {
|
||||
0: roomB,
|
||||
1: roomC,
|
||||
};
|
||||
expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual(indexToRoomId);
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "f",
|
||||
// currently the list is [B,C] so we will insert D then immediately delete it
|
||||
lists: {
|
||||
a: {
|
||||
count: 500,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 2,
|
||||
},
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 0,
|
||||
room_id: roomA,
|
||||
},
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
const listPromise = listenUntil(
|
||||
slidingSync,
|
||||
"SlidingSync.List",
|
||||
(listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(500);
|
||||
expect(roomIndexToRoomId).toEqual(indexToRoomId);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
it("should handle deletions correctly", async () => {
|
||||
expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({
|
||||
0: roomB,
|
||||
1: roomC,
|
||||
});
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "g",
|
||||
lists: {
|
||||
a: {
|
||||
count: 499,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
const listPromise = listenUntil(
|
||||
slidingSync,
|
||||
"SlidingSync.List",
|
||||
(listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(499);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
it("should handle insertions correctly", async () => {
|
||||
expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
});
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "h",
|
||||
lists: {
|
||||
a: {
|
||||
count: 500,
|
||||
ops: [
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 1,
|
||||
room_id: roomA,
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
let listPromise = listenUntil(
|
||||
slidingSync,
|
||||
"SlidingSync.List",
|
||||
(listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(500);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
1: roomA,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
let responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "h",
|
||||
lists: {
|
||||
a: {
|
||||
count: 501,
|
||||
ops: [
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 1,
|
||||
room_id: roomB,
|
||||
},
|
||||
],
|
||||
},
|
||||
b: {
|
||||
count: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
listPromise = listenUntil(slidingSync, "SlidingSync.List", (listKey, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listKey).toEqual("a");
|
||||
expect(joinedCount).toEqual(501);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
1: roomB,
|
||||
2: roomA,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
slidingSync.stop();
|
||||
});
|
||||
|
||||
// Regression test to make sure things like DELETE 0 INSERT 0 work correctly and we don't
|
||||
// end up losing room IDs.
|
||||
it("should handle insertions with a spurious DELETE correctly", async () => {
|
||||
slidingSync = new SlidingSync(
|
||||
proxyBaseUrl,
|
||||
new Map([
|
||||
[
|
||||
"a",
|
||||
{
|
||||
ranges: [[0, 20]],
|
||||
},
|
||||
],
|
||||
]),
|
||||
{},
|
||||
client!,
|
||||
1,
|
||||
);
|
||||
// initially start with nothing
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "a",
|
||||
lists: {
|
||||
a: {
|
||||
count: 0,
|
||||
ops: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
slidingSync.start();
|
||||
await httpBackend!.flushAllExpected();
|
||||
expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({});
|
||||
|
||||
// insert a room
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "b",
|
||||
lists: {
|
||||
a: {
|
||||
count: 1,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 0,
|
||||
room_id: roomA,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({
|
||||
0: roomA,
|
||||
});
|
||||
|
||||
// insert another room
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "c",
|
||||
lists: {
|
||||
a: {
|
||||
count: 1,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 0,
|
||||
room_id: roomB,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({
|
||||
0: roomB,
|
||||
1: roomA,
|
||||
});
|
||||
|
||||
// insert a final room
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "c",
|
||||
lists: {
|
||||
a: {
|
||||
count: 1,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE",
|
||||
index: 2,
|
||||
},
|
||||
{
|
||||
op: "INSERT",
|
||||
index: 0,
|
||||
room_id: roomC,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
1: roomB,
|
||||
2: roomA,
|
||||
});
|
||||
slidingSync.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("transaction IDs", () => {
|
||||
beforeAll(setupClient);
|
||||
afterAll(teardownClient);
|
||||
const roomId = "!foo:bar";
|
||||
|
||||
let slidingSync: SlidingSync;
|
||||
|
||||
// really this applies to them all but it's easier to just test one
|
||||
it("should resolve modifyRoomSubscriptions after SlidingSync.start() is called", async () => {
|
||||
const roomSubInfo = {
|
||||
timeline_limit: 1,
|
||||
required_state: [["m.room.name", ""]],
|
||||
};
|
||||
// add the subscription
|
||||
slidingSync = new SlidingSync(proxyBaseUrl, new Map(), roomSubInfo, client!, 1);
|
||||
// modification before SlidingSync.start()
|
||||
const subscribePromise = slidingSync.modifyRoomSubscriptions(new Set([roomId]));
|
||||
let txnId: string | undefined;
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check(function (req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toBeTruthy();
|
||||
expect(body.room_subscriptions[roomId]).toEqual(roomSubInfo);
|
||||
expect(body.txn_id).toBeTruthy();
|
||||
txnId = body.txn_id;
|
||||
})
|
||||
.respond(200, function () {
|
||||
return {
|
||||
pos: "aaa",
|
||||
txn_id: txnId,
|
||||
lists: {},
|
||||
extensions: {},
|
||||
rooms: {
|
||||
[roomId]: {
|
||||
name: "foo bar",
|
||||
required_state: [],
|
||||
timeline: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
slidingSync.start();
|
||||
await httpBackend!.flushAllExpected();
|
||||
await subscribePromise;
|
||||
});
|
||||
it("should resolve setList during a connection", async () => {
|
||||
const newList = {
|
||||
ranges: [[0, 20]],
|
||||
};
|
||||
const promise = slidingSync.setList("a", newList);
|
||||
let txnId: string | undefined;
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check(function (req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toBeFalsy();
|
||||
expect(body.lists["a"]).toEqual(newList);
|
||||
expect(body.txn_id).toBeTruthy();
|
||||
txnId = body.txn_id;
|
||||
})
|
||||
.respond(200, function () {
|
||||
return {
|
||||
pos: "bbb",
|
||||
txn_id: txnId,
|
||||
lists: { a: { count: 5 } },
|
||||
extensions: {},
|
||||
};
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await promise;
|
||||
expect(txnId).toBeDefined();
|
||||
});
|
||||
it("should resolve setListRanges during a connection", async () => {
|
||||
const promise = slidingSync.setListRanges("a", [[20, 40]]);
|
||||
let txnId: string | undefined;
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check(function (req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toBeFalsy();
|
||||
expect(body.lists["a"]).toEqual({
|
||||
ranges: [[20, 40]],
|
||||
});
|
||||
expect(body.txn_id).toBeTruthy();
|
||||
txnId = body.txn_id;
|
||||
})
|
||||
.respond(200, function () {
|
||||
return {
|
||||
pos: "ccc",
|
||||
txn_id: txnId,
|
||||
lists: { a: { count: 5 } },
|
||||
extensions: {},
|
||||
};
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await promise;
|
||||
expect(txnId).toBeDefined();
|
||||
});
|
||||
it("should resolve modifyRoomSubscriptionInfo during a connection", async () => {
|
||||
const promise = slidingSync.modifyRoomSubscriptionInfo({
|
||||
timeline_limit: 99,
|
||||
});
|
||||
let txnId: string | undefined;
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check(function (req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toBeTruthy();
|
||||
expect(body.room_subscriptions[roomId]).toEqual({
|
||||
timeline_limit: 99,
|
||||
});
|
||||
expect(body.txn_id).toBeTruthy();
|
||||
txnId = body.txn_id;
|
||||
})
|
||||
.respond(200, function () {
|
||||
return {
|
||||
pos: "ddd",
|
||||
txn_id: txnId,
|
||||
extensions: {},
|
||||
};
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
await promise;
|
||||
expect(txnId).toBeDefined();
|
||||
});
|
||||
it("should reject earlier pending promises if a later transaction is acknowledged", async () => {
|
||||
// i.e if we have [A,B,C] and see txn_id=C then A,B should be rejected.
|
||||
const gotTxnIds: any[] = [];
|
||||
const pushTxn = function (req: MockHttpBackend["requests"][0]) {
|
||||
gotTxnIds.push(req.data.txn_id);
|
||||
};
|
||||
const failPromise = slidingSync.setListRanges("a", [[20, 40]]);
|
||||
httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "e" }); // missing txn_id
|
||||
await httpBackend!.flushAllExpected();
|
||||
const failPromise2 = slidingSync.setListRanges("a", [[60, 70]]);
|
||||
httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "f" }); // missing txn_id
|
||||
await httpBackend!.flushAllExpected();
|
||||
|
||||
const okPromise = slidingSync.setListRanges("a", [[0, 20]]);
|
||||
let txnId: string | undefined;
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check((req) => {
|
||||
txnId = req.data.txn_id;
|
||||
})
|
||||
.respond(200, () => {
|
||||
// include the txn_id, earlier requests should now be reject()ed.
|
||||
return {
|
||||
pos: "g",
|
||||
txn_id: txnId,
|
||||
};
|
||||
});
|
||||
await Promise.all([
|
||||
expect(failPromise).rejects.toEqual(gotTxnIds[0]),
|
||||
expect(failPromise2).rejects.toEqual(gotTxnIds[1]),
|
||||
httpBackend!.flushAllExpected(),
|
||||
okPromise,
|
||||
]);
|
||||
|
||||
expect(txnId).toBeDefined();
|
||||
});
|
||||
it("should not reject later pending promises if an earlier transaction is acknowledged", async () => {
|
||||
// i.e if we have [A,B,C] and see txn_id=B then C should not be rejected but A should.
|
||||
const gotTxnIds: any[] = [];
|
||||
const pushTxn = function (req: MockHttpBackend["requests"][0]) {
|
||||
gotTxnIds.push(req.data?.txn_id);
|
||||
};
|
||||
const A = slidingSync.setListRanges("a", [[20, 40]]);
|
||||
httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "A" });
|
||||
await httpBackend!.flushAllExpected();
|
||||
const B = slidingSync.setListRanges("a", [[60, 70]]);
|
||||
httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "B" }); // missing txn_id
|
||||
await httpBackend!.flushAllExpected();
|
||||
|
||||
// attach rejection handlers now else if we do it later Jest treats that as an unhandled rejection
|
||||
// which is a fail.
|
||||
|
||||
const C = slidingSync.setListRanges("a", [[0, 20]]);
|
||||
let pendingC = true;
|
||||
C.finally(() => {
|
||||
pendingC = false;
|
||||
});
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check(pushTxn)
|
||||
.respond(200, () => {
|
||||
// include the txn_id for B, so C's promise is outstanding
|
||||
return {
|
||||
pos: "C",
|
||||
txn_id: gotTxnIds[1],
|
||||
};
|
||||
});
|
||||
await Promise.all([
|
||||
expect(A).rejects.toEqual(gotTxnIds[0]),
|
||||
httpBackend!.flushAllExpected(),
|
||||
// A is rejected, see above
|
||||
expect(B).resolves.toEqual(gotTxnIds[1]), // B is resolved
|
||||
]);
|
||||
expect(pendingC).toBe(true); // C is pending still
|
||||
});
|
||||
it("should do nothing for unknown txn_ids", async () => {
|
||||
const promise = slidingSync.setListRanges("a", [[20, 40]]);
|
||||
let pending = true;
|
||||
promise.finally(() => {
|
||||
pending = false;
|
||||
});
|
||||
let txnId: string | undefined;
|
||||
httpBackend!
|
||||
.when("POST", syncUrl)
|
||||
.check(function (req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toBeFalsy();
|
||||
expect(body.lists["a"]).toEqual({
|
||||
ranges: [[20, 40]],
|
||||
});
|
||||
expect(body.txn_id).toBeTruthy();
|
||||
txnId = body.txn_id;
|
||||
})
|
||||
.respond(200, function () {
|
||||
return {
|
||||
pos: "ccc",
|
||||
txn_id: "bogus transaction id",
|
||||
lists: { a: { count: 5 } },
|
||||
extensions: {},
|
||||
};
|
||||
});
|
||||
await httpBackend!.flushAllExpected();
|
||||
expect(txnId).toBeDefined();
|
||||
expect(pending).toBe(true);
|
||||
slidingSync.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("custom room subscriptions", () => {
|
||||
@@ -1543,7 +826,7 @@ describe("SlidingSync", () => {
|
||||
|
||||
const extPre: Extension<any, any> = {
|
||||
name: () => preExtName,
|
||||
onRequest: (initial) => {
|
||||
onRequest: async (initial) => {
|
||||
return onPreExtensionRequest(initial);
|
||||
},
|
||||
onResponse: (res) => {
|
||||
@@ -1553,7 +836,7 @@ describe("SlidingSync", () => {
|
||||
};
|
||||
const extPost: Extension<any, any> = {
|
||||
name: () => postExtName,
|
||||
onRequest: (initial) => {
|
||||
onRequest: async (initial) => {
|
||||
return onPostExtensionRequest(initial);
|
||||
},
|
||||
onResponse: (res) => {
|
||||
@@ -1568,7 +851,7 @@ describe("SlidingSync", () => {
|
||||
|
||||
const callbackOrder: string[] = [];
|
||||
let extensionOnResponseCalled = false;
|
||||
onPreExtensionRequest = () => {
|
||||
onPreExtensionRequest = async () => {
|
||||
return extReq;
|
||||
};
|
||||
onPreExtensionResponse = async (resp) => {
|
||||
@@ -1608,7 +891,7 @@ describe("SlidingSync", () => {
|
||||
});
|
||||
|
||||
it("should be able to send nothing in an extension request/response", async () => {
|
||||
onPreExtensionRequest = () => {
|
||||
onPreExtensionRequest = async () => {
|
||||
return undefined;
|
||||
};
|
||||
let responseCalled = false;
|
||||
@@ -1643,7 +926,7 @@ describe("SlidingSync", () => {
|
||||
|
||||
it("is possible to register extensions after start() has been called", async () => {
|
||||
slidingSync.registerExtension(extPost);
|
||||
onPostExtensionRequest = () => {
|
||||
onPostExtensionRequest = async () => {
|
||||
return extReq;
|
||||
};
|
||||
let responseCalled = false;
|
||||
|
||||
@@ -20,7 +20,13 @@ limitations under the License.
|
||||
import { type MockedFunction, type Mock } from "jest-mock";
|
||||
|
||||
import { EventType, HTTPError, MatrixError, UnsupportedDelayedEventsEndpointError, type Room } from "../../../src";
|
||||
import { type Focus, type LivekitFocusActive, type SessionMembershipData } from "../../../src/matrixrtc";
|
||||
import {
|
||||
MembershipManagerEvent,
|
||||
Status,
|
||||
type Focus,
|
||||
type LivekitFocusActive,
|
||||
type SessionMembershipData,
|
||||
} from "../../../src/matrixrtc";
|
||||
import { LegacyMembershipManager } from "../../../src/matrixrtc/LegacyMembershipManager";
|
||||
import { makeMockClient, makeMockRoom, membershipTemplate, mockCallMembership, type MockClient } from "./mocks";
|
||||
import { MembershipManager } from "../../../src/matrixrtc/NewMembershipManager";
|
||||
@@ -34,6 +40,14 @@ function waitForMockCall(method: MockedFunction<any>, returnVal?: Promise<any>)
|
||||
});
|
||||
});
|
||||
}
|
||||
function waitForMockCallOnce(method: MockedFunction<any>, returnVal?: Promise<any>) {
|
||||
return new Promise<void>((resolve) => {
|
||||
method.mockImplementationOnce(() => {
|
||||
resolve();
|
||||
return returnVal ?? Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createAsyncHandle(method: MockedFunction<any>) {
|
||||
const { reject, resolve, promise } = defer();
|
||||
@@ -78,16 +92,16 @@ describe.each([
|
||||
// There is no need to clean up mocks since we will recreate the client.
|
||||
});
|
||||
|
||||
describe("isJoined()", () => {
|
||||
describe("isActivated()", () => {
|
||||
it("defaults to false", () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
expect(manager.isJoined()).toEqual(false);
|
||||
expect(manager.isActivated()).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns true after join()", () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
manager.join([]);
|
||||
expect(manager.isJoined()).toEqual(true);
|
||||
expect(manager.isActivated()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,6 +139,23 @@ describe.each([
|
||||
{},
|
||||
"_@alice:example.org_AAAAAAA",
|
||||
);
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("reschedules delayed leave event if sending state cancels it", async () => {
|
||||
const memberManager = new TestMembershipManager(undefined, room, client, () => undefined);
|
||||
const waitForSendState = waitForMockCall(client.sendStateEvent);
|
||||
const waitForUpdateDelaye = waitForMockCallOnce(
|
||||
client._unstable_updateDelayedEvent,
|
||||
Promise.reject(new MatrixError({ errcode: "M_NOT_FOUND" })),
|
||||
);
|
||||
memberManager.join([focus], focusActive);
|
||||
await waitForSendState;
|
||||
await waitForUpdateDelaye;
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
// Once for the initial event and once because of the errcode: "M_NOT_FOUND"
|
||||
// Different to "sends a membership event and schedules delayed leave when joining a call" where its only called once (1)
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
describe("does not prefix the state key with _ for rooms that support user-owned state events", () => {
|
||||
@@ -505,7 +536,39 @@ describe.each([
|
||||
await testExpires(10_000, 1_000);
|
||||
});
|
||||
});
|
||||
describe("status updates", () => {
|
||||
it("starts 'Disconnected' !FailsForLegacy", () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
expect(manager.status).toBe(Status.Disconnected);
|
||||
});
|
||||
it("emits 'Connection' and 'Connected' after join !FailsForLegacy", async () => {
|
||||
const handleDelayedEvent = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||
const handleStateEvent = createAsyncHandle(client.sendStateEvent);
|
||||
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
expect(manager.status).toBe(Status.Disconnected);
|
||||
const connectEmit = jest.fn();
|
||||
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
||||
manager.join([focus], focusActive);
|
||||
expect(manager.status).toBe(Status.Connecting);
|
||||
handleDelayedEvent.resolve();
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
expect(connectEmit).toHaveBeenCalledWith(Status.Disconnected, Status.Connecting);
|
||||
handleStateEvent.resolve();
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
expect(connectEmit).toHaveBeenCalledWith(Status.Connecting, Status.Connected);
|
||||
});
|
||||
it("emits 'Disconnecting' and 'Disconnected' after leave !FailsForLegacy", async () => {
|
||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
||||
const connectEmit = jest.fn();
|
||||
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
||||
manager.join([focus], focusActive);
|
||||
await jest.advanceTimersByTimeAsync(1);
|
||||
await manager.leave();
|
||||
expect(connectEmit).toHaveBeenCalledWith(Status.Connected, Status.Disconnecting);
|
||||
expect(connectEmit).toHaveBeenCalledWith(Status.Disconnecting, Status.Disconnected);
|
||||
});
|
||||
});
|
||||
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", () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ limitations under the License.
|
||||
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import { OidcTokenRefresher } from "../../../src";
|
||||
import { OidcTokenRefresher, TokenRefreshLogoutError } from "../../../src";
|
||||
import { logger } from "../../../src/logger";
|
||||
import { makeDelegatedAuthConfig } from "../../test-utils/oidc";
|
||||
|
||||
@@ -266,5 +266,27 @@ describe("OidcTokenRefresher", () => {
|
||||
refreshToken: "second-new-refresh-token",
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw TokenRefreshLogoutError when expired", async () => {
|
||||
fetchMock.post(
|
||||
config.token_endpoint,
|
||||
{
|
||||
status: 400,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: {
|
||||
error: "invalid_grant",
|
||||
error_description: "The provided access grant is invalid, expired, or revoked.",
|
||||
},
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
const refresher = new OidcTokenRefresher(authConfig.issuer, clientId, redirectUri, deviceId, idTokenClaims);
|
||||
await refresher.oidcClientReady;
|
||||
|
||||
await expect(refresher.doRefreshAccessToken("refresh-token")).rejects.toThrow(TokenRefreshLogoutError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
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 * as utils from "../test-utils/test-utils";
|
||||
import { type IActionsObject, PushProcessor } from "../../src/pushprocessor";
|
||||
import {
|
||||
@@ -1004,3 +1020,25 @@ describe("rewriteDefaultRules", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPushRuleGlobRegex", () => {
|
||||
it("should not confuse flags in cache", () => {
|
||||
const pattern = "Test";
|
||||
const regex1 = PushProcessor.getPushRuleGlobRegex(pattern, false, "i");
|
||||
const regex2 = PushProcessor.getPushRuleGlobRegex(pattern, false, "g");
|
||||
const regex3 = PushProcessor.getPushRuleGlobRegex(pattern, false, "i");
|
||||
|
||||
expect(regex1.flags).toBe("i");
|
||||
expect(regex2.flags).toBe("g");
|
||||
|
||||
expect(regex1).not.toEqual(regex2);
|
||||
expect(regex1).toEqual(regex3);
|
||||
});
|
||||
|
||||
it("should not include word boundary in match", () => {
|
||||
const pattern = "@room";
|
||||
const regex = PushProcessor.getPushRuleGlobRegex(pattern, true);
|
||||
const input = "Foo @room Bar";
|
||||
expect(input.split(regex)).toEqual(["Foo ", "@room", " Bar"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1234,6 +1234,16 @@ describe("Room", function () {
|
||||
expect(room.name).toEqual(nameB);
|
||||
});
|
||||
|
||||
it("supports MSC4186 style heroes", () => {
|
||||
const nameB = "Bertha Bobbington";
|
||||
const nameC = "Clarissa Harissa";
|
||||
addMember(userB, KnownMembership.Join, { name: nameB });
|
||||
addMember(userC, KnownMembership.Join, { name: nameC });
|
||||
room.setMSC4186SummaryData([{ user_id: userB }, { user_id: userC }], undefined, undefined);
|
||||
room.recalculate();
|
||||
expect(room.name).toEqual(`${nameB} and ${nameC}`);
|
||||
});
|
||||
|
||||
it("reverts to empty room in case of self chat", function () {
|
||||
room.setSummary({
|
||||
"m.heroes": [],
|
||||
@@ -4276,4 +4286,9 @@ describe("Room", function () {
|
||||
expect(filteredEvents[0].getContent().body).toEqual("ev2");
|
||||
});
|
||||
});
|
||||
|
||||
it("saves and retrieves the bump stamp", () => {
|
||||
room.setBumpStamp(123456789);
|
||||
expect(room.getBumpStamp()).toEqual(123456789);
|
||||
});
|
||||
});
|
||||
|
||||
+12
-5
@@ -1234,7 +1234,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public canSupport = new Map<Feature, ServerSupport>();
|
||||
|
||||
// The pushprocessor caches useful things, so keep one and re-use it
|
||||
protected pushProcessor = new PushProcessor(this);
|
||||
public readonly pushProcessor = new PushProcessor(this);
|
||||
|
||||
// Promise to a response of the server's /versions response
|
||||
// TODO: This should expire: https://github.com/matrix-org/matrix-js-sdk/issues/1020
|
||||
@@ -2069,11 +2069,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
|
||||
/**
|
||||
* Get the config for the media repository.
|
||||
*
|
||||
* @param useAuthenticatedMedia - If true, the caller supports authenticated
|
||||
* media and wants an authentication-required URL. Note that server support
|
||||
* for authenticated media will *not* be checked - it is the caller's responsibility
|
||||
* to do so before calling this function.
|
||||
*
|
||||
* @returns Promise which resolves with an object containing the config.
|
||||
*/
|
||||
public getMediaConfig(): Promise<IMediaConfig> {
|
||||
return this.http.authedRequest(Method.Get, "/config", undefined, undefined, {
|
||||
prefix: MediaPrefix.V3,
|
||||
public getMediaConfig(useAuthenticatedMedia: boolean = false): Promise<IMediaConfig> {
|
||||
const path = useAuthenticatedMedia ? "/media/config" : "/config";
|
||||
return this.http.authedRequest(Method.Get, path, undefined, undefined, {
|
||||
prefix: useAuthenticatedMedia ? ClientPrefix.V1 : MediaPrefix.V3,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8194,7 +8201,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const clientTimeout = req.clientTimeout;
|
||||
delete req.clientTimeout;
|
||||
return this.http.authedRequest<MSC3575SlidingSyncResponse>(Method.Post, "/sync", qps, req, {
|
||||
prefix: "/_matrix/client/unstable/org.matrix.msc3575",
|
||||
prefix: "/_matrix/client/unstable/org.matrix.simplified_msc3575",
|
||||
baseUrl: proxyBaseUrl,
|
||||
localTimeoutMs: clientTimeout,
|
||||
abortSignal,
|
||||
|
||||
@@ -138,6 +138,15 @@ export interface SyncCryptoCallbacks {
|
||||
* @param syncState - information about the completed sync.
|
||||
*/
|
||||
onSyncCompleted(syncState: OnSyncCompletedData): void;
|
||||
|
||||
/**
|
||||
* Mark all tracked users' device lists as dirty.
|
||||
*
|
||||
* This method will cause additional `/keys/query` requests on the server, so should be used only
|
||||
* when the client has desynced tracking device list deltas from the server.
|
||||
* In MSC4186: Simplified Sliding Sync, this can happen when the server expires the connection.
|
||||
*/
|
||||
markAllTrackedUsersAsDirty(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -212,3 +212,17 @@ export class TokenRefreshError extends Error {
|
||||
return "TokenRefreshError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TokenRefreshError. This indicates that a request failed due to the token being expired,
|
||||
* and attempting to refresh said token failed in a way indicative of token invalidation.
|
||||
*/
|
||||
export class TokenRefreshLogoutError extends Error {
|
||||
public constructor(cause?: Error) {
|
||||
super(cause?.message ?? "");
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return "TokenRefreshLogoutError";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,10 @@ 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, MatrixError, TokenRefreshError } from "./errors.ts";
|
||||
import { ConnectionError, MatrixError, TokenRefreshError, TokenRefreshLogoutError } from "./errors.ts";
|
||||
import {
|
||||
HttpApiEvent,
|
||||
type HttpApiEventHandlerMap,
|
||||
@@ -234,7 +232,8 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
return TokenRefreshOutcome.Success;
|
||||
} catch (error) {
|
||||
this.opts.logger?.warn("Failed to refresh token", error);
|
||||
if (error instanceof OidcAuthError || error instanceof MatrixError) {
|
||||
// If we get a TokenError or MatrixError, we should log out, otherwise assume transient
|
||||
if (error instanceof TokenRefreshLogoutError || error instanceof MatrixError) {
|
||||
return TokenRefreshOutcome.Logout;
|
||||
}
|
||||
return TokenRefreshOutcome.Failure;
|
||||
|
||||
@@ -27,7 +27,7 @@ import { type Focus } from "./focus.ts";
|
||||
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
||||
import { type MembershipConfig } from "./MatrixRTCSession.ts";
|
||||
import { type EmptyObject } from "../@types/common.ts";
|
||||
import { type IMembershipManager } from "./NewMembershipManager.ts";
|
||||
import { type IMembershipManager, type MembershipManagerEvent, Status } from "./types.ts";
|
||||
|
||||
/**
|
||||
* This internal class is used by the MatrixRTCSession to manage the local user's own membership of the session.
|
||||
@@ -103,9 +103,35 @@ export class LegacyMembershipManager implements IMembershipManager {
|
||||
private getOldestMembership: () => CallMembership | undefined,
|
||||
) {}
|
||||
|
||||
public off(
|
||||
event: MembershipManagerEvent.StatusChanged,
|
||||
listener: (oldStatus: Status, newStatus: Status) => void,
|
||||
): this {
|
||||
logger.error("off is not implemented on LegacyMembershipManager");
|
||||
return this;
|
||||
}
|
||||
|
||||
public on(
|
||||
event: MembershipManagerEvent.StatusChanged,
|
||||
listener: (oldStatus: Status, newStatus: Status) => void,
|
||||
): this {
|
||||
logger.error("on is not implemented on LegacyMembershipManager");
|
||||
return this;
|
||||
}
|
||||
|
||||
public isJoined(): boolean {
|
||||
return this.relativeExpiry !== undefined;
|
||||
}
|
||||
public isActivated(): boolean {
|
||||
return this.isJoined();
|
||||
}
|
||||
/**
|
||||
* Unimplemented
|
||||
* @returns Status.Unknown
|
||||
*/
|
||||
public get status(): Status {
|
||||
return Status.Unknown;
|
||||
}
|
||||
|
||||
public join(fociPreferred: Focus[], fociActive?: Focus): void {
|
||||
this.ownFocusActive = fociActive;
|
||||
|
||||
@@ -25,10 +25,11 @@ import { RoomStateEvent } from "../models/room-state.ts";
|
||||
import { type Focus } from "./focus.ts";
|
||||
import { KnownMembership } from "../@types/membership.ts";
|
||||
import { type MatrixEvent } from "../models/event.ts";
|
||||
import { MembershipManager, type IMembershipManager } from "./NewMembershipManager.ts";
|
||||
import { MembershipManager } from "./NewMembershipManager.ts";
|
||||
import { EncryptionManager, type IEncryptionManager, type Statistics } from "./EncryptionManager.ts";
|
||||
import { LegacyMembershipManager } from "./LegacyMembershipManager.ts";
|
||||
import { logDurationSync } from "../utils.ts";
|
||||
import type { IMembershipManager } from "./types.ts";
|
||||
|
||||
const logger = rootLogger.getChild("MatrixRTCSession");
|
||||
|
||||
|
||||
@@ -24,110 +24,80 @@ import { type Room } from "../models/room.ts";
|
||||
import { defer, type IDeferred } from "../utils.ts";
|
||||
import { type CallMembership, DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "./CallMembership.ts";
|
||||
import { type Focus } from "./focus.ts";
|
||||
import {
|
||||
type IMembershipManager,
|
||||
type MembershipManagerEventHandlerMap,
|
||||
MembershipManagerEvent,
|
||||
Status,
|
||||
} from "./types.ts";
|
||||
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
||||
import { type MembershipConfig } from "./MatrixRTCSession.ts";
|
||||
import { ActionScheduler, type ActionUpdate } from "./NewMembershipManagerActionScheduler.ts";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
|
||||
const logger = rootLogger.getChild("MatrixRTCSession");
|
||||
|
||||
/**
|
||||
* This interface defines what a MembershipManager uses and exposes.
|
||||
* This interface is what we use to write tests and allows changing the actual implementation
|
||||
* without breaking tests because of some internal method renaming.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface IMembershipManager {
|
||||
/**
|
||||
* If we are trying to join, or have successfully joined the session.
|
||||
* It does not reflect if the room state is already configured to represent us being joined.
|
||||
* It only means that the Manager should be trying to connect or to disconnect running.
|
||||
* The Manager is still running right after isJoined becomes false to send the disconnect events.
|
||||
* (A more accurate name would be `isActivated`)
|
||||
* @returns true if we intend to be participating in the MatrixRTC session
|
||||
*/
|
||||
isJoined(): boolean;
|
||||
/**
|
||||
* Start sending all necessary events to make this user participate in the RTC session.
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
* @param fociActive the active focus to use in the joined RTC membership event.
|
||||
* @throws can throw if it exceeds a configured maximum retry.
|
||||
*/
|
||||
join(fociPreferred: Focus[], fociActive?: Focus, onError?: (error: unknown) => void): void;
|
||||
/**
|
||||
* Send all necessary events to make this user leave the RTC session.
|
||||
* @param timeout the maximum duration in ms until the promise is forced to resolve.
|
||||
* @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): Promise<boolean>;
|
||||
/**
|
||||
* Call this if the MatrixRTC session members have changed.
|
||||
*/
|
||||
onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void>;
|
||||
/**
|
||||
* The used active focus in the currently joined session.
|
||||
* @returns the used active focus in the currently joined session or undefined if not joined.
|
||||
*/
|
||||
getActiveFocus(): Focus | undefined;
|
||||
}
|
||||
|
||||
/* MembershipActionTypes:
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│SendFirstDelayedEvent│
|
||||
└─────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
┌────────────│SendJoinEvent│────────────┐
|
||||
│ └─────────────┘ │
|
||||
│ ┌─────┐ ┌──────┐ │ ┌──────┐
|
||||
▼ ▼ │ │ ▼ ▼ ▼ │
|
||||
|
||||
On Join: ───────────────┐ ┌───────────────(1)───────────┐
|
||||
▼ ▼ │
|
||||
┌────────────────┐ │
|
||||
│SendDelayedEvent│ ──────(2)───┐ │
|
||||
└────────────────┘ │ │
|
||||
│(3) │ │
|
||||
▼ │ │
|
||||
┌─────────────┐ │ │
|
||||
┌──────(4)───│SendJoinEvent│────(4)─────┐ │ │
|
||||
│ └─────────────┘ │ │ │
|
||||
│ ┌─────┐ ┌──────┐ │ │ │
|
||||
▼ ▼ │ │ ▼ ▼ ▼ │
|
||||
┌────────────┐ │ │ ┌───────────────────┐ │
|
||||
│UpdateExpiry│ │ │ │RestartDelayedEvent│ │
|
||||
│UpdateExpiry│ (s) (s)|RestartDelayedEvent│ │
|
||||
└────────────┘ │ │ └───────────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
└─────┘ └──────┘ │ │
|
||||
│ │
|
||||
┌────────────────────┐ │ │
|
||||
│SendMainDelayedEvent│◄───────┘ │
|
||||
└───────────────────┬┘ │
|
||||
│ │
|
||||
└─────────────────────┘
|
||||
STOP ALL ABOVE
|
||||
│ │ │ │ │ │
|
||||
└─────┘ └──────┘ └───────┘
|
||||
|
||||
On Leave: ───────── STOP ALL ABOVE
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ SendScheduledDelayedLeaveEvent│
|
||||
└───────────────────────────────┘
|
||||
│
|
||||
┌────────────────────────────────┐
|
||||
│ SendScheduledDelayedLeaveEvent │
|
||||
└────────────────────────────────┘
|
||||
│(5)
|
||||
▼
|
||||
┌──────────────┐
|
||||
│SendLeaveEvent│
|
||||
└──────────────┘
|
||||
|
||||
(1) [Not found error] results in resending the delayed event
|
||||
(2) [hasMemberEvent = true] Sending the delayed event if we
|
||||
already have a call member event results jumping to the
|
||||
RestartDelayedEvent loop directly
|
||||
(3) [hasMemberEvent = false] if there is not call member event
|
||||
sending it is the next step
|
||||
(4) Both (UpdateExpiry and RestartDelayedEvent) actions are
|
||||
scheduled when successfully sending the state event
|
||||
(5) Only if delayed event sending failed (fallback)
|
||||
(s) Successful restart/resend
|
||||
*/
|
||||
|
||||
/**
|
||||
* The different types of actions the MembershipManager can take.
|
||||
* @internal
|
||||
*/
|
||||
export enum MembershipActionType {
|
||||
SendFirstDelayedEvent = "SendFirstDelayedEvent",
|
||||
SendDelayedEvent = "SendDelayedEvent",
|
||||
// -> MembershipActionType.SendJoinEvent if successful
|
||||
// -> DelayedLeaveActionType.SendFirstDelayedEvent on error, retry sending the first delayed event.
|
||||
// -> DelayedLeaveActionType.SendDelayedEvent on error, retry sending the first delayed event.
|
||||
// -> DelayedLeaveActionType.RestartDelayedEvent on success start updating the delayed event
|
||||
SendJoinEvent = "SendJoinEvent",
|
||||
// -> MembershipActionType.SendJoinEvent if we run into a rate limit and need to retry
|
||||
// -> MembershipActionType.Update if we successfully send the join event then schedule the expire event update
|
||||
// -> DelayedLeaveActionType.RestartDelayedEvent to recheck the delayed event
|
||||
RestartDelayedEvent = "RestartDelayedEvent",
|
||||
// -> DelayedLeaveActionType.SendMainDelayedEvent on missing delay id but there is a rtc state event
|
||||
// -> DelayedLeaveActionType.SendFirstDelayedEvent on missing delay id and there is no state event
|
||||
// -> DelayedLeaveActionType.SendDelayedEvent on missing delay id and there is no state event
|
||||
// -> DelayedLeaveActionType.RestartDelayedEvent on success we schedule the next restart
|
||||
UpdateExpiry = "UpdateExpiry",
|
||||
// -> MembershipActionType.Update if the timeout has passed so the next update is required.
|
||||
SendMainDelayedEvent = "SendMainDelayedEvent",
|
||||
// -> DelayedLeaveActionType.RestartDelayedEvent on success start updating the delayed event
|
||||
// -> DelayedLeaveActionType.SendMainDelayedEvent on error try again
|
||||
SendScheduledDelayedLeaveEvent = "SendScheduledDelayedLeaveEvent",
|
||||
// -> MembershipActionType.SendLeaveEvent on failiour (not found) we need to send the leave manually and cannot use the scheduled delayed event
|
||||
// -> DelayedLeaveActionType.SendScheduledDelayedLeaveEvent on error we try again.
|
||||
@@ -138,7 +108,7 @@ export enum MembershipActionType {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface ActionSchedulerState {
|
||||
export interface MembershipManagerState {
|
||||
/** The delayId we got when successfully sending the delayed leave event.
|
||||
* Gets set to undefined if the server claims it cannot find the delayed event anymore. */
|
||||
delayId?: string;
|
||||
@@ -158,17 +128,6 @@ export interface ActionSchedulerState {
|
||||
networkErrorRetries: Map<MembershipActionType, number>;
|
||||
}
|
||||
|
||||
enum Status {
|
||||
Disconnected = "Disconnected",
|
||||
Connecting = "Connecting",
|
||||
ConnectingFailed = "ConnectingFailed",
|
||||
Connected = "Connected",
|
||||
Reconnecting = "Reconnecting",
|
||||
Disconnecting = "Disconnecting",
|
||||
Stuck = "Stuck",
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is responsible for sending all events relating to the own membership of a matrixRTC call.
|
||||
* It has the following tasks:
|
||||
@@ -182,11 +141,18 @@ enum Status {
|
||||
* - Stop the timer for the delay refresh
|
||||
* - Stop the timer for updating the state event
|
||||
*/
|
||||
export class MembershipManager implements IMembershipManager {
|
||||
export class MembershipManager
|
||||
extends TypedEventEmitter<MembershipManagerEvent, MembershipManagerEventHandlerMap>
|
||||
implements IMembershipManager
|
||||
{
|
||||
private activated = false;
|
||||
public isJoined(): boolean {
|
||||
public isActivated(): boolean {
|
||||
return this.activated;
|
||||
}
|
||||
// DEPRECATED use isActivated
|
||||
public isJoined(): boolean {
|
||||
return this.isActivated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the MembershipManager in a state where it tries to be joined.
|
||||
@@ -205,23 +171,27 @@ export class MembershipManager implements IMembershipManager {
|
||||
this.focusActive = focusActive;
|
||||
this.leavePromiseDefer = undefined;
|
||||
this.activated = true;
|
||||
|
||||
this.oldStatus = this.status;
|
||||
this.state = MembershipManager.defaultState;
|
||||
|
||||
this.scheduler
|
||||
.startWithJoin()
|
||||
.then(() => {
|
||||
if (!this.scheduler.running) {
|
||||
this.leavePromiseDefer?.resolve(true);
|
||||
this.leavePromiseDefer = undefined;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("MembershipManager stopped because: ", e);
|
||||
onError?.(e);
|
||||
})
|
||||
// Should already be set to false when calling `leave` in non error cases.
|
||||
.finally(() => (this.activated = false));
|
||||
.finally(() => {
|
||||
// Should already be set to false when calling `leave` in non error cases.
|
||||
this.activated = false;
|
||||
// Here the scheduler is not running anymore so we the `membershipLoopHandler` is not called to emit.
|
||||
if (this.oldStatus && this.oldStatus !== this.status) {
|
||||
this.emit(MembershipManagerEvent.StatusChanged, this.oldStatus, this.status);
|
||||
}
|
||||
if (!this.scheduler.running) {
|
||||
this.leavePromiseDefer?.resolve(true);
|
||||
this.leavePromiseDefer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,7 +226,7 @@ export class MembershipManager implements IMembershipManager {
|
||||
// If one of these actions are scheduled or are getting inserted in the next iteration, we should already
|
||||
// take care of our missing membership.
|
||||
const sendingMembershipActions = [
|
||||
MembershipActionType.SendFirstDelayedEvent,
|
||||
MembershipActionType.SendDelayedEvent,
|
||||
MembershipActionType.SendJoinEvent,
|
||||
];
|
||||
logger.warn("Missing own membership: force re-join");
|
||||
@@ -313,6 +283,7 @@ export class MembershipManager implements IMembershipManager {
|
||||
>,
|
||||
private getOldestMembership: () => CallMembership | undefined,
|
||||
) {
|
||||
super();
|
||||
const [userId, deviceId] = [this.client.getUserId(), this.client.getDeviceId()];
|
||||
if (userId === null) throw Error("Missing userId in client");
|
||||
if (deviceId === null) throw Error("Missing deviceId in client");
|
||||
@@ -322,8 +293,8 @@ export class MembershipManager implements IMembershipManager {
|
||||
}
|
||||
|
||||
// MembershipManager mutable state.
|
||||
private state: ActionSchedulerState;
|
||||
private static get defaultState(): ActionSchedulerState {
|
||||
private state: MembershipManagerState;
|
||||
private static get defaultState(): MembershipManagerState {
|
||||
return {
|
||||
hasMemberStateEvent: false,
|
||||
delayId: undefined,
|
||||
@@ -380,9 +351,13 @@ export class MembershipManager implements IMembershipManager {
|
||||
private oldStatus?: Status;
|
||||
private scheduler = new ActionScheduler((type): Promise<ActionUpdate> => {
|
||||
if (this.oldStatus) {
|
||||
// we put this at the beginning of the actions scheduler loop handle callback since it is a loop this
|
||||
// we put this at the beginning of the actions scheduler loop handle callback since it is a loop this
|
||||
// is equivalent to running it at the end of the loop. (just after applying the status/action list changes)
|
||||
// This order is required because this method needs to return the action updates.
|
||||
logger.debug(`MembershipManager applied action changes. Status: ${this.oldStatus} -> ${this.status}`);
|
||||
if (this.oldStatus !== this.status) {
|
||||
this.emit(MembershipManagerEvent.StatusChanged, this.oldStatus, this.status);
|
||||
}
|
||||
}
|
||||
this.oldStatus = this.status;
|
||||
logger.debug(`MembershipManager before processing action. status=${this.oldStatus}`);
|
||||
@@ -391,17 +366,16 @@ export class MembershipManager implements IMembershipManager {
|
||||
|
||||
// LOOP HANDLER:
|
||||
private async membershipLoopHandler(type: MembershipActionType): Promise<ActionUpdate> {
|
||||
this.oldStatus = this.status;
|
||||
switch (type) {
|
||||
case MembershipActionType.SendFirstDelayedEvent: {
|
||||
case MembershipActionType.SendDelayedEvent: {
|
||||
// Before we start we check if we come from a state where we have a delay id.
|
||||
if (!this.state.delayId) {
|
||||
return this.sendFirstDelayedLeaveEvent(); // Normal case without any previous delayed id.
|
||||
return this.sendOrResendDelayedLeaveEvent(); // Normal case without any previous delayed id.
|
||||
} else {
|
||||
// This can happen if someone else (or another client) removes our own membership event.
|
||||
// It will trigger `onRTCSessionMemberUpdate` queue `MembershipActionType.SendFirstDelayedEvent`.
|
||||
// We might still have our delayed event from the previous participation and dependent on the server this might not
|
||||
// get automatically removed if the state changes. Hence It would remove our membership unexpectedly shortly after the rejoin.
|
||||
// get removed automatically if the state changes. Hence, it would remove our membership unexpectedly shortly after the rejoin.
|
||||
//
|
||||
// In this block we will try to cancel this delayed event before setting up a new one.
|
||||
|
||||
@@ -411,17 +385,10 @@ export class MembershipManager implements IMembershipManager {
|
||||
case MembershipActionType.RestartDelayedEvent: {
|
||||
if (!this.state.delayId) {
|
||||
// Delay id got reset. This action was used to check if the hs canceled the delayed event when the join state got sent.
|
||||
return createInsertActionUpdate(
|
||||
this.state.hasMemberStateEvent
|
||||
? MembershipActionType.SendMainDelayedEvent
|
||||
: MembershipActionType.SendFirstDelayedEvent,
|
||||
);
|
||||
return createInsertActionUpdate(MembershipActionType.SendDelayedEvent);
|
||||
}
|
||||
return this.restartDelayedEvent(this.state.delayId);
|
||||
}
|
||||
case MembershipActionType.SendMainDelayedEvent: {
|
||||
return this.sendMainDelayedEvent();
|
||||
}
|
||||
case MembershipActionType.SendScheduledDelayedLeaveEvent: {
|
||||
// We are already good
|
||||
if (!this.state.hasMemberStateEvent) {
|
||||
@@ -452,7 +419,11 @@ export class MembershipManager implements IMembershipManager {
|
||||
}
|
||||
|
||||
// HANDLERS (used in the membershipLoopHandler)
|
||||
private async sendFirstDelayedLeaveEvent(): Promise<ActionUpdate> {
|
||||
private async sendOrResendDelayedLeaveEvent(): Promise<ActionUpdate> {
|
||||
// We can reach this at the start of a call (where we do not yet have a membership: state.hasMemberStateEvent=false)
|
||||
// or during a call if the state event canceled our delayed event or caused by an unexpected error that removed our delayed event.
|
||||
// (Another client could have canceled it, the homeserver might have removed/lost it due to a restart, ...)
|
||||
// In the `then` and `catch` block we treat both cases differently. "if (this.state.hasMemberStateEvent) {} else {}"
|
||||
return await this.client
|
||||
._unstable_sendDelayedStateEvent(
|
||||
this.room.roomId,
|
||||
@@ -465,27 +436,46 @@ export class MembershipManager implements IMembershipManager {
|
||||
)
|
||||
.then((response) => {
|
||||
// On success we reset retries and set delayId.
|
||||
this.state.rateLimitRetries.set(MembershipActionType.SendFirstDelayedEvent, 0);
|
||||
this.state.networkErrorRetries.set(MembershipActionType.SendFirstDelayedEvent, 0);
|
||||
this.resetRateLimitCounter(MembershipActionType.SendDelayedEvent);
|
||||
this.state.delayId = response.delay_id;
|
||||
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
||||
if (this.state.hasMemberStateEvent) {
|
||||
// This action was scheduled because the previous delayed event was cancelled
|
||||
// due to lack of https://github.com/element-hq/synapse/pull/17810
|
||||
return createInsertActionUpdate(
|
||||
MembershipActionType.RestartDelayedEvent,
|
||||
this.membershipKeepAlivePeriod,
|
||||
);
|
||||
} else {
|
||||
// This action was scheduled because we are in the process of joining
|
||||
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
const repeatActionType = MembershipActionType.SendFirstDelayedEvent;
|
||||
const repeatActionType = MembershipActionType.SendDelayedEvent;
|
||||
if (this.manageMaxDelayExceededSituation(e)) {
|
||||
return createInsertActionUpdate(repeatActionType);
|
||||
}
|
||||
const update = this.actionUpdateFromErrors(e, repeatActionType, "sendDelayedStateEvent");
|
||||
if (update) return update;
|
||||
|
||||
// log and fall through
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) {
|
||||
logger.info("Not using delayed event because the endpoint is not supported");
|
||||
if (this.state.hasMemberStateEvent) {
|
||||
// This action was scheduled because the previous delayed event was cancelled
|
||||
// due to lack of https://github.com/element-hq/synapse/pull/17810
|
||||
|
||||
// Don't do any other delayed event work if its not supported.
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) return {};
|
||||
throw Error("Could not send delayed event, even though delayed events are supported. " + e);
|
||||
} else {
|
||||
logger.info("Not using delayed event because: " + e);
|
||||
// This action was scheduled because we are in the process of joining
|
||||
// log and fall through
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) {
|
||||
logger.info("Not using delayed event because the endpoint is not supported");
|
||||
} else {
|
||||
logger.info("Not using delayed event because: " + e);
|
||||
}
|
||||
// On any other error we fall back to not using delayed events and send the join state event immediately
|
||||
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
||||
}
|
||||
// On any other error we fall back to not using delayed events and send the join state event immediately
|
||||
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -495,11 +485,11 @@ export class MembershipManager implements IMembershipManager {
|
||||
._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Cancel)
|
||||
.then(() => {
|
||||
this.state.delayId = undefined;
|
||||
this.resetRateLimitCounter(MembershipActionType.SendFirstDelayedEvent);
|
||||
return createReplaceActionUpdate(MembershipActionType.SendFirstDelayedEvent);
|
||||
this.resetRateLimitCounter(MembershipActionType.SendDelayedEvent);
|
||||
return createReplaceActionUpdate(MembershipActionType.SendDelayedEvent);
|
||||
})
|
||||
.catch((e) => {
|
||||
const repeatActionType = MembershipActionType.SendFirstDelayedEvent;
|
||||
const repeatActionType = MembershipActionType.SendDelayedEvent;
|
||||
const update = this.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
|
||||
if (update) return update;
|
||||
|
||||
@@ -538,7 +528,7 @@ export class MembershipManager implements IMembershipManager {
|
||||
const repeatActionType = MembershipActionType.RestartDelayedEvent;
|
||||
if (this.isNotFoundError(e)) {
|
||||
this.state.delayId = undefined;
|
||||
return createInsertActionUpdate(MembershipActionType.SendMainDelayedEvent);
|
||||
return createInsertActionUpdate(MembershipActionType.SendDelayedEvent);
|
||||
}
|
||||
// If the HS does not support delayed events we wont reschedule.
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) return {};
|
||||
@@ -552,40 +542,6 @@ export class MembershipManager implements IMembershipManager {
|
||||
});
|
||||
}
|
||||
|
||||
private async sendMainDelayedEvent(): Promise<ActionUpdate> {
|
||||
return await this.client
|
||||
._unstable_sendDelayedStateEvent(
|
||||
this.room.roomId,
|
||||
{
|
||||
delay: this.membershipServerSideExpiryTimeout,
|
||||
},
|
||||
EventType.GroupCallMemberPrefix,
|
||||
{}, // leave event
|
||||
this.stateKey,
|
||||
)
|
||||
.then((response) => {
|
||||
this.state.delayId = response.delay_id;
|
||||
this.resetRateLimitCounter(MembershipActionType.SendMainDelayedEvent);
|
||||
return createInsertActionUpdate(
|
||||
MembershipActionType.RestartDelayedEvent,
|
||||
this.membershipKeepAlivePeriod,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
const repeatActionType = MembershipActionType.SendMainDelayedEvent;
|
||||
// Don't do any other delayed event work if its not supported.
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) return {};
|
||||
|
||||
if (this.manageMaxDelayExceededSituation(e)) {
|
||||
return createInsertActionUpdate(repeatActionType);
|
||||
}
|
||||
const update = this.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
|
||||
if (update) return update;
|
||||
|
||||
throw Error("Could not send delayed event, even though delayed events are supported. " + e);
|
||||
});
|
||||
}
|
||||
|
||||
private async sendScheduledDelayedLeaveEventOrFallbackToSendLeaveEvent(delayId: string): Promise<ActionUpdate> {
|
||||
return await this.client
|
||||
._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Send)
|
||||
@@ -887,9 +843,8 @@ export class MembershipManager implements IMembershipManager {
|
||||
if (actions.length === 1) {
|
||||
const { type } = actions[0];
|
||||
switch (type) {
|
||||
case MembershipActionType.SendFirstDelayedEvent:
|
||||
case MembershipActionType.SendDelayedEvent:
|
||||
case MembershipActionType.SendJoinEvent:
|
||||
case MembershipActionType.SendMainDelayedEvent:
|
||||
return Status.Connecting;
|
||||
case MembershipActionType.UpdateExpiry: // where no delayed events
|
||||
return Status.Connected;
|
||||
@@ -904,7 +859,7 @@ export class MembershipManager implements IMembershipManager {
|
||||
// normal state for connected with delayed events
|
||||
if (
|
||||
(types.includes(MembershipActionType.RestartDelayedEvent) ||
|
||||
types.includes(MembershipActionType.SendMainDelayedEvent)) &&
|
||||
(types.includes(MembershipActionType.SendDelayedEvent) && this.state.hasMemberStateEvent)) &&
|
||||
types.includes(MembershipActionType.UpdateExpiry)
|
||||
) {
|
||||
return Status.Connected;
|
||||
|
||||
@@ -73,7 +73,7 @@ export class ActionScheduler {
|
||||
return;
|
||||
}
|
||||
this.running = true;
|
||||
this._actions = [{ ts: Date.now(), type: MembershipActionType.SendFirstDelayedEvent }];
|
||||
this._actions = [{ ts: Date.now(), type: MembershipActionType.SendDelayedEvent }];
|
||||
try {
|
||||
while (this._actions.length > 0) {
|
||||
// Sort so next (smallest ts) action is at the beginning
|
||||
@@ -98,7 +98,7 @@ export class ActionScheduler {
|
||||
`\nDate.now: "${Date.now()}`,
|
||||
);
|
||||
try {
|
||||
// `this.wakeup` can also be called and sets the `wakupUpdate` object while we are in the handler.
|
||||
// `this.wakeup` can also be called and sets the `wakeupUpdate` object while we are in the handler.
|
||||
handlerResult = await this.membershipLoopHandler(nextAction.type as MembershipActionType);
|
||||
} catch (e) {
|
||||
throw Error(`The MembershipManager shut down because of the end condition: ${e}`);
|
||||
@@ -125,7 +125,7 @@ export class ActionScheduler {
|
||||
}
|
||||
|
||||
public initiateJoin(): void {
|
||||
this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendFirstDelayedEvent }] });
|
||||
this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendDelayedEvent }] });
|
||||
}
|
||||
public initiateLeave(): void {
|
||||
this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendScheduledDelayedLeaveEvent }] });
|
||||
|
||||
@@ -20,3 +20,4 @@ export * from "./LivekitFocus.ts";
|
||||
export * from "./MatrixRTCSession.ts";
|
||||
export * from "./MatrixRTCSessionManager.ts";
|
||||
export type * from "./types.ts";
|
||||
export { Status, MembershipManagerEvent } from "./types.ts";
|
||||
|
||||
+84
-1
@@ -13,7 +13,10 @@ 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 IMentions } from "../matrix.ts";
|
||||
import type { IMentions } from "../matrix.ts";
|
||||
import type { CallMembership } from "./CallMembership.ts";
|
||||
import type { Focus } from "./focus.ts";
|
||||
|
||||
export interface EncryptionKeyEntry {
|
||||
index: number;
|
||||
key: string;
|
||||
@@ -34,3 +37,83 @@ export interface ICallNotifyContent {
|
||||
"notify_type": CallNotifyType;
|
||||
"call_id": string;
|
||||
}
|
||||
|
||||
export enum Status {
|
||||
Disconnected = "Disconnected",
|
||||
Connecting = "Connecting",
|
||||
ConnectingFailed = "ConnectingFailed",
|
||||
Connected = "Connected",
|
||||
Reconnecting = "Reconnecting",
|
||||
Disconnecting = "Disconnecting",
|
||||
Stuck = "Stuck",
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
export enum MembershipManagerEvent {
|
||||
StatusChanged = "StatusChanged",
|
||||
}
|
||||
|
||||
export type MembershipManagerEventHandlerMap = {
|
||||
[MembershipManagerEvent.StatusChanged]: (prefStatus: Status, newStatus: Status) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* This interface defines what a MembershipManager uses and exposes.
|
||||
* This interface is what we use to write tests and allows changing the actual implementation
|
||||
* without breaking tests because of some internal method renaming.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface IMembershipManager {
|
||||
/**
|
||||
* If we are trying to join, or have successfully joined the session.
|
||||
* It does not reflect if the room state is already configured to represent us being joined.
|
||||
* It only means that the Manager should be trying to connect or to disconnect running.
|
||||
* The Manager is still running right after isJoined becomes false to send the disconnect events.
|
||||
* @returns true if we intend to be participating in the MatrixRTC session
|
||||
* @deprecated This name is confusing and replaced by `isActivated()`. (Returns the same as `isActivated()`)
|
||||
*/
|
||||
isJoined(): boolean;
|
||||
/**
|
||||
* If the manager is activated. This means it tries to do its job to join the call, resend state events...
|
||||
* It does not imply that the room state is already configured to represent being joined.
|
||||
* It means that the Manager tries to connect or is connected. ("the manager is still active")
|
||||
* Once `leave()` is called the manager is not activated anymore but still running until `leave()` resolves.
|
||||
* @returns `true` if we intend to be participating in the MatrixRTC session
|
||||
*/
|
||||
isActivated(): boolean;
|
||||
/**
|
||||
* Get the actual connection status of the manager.
|
||||
*/
|
||||
get status(): Status;
|
||||
/**
|
||||
* The current status while the manager is activated
|
||||
*/
|
||||
/**
|
||||
* Start sending all necessary events to make this user participate in the RTC session.
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
* @param fociActive the active focus to use in the joined RTC membership event.
|
||||
* @throws can throw if it exceeds a configured maximum retry.
|
||||
*/
|
||||
join(fociPreferred: Focus[], fociActive?: Focus, onError?: (error: unknown) => void): void;
|
||||
/**
|
||||
* Send all necessary events to make this user leave the RTC session.
|
||||
* @param timeout the maximum duration in ms until the promise is forced to resolve.
|
||||
* @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): Promise<boolean>;
|
||||
/**
|
||||
* Call this if the MatrixRTC session members have changed.
|
||||
*/
|
||||
onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void>;
|
||||
/**
|
||||
* The used active focus in the currently joined session.
|
||||
* @returns the used active focus in the currently joined session or undefined if not joined.
|
||||
*/
|
||||
getActiveFocus(): Focus | undefined;
|
||||
|
||||
// TypedEventEmitter methods:
|
||||
on(event: MembershipManagerEvent.StatusChanged, listener: (oldStatus: Status, newStatus: Status) => void): this;
|
||||
off(event: MembershipManagerEvent.StatusChanged, listener: (oldStatus: Status, newStatus: Status) => void): this;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,40 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A stripped m.room.member event which contains the key renderable fields from the event,
|
||||
* sent only in simplified sliding sync (not `/v3/sync`).
|
||||
* This is very similar to MSC4186Hero from sliding-sync.ts but an internal format with
|
||||
* camelCase rather than underscores.
|
||||
*/
|
||||
export type Hero = {
|
||||
userId: string;
|
||||
displayName?: string;
|
||||
avatarUrl?: string;
|
||||
/**
|
||||
* If true, the hero is from an MSC4186 summary, in which case `displayName` and `avatarUrl` will
|
||||
* have been set by the server if available. If false, the `Hero` has been constructed from a `/v3/sync` response,
|
||||
* so these fields will always be undefined.
|
||||
*/
|
||||
fromMSC4186: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* High level summary information for a room, as returned by `/v3/sync`.
|
||||
*/
|
||||
export interface IRoomSummary {
|
||||
/**
|
||||
* The room heroes: a selected set of members that can be used when summarising or
|
||||
* generating a name for a room. List of user IDs.
|
||||
*/
|
||||
"m.heroes": string[];
|
||||
/**
|
||||
* The number of joined members in the room.
|
||||
*/
|
||||
"m.joined_member_count"?: number;
|
||||
/**
|
||||
* The number of invited members in the room.
|
||||
*/
|
||||
"m.invited_member_count"?: number;
|
||||
}
|
||||
|
||||
|
||||
+133
-23
@@ -35,7 +35,7 @@ import {
|
||||
} from "./event.ts";
|
||||
import { EventStatus } from "./event-status.ts";
|
||||
import { RoomMember } from "./room-member.ts";
|
||||
import { type IRoomSummary, RoomSummary } from "./room-summary.ts";
|
||||
import { type IRoomSummary, type Hero, RoomSummary } from "./room-summary.ts";
|
||||
import { logger } from "../logger.ts";
|
||||
import { TypedReEmitter } from "../ReEmitter.ts";
|
||||
import {
|
||||
@@ -77,6 +77,7 @@ import { compareEventOrdering } from "./compare-event-ordering.ts";
|
||||
import * as utils from "../utils.ts";
|
||||
import { KnownMembership, type Membership } from "../@types/membership.ts";
|
||||
import { type Capabilities, type IRoomVersionsCapability, RoomVersionStability } from "../serverCapabilities.ts";
|
||||
import { type MSC4186Hero } from "../sliding-sync.ts";
|
||||
|
||||
// These constants are used as sane defaults when the homeserver doesn't support
|
||||
// the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be
|
||||
@@ -335,6 +336,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
public readonly reEmitter: TypedReEmitter<RoomEmittedEvents, RoomEventHandlerMap>;
|
||||
private txnToEvent: Map<string, MatrixEvent> = new Map(); // Pending in-flight requests { string: MatrixEvent }
|
||||
private notificationCounts: NotificationCount = {};
|
||||
private bumpStamp: number | undefined = undefined;
|
||||
private readonly threadNotifications = new Map<string, NotificationCount>();
|
||||
public readonly cachedThreadReadReceipts = new Map<string, CachedReceiptStructure[]>();
|
||||
// Useful to know at what point the current user has started using threads in this room
|
||||
@@ -361,7 +363,16 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
// read by megolm via getter; boolean value - null indicates "use global value"
|
||||
private blacklistUnverifiedDevices?: boolean;
|
||||
private selfMembership?: Membership;
|
||||
private summaryHeroes: string[] | null = null;
|
||||
/**
|
||||
* A `Hero` is a stripped `m.room.member` event which contains the important renderable fields from the event.
|
||||
*
|
||||
* It is used in MSC4186 (Simplified Sliding Sync) as a replacement for the old `summary` field.
|
||||
*
|
||||
* When we are doing old-style (`/v3/sync`) sync, we simulate the SSS behaviour by constructing
|
||||
* a `Hero` object based on the user id we get from the summary. Obviously, in that case,
|
||||
* the `Hero` will lack a `displayName` or `avatarUrl`.
|
||||
*/
|
||||
private heroes: Hero[] | null = null;
|
||||
// flags to stop logspam about missing m.room.create events
|
||||
private getTypeWarning = false;
|
||||
private getVersionWarning = false;
|
||||
@@ -879,7 +890,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
// fall back to summary information
|
||||
const memberCount = this.getInvitedAndJoinedMemberCount();
|
||||
if (memberCount === 2) {
|
||||
return this.summaryHeroes?.[0];
|
||||
return this.heroes?.[0]?.userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -897,8 +908,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
}
|
||||
}
|
||||
// Remember, we're assuming this room is a DM, so returning the first member we find should be fine
|
||||
if (Array.isArray(this.summaryHeroes) && this.summaryHeroes.length) {
|
||||
return this.summaryHeroes[0];
|
||||
if (Array.isArray(this.heroes) && this.heroes.length) {
|
||||
return this.heroes[0].userId;
|
||||
}
|
||||
const members = this.currentState.getMembers();
|
||||
const anyMember = members.find((m) => m.userId !== this.myUserId);
|
||||
@@ -940,12 +951,45 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
if (nonFunctionalMemberCount > 2) return;
|
||||
|
||||
// Prefer the list of heroes, if present. It should only include the single other user in the DM.
|
||||
const nonFunctionalHeroes = this.summaryHeroes?.filter((h) => !functionalMembers.includes(h));
|
||||
const nonFunctionalHeroes = this.heroes?.filter((h) => !functionalMembers.includes(h.userId));
|
||||
const hasHeroes = Array.isArray(nonFunctionalHeroes) && nonFunctionalHeroes.length;
|
||||
if (hasHeroes) {
|
||||
// use first hero that has a display name or avatar url, or whose user ID
|
||||
// can be looked up as a member of the room
|
||||
for (const hero of nonFunctionalHeroes) {
|
||||
// If the hero was from a legacy sync (`/v3/sync`), we will need to look the user ID up in the room
|
||||
// the display name and avatar URL will not be set.
|
||||
if (!hero.fromMSC4186) {
|
||||
// attempt to look up renderable fields from the m.room.member event if it exists
|
||||
const member = this.getMember(hero.userId);
|
||||
if (member) {
|
||||
return member;
|
||||
}
|
||||
} else {
|
||||
// use the Hero supplied values for the room member.
|
||||
// TODO: It's unfortunate that this function, which clearly only cares about the
|
||||
// avatar url, returns the entire RoomMember event. We need to fake an event
|
||||
// to meet this API shape.
|
||||
const heroMember = new RoomMember(this.roomId, hero.userId);
|
||||
// set the display name and avatar url
|
||||
heroMember.setMembershipEvent(
|
||||
new MatrixEvent({
|
||||
// ensure it's unique even if we hit the same millisecond
|
||||
event_id: "$" + this.roomId + hero.userId + new Date().getTime(),
|
||||
type: EventType.RoomMember,
|
||||
state_key: hero.userId,
|
||||
content: {
|
||||
displayname: hero.displayName,
|
||||
avatar_url: hero.avatarUrl,
|
||||
},
|
||||
}),
|
||||
);
|
||||
return heroMember;
|
||||
}
|
||||
}
|
||||
const availableMember = nonFunctionalHeroes
|
||||
.map((userId) => {
|
||||
return this.getMember(userId);
|
||||
.map((hero) => {
|
||||
return this.getMember(hero.userId);
|
||||
})
|
||||
.find((member) => !!member);
|
||||
if (availableMember) {
|
||||
@@ -970,8 +1014,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
// trust and try falling back to a hero, creating a one-off member for it
|
||||
if (hasHeroes) {
|
||||
const availableUser = nonFunctionalHeroes
|
||||
.map((userId) => {
|
||||
return this.client.getUser(userId);
|
||||
.map((hero) => {
|
||||
return this.client.getUser(hero.userId);
|
||||
})
|
||||
.find((user) => !!user);
|
||||
if (availableUser) {
|
||||
@@ -1602,6 +1646,24 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
this.emit(RoomEvent.UnreadNotifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bump stamp for this room. This can be used for sorting rooms when the timeline
|
||||
* entries are unknown. Used in MSC4186: Simplified Sliding Sync.
|
||||
* @param bumpStamp The bump_stamp value from the server
|
||||
*/
|
||||
public setBumpStamp(bumpStamp: number): void {
|
||||
this.bumpStamp = bumpStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bump stamp for this room. This can be used for sorting rooms when the timeline
|
||||
* entries are unknown. Used in MSC4186: Simplified Sliding Sync.
|
||||
* @returns The bump stamp for the room, if it exists.
|
||||
*/
|
||||
public getBumpStamp(): number | undefined {
|
||||
return this.bumpStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set one of the notification counts for this room
|
||||
* @param type - The type of notification count to set.
|
||||
@@ -1616,8 +1678,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
return this.setUnreadNotificationCount(type, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a legacy room summary (/v3/sync as opposed to MSC4186) and updates the room with it.
|
||||
*
|
||||
* @param summary - The room summary to update the room with
|
||||
*/
|
||||
public setSummary(summary: IRoomSummary): void {
|
||||
const heroes = summary["m.heroes"];
|
||||
const heroes = summary["m.heroes"]?.map((h) => ({ userId: h, fromMSC4186: false }));
|
||||
const joinedCount = summary["m.joined_member_count"];
|
||||
const invitedCount = summary["m.invited_member_count"];
|
||||
if (Number.isInteger(joinedCount)) {
|
||||
@@ -1627,17 +1694,53 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
this.currentState.setInvitedMemberCount(invitedCount!);
|
||||
}
|
||||
if (Array.isArray(heroes)) {
|
||||
// be cautious about trusting server values,
|
||||
// and make sure heroes doesn't contain our own id
|
||||
// just to be sure
|
||||
this.summaryHeroes = heroes.filter((userId) => {
|
||||
return userId !== this.myUserId;
|
||||
// filter out ourselves just in case
|
||||
this.heroes = heroes.filter((h) => {
|
||||
return h.userId != this.myUserId;
|
||||
});
|
||||
}
|
||||
|
||||
this.emit(RoomEvent.Summary, summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes information from the MSC4186 room summary and updates the room with it.
|
||||
*
|
||||
* @param heroes - The room's hero members
|
||||
* @param joinedCount - The number of joined members
|
||||
* @param invitedCount - The number of invited members
|
||||
*/
|
||||
public setMSC4186SummaryData(
|
||||
heroes: MSC4186Hero[] | undefined,
|
||||
joinedCount: number | undefined,
|
||||
invitedCount: number | undefined,
|
||||
): void {
|
||||
if (heroes) {
|
||||
this.heroes = heroes
|
||||
.filter((h) => h.user_id !== this.myUserId)
|
||||
.map((h) => ({
|
||||
userId: h.user_id,
|
||||
displayName: h.displayname,
|
||||
avatarUrl: h.avatar_url,
|
||||
fromMSC4186: true,
|
||||
}));
|
||||
}
|
||||
if (joinedCount !== undefined && Number.isInteger(joinedCount)) {
|
||||
this.currentState.setJoinedMemberCount(joinedCount);
|
||||
}
|
||||
if (invitedCount !== undefined && Number.isInteger(invitedCount)) {
|
||||
this.currentState.setInvitedMemberCount(invitedCount);
|
||||
}
|
||||
|
||||
// Construct a summary object to emit as the event wants the info in a single object
|
||||
// more like old-style (/v3/sync) summaries.
|
||||
this.emit(RoomEvent.Summary, {
|
||||
"m.heroes": this.heroes ? this.heroes.map((h) => h.userId) : [],
|
||||
"m.joined_member_count": joinedCount,
|
||||
"m.invited_member_count": invitedCount,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to send encrypted messages to devices within this room.
|
||||
* @param value - true to blacklist unverified devices, null
|
||||
@@ -3459,18 +3562,25 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
// get service members (e.g. helper bots) for exclusion
|
||||
const excludedUserIds = this.getFunctionalMembers();
|
||||
|
||||
// get members that are NOT ourselves and are actually in the room.
|
||||
// get members from heroes that are NOT ourselves
|
||||
let otherNames: string[] = [];
|
||||
if (this.summaryHeroes) {
|
||||
// if we have a summary, the member state events should be in the room state
|
||||
this.summaryHeroes.forEach((userId) => {
|
||||
if (this.heroes) {
|
||||
// if we have heroes, use those as the names
|
||||
this.heroes.forEach((hero) => {
|
||||
// filter service members
|
||||
if (excludedUserIds.includes(userId)) {
|
||||
if (excludedUserIds.includes(hero.userId)) {
|
||||
inviteJoinCount--;
|
||||
return;
|
||||
}
|
||||
const member = this.getMember(userId);
|
||||
otherNames.push(member ? member.name : userId);
|
||||
// If the hero has a display name, use that.
|
||||
// Otherwise, look their user ID up in the membership and use
|
||||
// the name from there, or the user ID as a last resort.
|
||||
if (hero.displayName) {
|
||||
otherNames.push(hero.displayName);
|
||||
} else {
|
||||
const member = this.getMember(hero.userId);
|
||||
otherNames.push(member ? member.name : hero.userId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let otherMembers = this.currentState.getMembers().filter((m) => {
|
||||
|
||||
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type IdTokenClaims, OidcClient, WebStorageStateStore } from "oidc-client-ts";
|
||||
import { type IdTokenClaims, OidcClient, WebStorageStateStore, ErrorResponse } from "oidc-client-ts";
|
||||
|
||||
import { type AccessTokens } from "../http-api/index.ts";
|
||||
import { type AccessTokens, TokenRefreshLogoutError } from "../http-api/index.ts";
|
||||
import { generateScope } from "./authorize.ts";
|
||||
import { discoverAndValidateOIDCIssuerWellKnown } from "./discovery.ts";
|
||||
import { logger } from "../logger.ts";
|
||||
@@ -104,6 +104,12 @@ export class OidcTokenRefresher {
|
||||
try {
|
||||
const tokens = await this.inflightRefreshRequest;
|
||||
return tokens;
|
||||
} catch (e) {
|
||||
// If we encounter an OIDC error then signal that it should cause a logout by upgrading it to a TokenRefreshLogoutError
|
||||
if (e instanceof ErrorResponse) {
|
||||
throw new TokenRefreshLogoutError(e);
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
this.inflightRefreshRequest = undefined;
|
||||
}
|
||||
|
||||
+25
-16
@@ -308,6 +308,28 @@ export class PushProcessor {
|
||||
return newRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RegExp object for the given glob pattern with a single capture group around the pattern itself, caching the result.
|
||||
* No cache invalidation is present currently,
|
||||
* as this will be inherently bounded to the size of the user's own push rules.
|
||||
* @param pattern - the glob pattern to convert to a RegExp
|
||||
* @param alignToWordBoundary - whether to align the pattern to word boundaries,
|
||||
* as specified for `content.body` matches, will use lookaround assertions to ensure the match only includes the pattern
|
||||
* @param flags - the flags to pass to the RegExp constructor, defaults to case-insensitive
|
||||
*/
|
||||
public static getPushRuleGlobRegex(pattern: string, alignToWordBoundary = false, flags = "i"): RegExp {
|
||||
const [prefix, suffix] = alignToWordBoundary ? ["(?<=^|\\W)", "(?=\\W|$)"] : ["^", "$"];
|
||||
const cacheKey = `${alignToWordBoundary}-${flags}-${pattern}`;
|
||||
|
||||
if (!PushProcessor.cachedGlobToRegex[cacheKey]) {
|
||||
PushProcessor.cachedGlobToRegex[cacheKey] = new RegExp(
|
||||
prefix + "(" + globToRegexp(pattern) + ")" + suffix,
|
||||
flags,
|
||||
);
|
||||
}
|
||||
return PushProcessor.cachedGlobToRegex[cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-caches the parsed keys for push rules and cleans out any obsolete cache
|
||||
* entries. Should be called after push rules are updated.
|
||||
@@ -567,11 +589,9 @@ export class PushProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regex =
|
||||
cond.key === "content.body"
|
||||
? this.createCachedRegex("(^|\\W)", cond.pattern, "(\\W|$)")
|
||||
: this.createCachedRegex("^", cond.pattern, "$");
|
||||
|
||||
// Align to word boundary on `content.body` matches, whole string otherwise
|
||||
// https://spec.matrix.org/v1.13/client-server-api/#conditions-1
|
||||
const regex = PushProcessor.getPushRuleGlobRegex(cond.pattern, cond.key === "content.body");
|
||||
return !!val.match(regex);
|
||||
}
|
||||
|
||||
@@ -621,17 +641,6 @@ export class PushProcessor {
|
||||
);
|
||||
}
|
||||
|
||||
private createCachedRegex(prefix: string, glob: string, suffix: string): RegExp {
|
||||
if (PushProcessor.cachedGlobToRegex[glob]) {
|
||||
return PushProcessor.cachedGlobToRegex[glob];
|
||||
}
|
||||
PushProcessor.cachedGlobToRegex[glob] = new RegExp(
|
||||
prefix + globToRegexp(glob) + suffix,
|
||||
"i", // Case insensitive
|
||||
);
|
||||
return PushProcessor.cachedGlobToRegex[glob];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the key into the separate fields to search by splitting on
|
||||
* unescaped ".", and then removing any escape characters.
|
||||
|
||||
@@ -219,6 +219,12 @@ export class OutgoingRequestProcessor {
|
||||
|
||||
// we use the full prefix
|
||||
prefix: "",
|
||||
|
||||
// We set a timeout of 60 seconds to guard against requests getting stuck forever and wedging the
|
||||
// request loop (cf https://github.com/element-hq/element-web/issues/29534).
|
||||
//
|
||||
// (XXX: should we do this in the whole of the js-sdk?)
|
||||
localTimeoutMs: 60000,
|
||||
};
|
||||
|
||||
return await this.http.authedRequest<string>(method, path, queryParams, body, opts);
|
||||
|
||||
@@ -1635,7 +1635,6 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
||||
|
||||
/** called by the sync loop after processing each sync.
|
||||
*
|
||||
* TODO: figure out something equivalent for sliding sync.
|
||||
*
|
||||
* @param syncState - information on the completed sync.
|
||||
*/
|
||||
@@ -1647,6 +1646,13 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi#markAllTrackedUsersAsDirty}.
|
||||
*/
|
||||
public async markAllTrackedUsersAsDirty(): Promise<void> {
|
||||
await this.olmMachine.markAllTrackedUsersAsDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming m.key.verification.request event, received either in-room or in a to-device message.
|
||||
*
|
||||
|
||||
+28
-28
@@ -82,9 +82,16 @@ class ExtensionE2EE implements Extension<ExtensionE2EERequest, ExtensionE2EEResp
|
||||
return ExtensionState.PreProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): ExtensionE2EERequest | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined;
|
||||
public async onRequest(isInitial: boolean): Promise<ExtensionE2EERequest> {
|
||||
if (isInitial) {
|
||||
// In SSS, the `?pos=` contains the stream position for device list updates.
|
||||
// If we do not have a `?pos=` (e.g because we forgot it, or because the server
|
||||
// invalidated our connection) then we MUST invlaidate all device lists because
|
||||
// the server will not tell us the delta. This will then cause UTDs as we will fail
|
||||
// to encrypt for new devices. This is an expensive call, so we should
|
||||
// really really remember `?pos=` wherever possible.
|
||||
logger.log("ExtensionE2EE: invalidating all device lists due to missing 'pos'");
|
||||
await this.crypto.markAllTrackedUsersAsDirty();
|
||||
}
|
||||
return {
|
||||
enabled: true, // this is sticky so only send it on the initial request
|
||||
@@ -134,15 +141,12 @@ class ExtensionToDevice implements Extension<ExtensionToDeviceRequest, Extension
|
||||
return ExtensionState.PreProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): ExtensionToDeviceRequest {
|
||||
const extReq: ExtensionToDeviceRequest = {
|
||||
public async onRequest(isInitial: boolean): Promise<ExtensionToDeviceRequest> {
|
||||
return {
|
||||
since: this.nextBatch !== null ? this.nextBatch : undefined,
|
||||
limit: 100,
|
||||
enabled: true,
|
||||
};
|
||||
if (isInitial) {
|
||||
extReq["limit"] = 100;
|
||||
extReq["enabled"] = true;
|
||||
}
|
||||
return extReq;
|
||||
}
|
||||
|
||||
public async onResponse(data: ExtensionToDeviceResponse): Promise<void> {
|
||||
@@ -216,10 +220,7 @@ class ExtensionAccountData implements Extension<ExtensionAccountDataRequest, Ext
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): ExtensionAccountDataRequest | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined;
|
||||
}
|
||||
public async onRequest(isInitial: boolean): Promise<ExtensionAccountDataRequest> {
|
||||
return {
|
||||
enabled: true,
|
||||
};
|
||||
@@ -286,10 +287,7 @@ class ExtensionTyping implements Extension<ExtensionTypingRequest, ExtensionTypi
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): ExtensionTypingRequest | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||
}
|
||||
public async onRequest(isInitial: boolean): Promise<ExtensionTypingRequest> {
|
||||
return {
|
||||
enabled: true,
|
||||
};
|
||||
@@ -325,13 +323,10 @@ class ExtensionReceipts implements Extension<ExtensionReceiptsRequest, Extension
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): ExtensionReceiptsRequest | undefined {
|
||||
if (isInitial) {
|
||||
return {
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||
public async onRequest(isInitial: boolean): Promise<ExtensionReceiptsRequest> {
|
||||
return {
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async onResponse(data: ExtensionReceiptsResponse): Promise<void> {
|
||||
@@ -442,6 +437,7 @@ export class SlidingSyncSdk {
|
||||
}
|
||||
} else {
|
||||
this.failCount = 0;
|
||||
logger.log(`SlidingSyncState.RequestFinished with ${Object.keys(resp?.rooms || []).length} rooms`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -580,7 +576,7 @@ export class SlidingSyncSdk {
|
||||
|
||||
// TODO: handle threaded / beacon events
|
||||
|
||||
if (roomData.initial) {
|
||||
if (roomData.limited || roomData.initial) {
|
||||
// we should not know about any of these timeline entries if this is a genuinely new room.
|
||||
// If we do, then we've effectively done scrollback (e.g requesting timeline_limit: 1 for
|
||||
// this room, then timeline_limit: 50).
|
||||
@@ -637,6 +633,9 @@ export class SlidingSyncSdk {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, roomData.highlight_count);
|
||||
}
|
||||
}
|
||||
if (roomData.bump_stamp) {
|
||||
room.setBumpStamp(roomData.bump_stamp);
|
||||
}
|
||||
|
||||
if (Number.isInteger(roomData.invited_count)) {
|
||||
room.currentState.setInvitedMemberCount(roomData.invited_count!);
|
||||
@@ -656,11 +655,10 @@ export class SlidingSyncSdk {
|
||||
inviteStateEvents.forEach((e) => {
|
||||
this.client.emit(ClientEvent.Event, e);
|
||||
});
|
||||
room.updateMyMembership(KnownMembership.Invite);
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomData.initial) {
|
||||
if (roomData.limited) {
|
||||
// set the back-pagination token. Do this *before* adding any
|
||||
// events so that clients can start back-paginating.
|
||||
room.getLiveTimeline().setPaginationToken(roomData.prev_batch ?? null, EventTimeline.BACKWARDS);
|
||||
@@ -728,6 +726,8 @@ export class SlidingSyncSdk {
|
||||
// synchronous execution prior to emitting SlidingSyncState.Complete
|
||||
room.updateMyMembership(KnownMembership.Join);
|
||||
|
||||
room.setMSC4186SummaryData(roomData.heroes, roomData.joined_count, roomData.invited_count);
|
||||
|
||||
room.recalculate();
|
||||
if (roomData.initial) {
|
||||
client.store.storeRoom(room);
|
||||
|
||||
+38
-329
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2022-2024 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.
|
||||
@@ -18,7 +18,7 @@ import { logger } from "./logger.ts";
|
||||
import { type MatrixClient } from "./client.ts";
|
||||
import { type IRoomEvent, type IStateEvent } from "./sync-accumulator.ts";
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter.ts";
|
||||
import { sleep, type IDeferred, defer } from "./utils.ts";
|
||||
import { sleep } from "./utils.ts";
|
||||
import { type HTTPError } from "./http-api/index.ts";
|
||||
|
||||
// /sync requests allow you to set a timeout= but the request may continue
|
||||
@@ -82,10 +82,23 @@ export interface MSC3575SlidingSyncRequest {
|
||||
clientTimeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* New format of hero introduced in MSC4186 with display name and avatar URL
|
||||
* in addition to just user_id (as it is on the wire, with underscores)
|
||||
* as opposed to Hero in room-summary.ts which has fields in camelCase
|
||||
* (and also a flag to note what format the hero came from).
|
||||
*/
|
||||
export interface MSC4186Hero {
|
||||
user_id: string;
|
||||
displayname?: string;
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
export interface MSC3575RoomData {
|
||||
name: string;
|
||||
required_state: IStateEvent[];
|
||||
timeline: (IRoomEvent | IStateEvent)[];
|
||||
heroes?: MSC4186Hero[];
|
||||
notification_count?: number;
|
||||
highlight_count?: number;
|
||||
joined_count?: number;
|
||||
@@ -96,41 +109,13 @@ export interface MSC3575RoomData {
|
||||
is_dm?: boolean;
|
||||
prev_batch?: string;
|
||||
num_live?: number;
|
||||
bump_stamp?: number;
|
||||
}
|
||||
|
||||
interface ListResponse {
|
||||
count: number;
|
||||
ops: Operation[];
|
||||
}
|
||||
|
||||
interface BaseOperation {
|
||||
op: string;
|
||||
}
|
||||
|
||||
interface DeleteOperation extends BaseOperation {
|
||||
op: "DELETE";
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface InsertOperation extends BaseOperation {
|
||||
op: "INSERT";
|
||||
index: number;
|
||||
room_id: string;
|
||||
}
|
||||
|
||||
interface InvalidateOperation extends BaseOperation {
|
||||
op: "INVALIDATE";
|
||||
range: [number, number];
|
||||
}
|
||||
|
||||
interface SyncOperation extends BaseOperation {
|
||||
op: "SYNC";
|
||||
range: [number, number];
|
||||
room_ids: string[];
|
||||
}
|
||||
|
||||
type Operation = DeleteOperation | InsertOperation | InvalidateOperation | SyncOperation;
|
||||
|
||||
/**
|
||||
* A complete Sliding Sync response
|
||||
*/
|
||||
@@ -163,7 +148,6 @@ class SlidingList {
|
||||
private isModified?: boolean;
|
||||
|
||||
// returned data
|
||||
public roomIndexToRoomId: Record<number, string> = {};
|
||||
public joinedCount = 0;
|
||||
|
||||
/**
|
||||
@@ -204,9 +188,6 @@ class SlidingList {
|
||||
// reset values as the join count may be very different (if filters changed) including the rooms
|
||||
// (e.g. sort orders or sliding window ranges changed)
|
||||
|
||||
// the constantly changing sliding window ranges. Not an array for performance reasons
|
||||
// E.g. tracking ranges 0-99, 500-599, we don't want to have a 600 element array
|
||||
this.roomIndexToRoomId = {};
|
||||
// the total number of joined rooms according to the server, always >= len(roomIndexToRoomId)
|
||||
this.joinedCount = 0;
|
||||
}
|
||||
@@ -226,26 +207,6 @@ class SlidingList {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given index is within the list range. This is required even though the /sync API
|
||||
* provides explicit updates with index positions because of the following situation:
|
||||
* 0 1 2 3 4 5 6 7 8 indexes
|
||||
* a b c d e f COMMANDS: SYNC 0 2 a b c; SYNC 6 8 d e f;
|
||||
* a b c d _ f COMMAND: DELETE 7;
|
||||
* e a b c d f COMMAND: INSERT 0 e;
|
||||
* c=3 is wrong as we are not tracking it, ergo we need to see if `i` is in range else drop it
|
||||
* @param i - The index to check
|
||||
* @returns True if the index is within a sliding window
|
||||
*/
|
||||
public isIndexInRange(i: number): boolean {
|
||||
for (const r of this.list.ranges) {
|
||||
if (r[0] <= i && i <= r[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,10 +235,10 @@ export interface Extension<Req extends object, Res extends object> {
|
||||
/**
|
||||
* A function which is called when the request JSON is being formed.
|
||||
* Returns the data to insert under this key.
|
||||
* @param isInitial - True when this is part of the initial request (send sticky params)
|
||||
* @param isInitial - True when this is part of the initial request.
|
||||
* @returns The request JSON to send.
|
||||
*/
|
||||
onRequest(isInitial: boolean): Req | undefined;
|
||||
onRequest(isInitial: boolean): Promise<Req>;
|
||||
/**
|
||||
* A function which is called when there is response JSON under this extension.
|
||||
* @param data - The response JSON under the extension name.
|
||||
@@ -295,12 +256,10 @@ export interface Extension<Req extends object, Res extends object> {
|
||||
* of information when processing sync responses.
|
||||
* - RoomData: concerns rooms, useful for SlidingSyncSdk to update its knowledge of rooms.
|
||||
* - Lifecycle: concerns callbacks at various well-defined points in the sync process.
|
||||
* - List: concerns lists, useful for UI layers to re-render room lists.
|
||||
* Specifically, the order of event invocation is:
|
||||
* - Lifecycle (state=RequestFinished)
|
||||
* - RoomData (N times)
|
||||
* - Lifecycle (state=Complete)
|
||||
* - List (at most once per list)
|
||||
*/
|
||||
export enum SlidingSyncEvent {
|
||||
/**
|
||||
@@ -313,16 +272,9 @@ export enum SlidingSyncEvent {
|
||||
* - SlidingSyncState.RequestFinished: Fires after we receive a valid response but before the
|
||||
* response has been processed. Perform any pre-process steps here. If there was a problem syncing,
|
||||
* `err` will be set (e.g network errors).
|
||||
* - SlidingSyncState.Complete: Fires after all SlidingSyncEvent.RoomData have been fired but before
|
||||
* SlidingSyncEvent.List.
|
||||
* - SlidingSyncState.Complete: Fires after the response has been processed.
|
||||
*/
|
||||
Lifecycle = "SlidingSync.Lifecycle",
|
||||
/**
|
||||
* This event fires whenever there has been a change to this list index. It fires exactly once
|
||||
* per list, even if there were multiple operations for the list.
|
||||
* It fires AFTER Lifecycle and RoomData events.
|
||||
*/
|
||||
List = "SlidingSync.List",
|
||||
}
|
||||
|
||||
export type SlidingSyncEventHandlerMap = {
|
||||
@@ -332,7 +284,6 @@ export type SlidingSyncEventHandlerMap = {
|
||||
resp: MSC3575SlidingSyncResponse | null,
|
||||
err?: Error,
|
||||
) => void;
|
||||
[SlidingSyncEvent.List]: (listKey: string, joinedCount: number, roomIndexToRoomId: Record<number, string>) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -347,11 +298,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
private terminated = false;
|
||||
// flag set when resend() is called because we cannot rely on detecting AbortError in JS SDK :(
|
||||
private needsResend = false;
|
||||
// the txn_id to send with the next request.
|
||||
private txnId: string | null = null;
|
||||
// a list (in chronological order of when they were sent) of objects containing the txn ID and
|
||||
// a defer to resolve/reject depending on whether they were successfully sent or not.
|
||||
private txnIdDefers: (IDeferred<string> & { txnId: string })[] = [];
|
||||
// map of extension name to req/resp handler
|
||||
private extensions: Record<string, Extension<any, any>> = {};
|
||||
|
||||
@@ -426,14 +372,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* @param key - The list key
|
||||
* @returns The list data which contains the rooms in this list
|
||||
*/
|
||||
public getListData(key: string): { joinedCount: number; roomIndexToRoomId: Record<number, string> } | null {
|
||||
public getListData(key: string): { joinedCount: number } | null {
|
||||
const data = this.lists.get(key);
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
joinedCount: data.joinedCount,
|
||||
roomIndexToRoomId: Object.assign({}, data.roomIndexToRoomId),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -461,13 +406,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
||||
* immediately after sending, in which case the action will be applied in the subsequent request)
|
||||
*/
|
||||
public setListRanges(key: string, ranges: number[][]): Promise<string> {
|
||||
public setListRanges(key: string, ranges: number[][]): void {
|
||||
const list = this.lists.get(key);
|
||||
if (!list) {
|
||||
return Promise.reject(new Error("no list with key " + key));
|
||||
throw new Error("no list with key " + key);
|
||||
}
|
||||
list.updateListRange(ranges);
|
||||
return this.resend();
|
||||
this.resend();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -479,7 +424,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
||||
* immediately after sending, in which case the action will be applied in the subsequent request)
|
||||
*/
|
||||
public setList(key: string, list: MSC3575List): Promise<string> {
|
||||
public setList(key: string, list: MSC3575List): void {
|
||||
const existingList = this.lists.get(key);
|
||||
if (existingList) {
|
||||
existingList.replaceList(list);
|
||||
@@ -488,7 +433,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
this.lists.set(key, new SlidingList(list));
|
||||
}
|
||||
this.listModifiedCount += 1;
|
||||
return this.resend();
|
||||
this.resend();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -504,27 +449,21 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* /sync request to resend new subscriptions. If the /sync stream has not started, this will
|
||||
* prepare the room subscriptions for when start() is called.
|
||||
* @param s - The new desired room subscriptions.
|
||||
* @returns A promise which resolves to the transaction ID when it has been received down sync
|
||||
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
||||
* immediately after sending, in which case the action will be applied in the subsequent request)
|
||||
*/
|
||||
public modifyRoomSubscriptions(s: Set<string>): Promise<string> {
|
||||
public modifyRoomSubscriptions(s: Set<string>): void {
|
||||
this.desiredRoomSubscriptions = s;
|
||||
return this.resend();
|
||||
this.resend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify which events to retrieve for room subscriptions. Invalidates all room subscriptions
|
||||
* such that they will be sent up afresh.
|
||||
* @param rs - The new room subscription fields to fetch.
|
||||
* @returns A promise which resolves to the transaction ID when it has been received down sync
|
||||
* (or rejects with the transaction ID if the action was not applied e.g the request was cancelled
|
||||
* immediately after sending, in which case the action will be applied in the subsequent request)
|
||||
*/
|
||||
public modifyRoomSubscriptionInfo(rs: MSC3575RoomSubscription): Promise<string> {
|
||||
public modifyRoomSubscriptionInfo(rs: MSC3575RoomSubscription): void {
|
||||
this.roomSubscriptionInfo = rs;
|
||||
this.confirmedRoomSubscriptions = new Set<string>();
|
||||
return this.resend();
|
||||
this.resend();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -538,11 +477,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
this.extensions[ext.name()] = ext;
|
||||
}
|
||||
|
||||
private getExtensionRequest(isInitial: boolean): Record<string, object | undefined> {
|
||||
private async getExtensionRequest(isInitial: boolean): Promise<Record<string, object | undefined>> {
|
||||
const ext: Record<string, object | undefined> = {};
|
||||
Object.keys(this.extensions).forEach((extName) => {
|
||||
ext[extName] = this.extensions[extName].onRequest(isInitial);
|
||||
});
|
||||
for (const extName in this.extensions) {
|
||||
ext[extName] = await this.extensions[extName].onRequest(isInitial);
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
@@ -595,203 +534,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
this.emit(SlidingSyncEvent.Lifecycle, state, resp, err);
|
||||
}
|
||||
|
||||
private shiftRight(listKey: string, hi: number, low: number): void {
|
||||
const list = this.lists.get(listKey);
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
// l h
|
||||
// 0,1,2,3,4 <- before
|
||||
// 0,1,2,2,3 <- after, hi is deleted and low is duplicated
|
||||
for (let i = hi; i > low; i--) {
|
||||
if (list.isIndexInRange(i)) {
|
||||
list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private shiftLeft(listKey: string, hi: number, low: number): void {
|
||||
const list = this.lists.get(listKey);
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
// l h
|
||||
// 0,1,2,3,4 <- before
|
||||
// 0,1,3,4,4 <- after, low is deleted and hi is duplicated
|
||||
for (let i = low; i < hi; i++) {
|
||||
if (list.isIndexInRange(i)) {
|
||||
list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeEntry(listKey: string, index: number): void {
|
||||
const list = this.lists.get(listKey);
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
// work out the max index
|
||||
let max = -1;
|
||||
for (const n in list.roomIndexToRoomId) {
|
||||
if (Number(n) > max) {
|
||||
max = Number(n);
|
||||
}
|
||||
}
|
||||
if (max < 0 || index > max) {
|
||||
return;
|
||||
}
|
||||
// Everything higher than the gap needs to be shifted left.
|
||||
this.shiftLeft(listKey, max, index);
|
||||
delete list.roomIndexToRoomId[max];
|
||||
}
|
||||
|
||||
private addEntry(listKey: string, index: number): void {
|
||||
const list = this.lists.get(listKey);
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
// work out the max index
|
||||
let max = -1;
|
||||
for (const n in list.roomIndexToRoomId) {
|
||||
if (Number(n) > max) {
|
||||
max = Number(n);
|
||||
}
|
||||
}
|
||||
if (max < 0 || index > max) {
|
||||
return;
|
||||
}
|
||||
// Everything higher than the gap needs to be shifted right, +1 so we don't delete the highest element
|
||||
this.shiftRight(listKey, max + 1, index);
|
||||
}
|
||||
|
||||
private processListOps(list: ListResponse, listKey: string): void {
|
||||
let gapIndex = -1;
|
||||
const listData = this.lists.get(listKey);
|
||||
if (!listData) {
|
||||
return;
|
||||
}
|
||||
list.ops.forEach((op: Operation) => {
|
||||
if (!listData) {
|
||||
return;
|
||||
}
|
||||
switch (op.op) {
|
||||
case "DELETE": {
|
||||
logger.debug("DELETE", listKey, op.index, ";");
|
||||
delete listData.roomIndexToRoomId[op.index];
|
||||
if (gapIndex !== -1) {
|
||||
// we already have a DELETE operation to process, so process it.
|
||||
this.removeEntry(listKey, gapIndex);
|
||||
}
|
||||
gapIndex = op.index;
|
||||
break;
|
||||
}
|
||||
case "INSERT": {
|
||||
logger.debug("INSERT", listKey, op.index, op.room_id, ";");
|
||||
if (listData.roomIndexToRoomId[op.index]) {
|
||||
// something is in this space, shift items out of the way
|
||||
if (gapIndex < 0) {
|
||||
// we haven't been told where to shift from, so make way for a new room entry.
|
||||
this.addEntry(listKey, op.index);
|
||||
} else if (gapIndex > op.index) {
|
||||
// the gap is further down the list, shift every element to the right
|
||||
// starting at the gap so we can just shift each element in turn:
|
||||
// [A,B,C,_] gapIndex=3, op.index=0
|
||||
// [A,B,C,C] i=3
|
||||
// [A,B,B,C] i=2
|
||||
// [A,A,B,C] i=1
|
||||
// Terminate. We'll assign into op.index next.
|
||||
this.shiftRight(listKey, gapIndex, op.index);
|
||||
} else if (gapIndex < op.index) {
|
||||
// the gap is further up the list, shift every element to the left
|
||||
// starting at the gap so we can just shift each element in turn
|
||||
this.shiftLeft(listKey, op.index, gapIndex);
|
||||
}
|
||||
}
|
||||
// forget the gap, we don't need it anymore. This is outside the check for
|
||||
// a room being present in this index position because INSERTs always universally
|
||||
// forget the gap, not conditionally based on the presence of a room in the INSERT
|
||||
// position. Without this, DELETE 0; INSERT 0; would do the wrong thing.
|
||||
gapIndex = -1;
|
||||
listData.roomIndexToRoomId[op.index] = op.room_id;
|
||||
break;
|
||||
}
|
||||
case "INVALIDATE": {
|
||||
const startIndex = op.range[0];
|
||||
for (let i = startIndex; i <= op.range[1]; i++) {
|
||||
delete listData.roomIndexToRoomId[i];
|
||||
}
|
||||
logger.debug("INVALIDATE", listKey, op.range[0], op.range[1], ";");
|
||||
break;
|
||||
}
|
||||
case "SYNC": {
|
||||
const startIndex = op.range[0];
|
||||
for (let i = startIndex; i <= op.range[1]; i++) {
|
||||
const roomId = op.room_ids[i - startIndex];
|
||||
if (!roomId) {
|
||||
break; // we are at the end of list
|
||||
}
|
||||
listData.roomIndexToRoomId[i] = roomId;
|
||||
}
|
||||
logger.debug("SYNC", listKey, op.range[0], op.range[1], (op.room_ids || []).join(" "), ";");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (gapIndex !== -1) {
|
||||
// we already have a DELETE operation to process, so process it
|
||||
// Everything higher than the gap needs to be shifted left.
|
||||
this.removeEntry(listKey, gapIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend a Sliding Sync request. Used when something has changed in the request. Resolves with
|
||||
* the transaction ID of this request on success. Rejects with the transaction ID of this request
|
||||
* on failure.
|
||||
* Resend a Sliding Sync request. Used when something has changed in the request.
|
||||
*/
|
||||
public resend(): Promise<string> {
|
||||
if (this.needsResend && this.txnIdDefers.length > 0) {
|
||||
// we already have a resend queued, so just return the same promise
|
||||
return this.txnIdDefers[this.txnIdDefers.length - 1].promise;
|
||||
}
|
||||
public resend(): void {
|
||||
this.needsResend = true;
|
||||
this.txnId = this.client.makeTxnId();
|
||||
const d = defer<string>();
|
||||
this.txnIdDefers.push({
|
||||
...d,
|
||||
txnId: this.txnId,
|
||||
});
|
||||
this.abortController?.abort();
|
||||
this.abortController = new AbortController();
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
private resolveTransactionDefers(txnId?: string): void {
|
||||
if (!txnId) {
|
||||
return;
|
||||
}
|
||||
// find the matching index
|
||||
let txnIndex = -1;
|
||||
for (let i = 0; i < this.txnIdDefers.length; i++) {
|
||||
if (this.txnIdDefers[i].txnId === txnId) {
|
||||
txnIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (txnIndex === -1) {
|
||||
// this shouldn't happen; we shouldn't be seeing txn_ids for things we don't know about,
|
||||
// whine about it.
|
||||
logger.warn(`resolveTransactionDefers: seen ${txnId} but it isn't a pending txn, ignoring.`);
|
||||
return;
|
||||
}
|
||||
// This list is sorted in time, so if the input txnId ACKs in the middle of this array,
|
||||
// then everything before it that hasn't been ACKed yet never will and we should reject them.
|
||||
for (let i = 0; i < txnIndex; i++) {
|
||||
this.txnIdDefers[i].reject(this.txnIdDefers[i].txnId);
|
||||
}
|
||||
this.txnIdDefers[txnIndex].resolve(txnId);
|
||||
// clear out settled promises, including the one we resolved.
|
||||
this.txnIdDefers = this.txnIdDefers.slice(txnIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -802,7 +551,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
this.abortController?.abort();
|
||||
// remove all listeners so things can be GC'd
|
||||
this.removeAllListeners(SlidingSyncEvent.Lifecycle);
|
||||
this.removeAllListeners(SlidingSyncEvent.List);
|
||||
this.removeAllListeners(SlidingSyncEvent.RoomData);
|
||||
}
|
||||
|
||||
@@ -811,20 +559,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
*/
|
||||
private resetup(): void {
|
||||
logger.warn("SlidingSync: resetting connection info");
|
||||
// any pending txn ID defers will be forgotten already by the server, so clear them out
|
||||
this.txnIdDefers.forEach((d) => {
|
||||
d.reject(d.txnId);
|
||||
});
|
||||
this.txnIdDefers = [];
|
||||
// resend sticky params and de-confirm all subscriptions
|
||||
this.lists.forEach((l) => {
|
||||
l.setModified(true);
|
||||
});
|
||||
this.confirmedRoomSubscriptions = new Set<string>(); // leave desired ones alone though!
|
||||
// reset the connection as we might be wedged
|
||||
this.needsResend = true;
|
||||
this.abortController?.abort();
|
||||
this.abortController = new AbortController();
|
||||
this.resend();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -836,20 +577,18 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
let currentPos: string | undefined;
|
||||
while (!this.terminated) {
|
||||
this.needsResend = false;
|
||||
let doNotUpdateList = false;
|
||||
let resp: MSC3575SlidingSyncResponse | undefined;
|
||||
try {
|
||||
const listModifiedCount = this.listModifiedCount;
|
||||
const reqLists: Record<string, MSC3575List> = {};
|
||||
this.lists.forEach((l: SlidingList, key: string) => {
|
||||
reqLists[key] = l.getList(false);
|
||||
reqLists[key] = l.getList(true);
|
||||
});
|
||||
const reqBody: MSC3575SlidingSyncRequest = {
|
||||
lists: reqLists,
|
||||
pos: currentPos,
|
||||
timeout: this.timeoutMS,
|
||||
clientTimeout: this.timeoutMS + BUFFER_PERIOD_MS,
|
||||
extensions: this.getExtensionRequest(currentPos === undefined),
|
||||
extensions: await this.getExtensionRequest(currentPos === undefined),
|
||||
};
|
||||
// check if we are (un)subscribing to a room and modify request this one time for it
|
||||
const newSubscriptions = difference(this.desiredRoomSubscriptions, this.confirmedRoomSubscriptions);
|
||||
@@ -868,10 +607,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
reqBody.room_subscriptions[roomId] = sub;
|
||||
}
|
||||
}
|
||||
if (this.txnId) {
|
||||
reqBody.txn_id = this.txnId;
|
||||
this.txnId = null;
|
||||
}
|
||||
this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl, this.abortController.signal);
|
||||
resp = await this.pendingReq;
|
||||
currentPos = resp.pos;
|
||||
@@ -882,13 +617,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
for (const roomId of unsubscriptions) {
|
||||
this.confirmedRoomSubscriptions.delete(roomId);
|
||||
}
|
||||
if (listModifiedCount !== this.listModifiedCount) {
|
||||
// the lists have been modified whilst we were waiting for 'await' to return, but the abort()
|
||||
// call did nothing. It is NOT SAFE to modify the list array now. We'll process the response but
|
||||
// not update list pointers.
|
||||
logger.debug("list modified during await call, not updating list");
|
||||
doNotUpdateList = true;
|
||||
}
|
||||
// mark all these lists as having been sent as sticky so we don't keep sending sticky params
|
||||
this.lists.forEach((l) => {
|
||||
l.setModified(false);
|
||||
@@ -931,27 +659,8 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
await this.invokeRoomDataListeners(roomId, resp!.rooms[roomId]);
|
||||
}
|
||||
|
||||
const listKeysWithUpdates: Set<string> = new Set();
|
||||
if (!doNotUpdateList) {
|
||||
for (const [key, list] of Object.entries(resp.lists)) {
|
||||
list.ops = list.ops ?? [];
|
||||
if (list.ops.length > 0) {
|
||||
listKeysWithUpdates.add(key);
|
||||
}
|
||||
this.processListOps(list, key);
|
||||
}
|
||||
}
|
||||
this.invokeLifecycleListeners(SlidingSyncState.Complete, resp);
|
||||
await this.onPostExtensionsResponse(resp.extensions);
|
||||
listKeysWithUpdates.forEach((listKey: string) => {
|
||||
const list = this.lists.get(listKey);
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
this.emit(SlidingSyncEvent.List, listKey, list.joinedCount, Object.assign({}, list.roomIndexToRoomId));
|
||||
});
|
||||
|
||||
this.resolveTransactionDefers(resp.txn_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,20 +82,20 @@
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/core@^7.12.10":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.9.tgz#71838542a4b1e49dfed353d7acbc6eb89f4a76f2"
|
||||
integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9"
|
||||
integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==
|
||||
dependencies:
|
||||
"@ampproject/remapping" "^2.2.0"
|
||||
"@babel/code-frame" "^7.26.2"
|
||||
"@babel/generator" "^7.26.9"
|
||||
"@babel/generator" "^7.26.10"
|
||||
"@babel/helper-compilation-targets" "^7.26.5"
|
||||
"@babel/helper-module-transforms" "^7.26.0"
|
||||
"@babel/helpers" "^7.26.9"
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/helpers" "^7.26.10"
|
||||
"@babel/parser" "^7.26.10"
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/traverse" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
"@babel/traverse" "^7.26.10"
|
||||
"@babel/types" "^7.26.10"
|
||||
convert-source-map "^2.0.0"
|
||||
debug "^4.1.0"
|
||||
gensync "^1.0.0-beta.2"
|
||||
@@ -103,18 +103,18 @@
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/eslint-parser@^7.12.10":
|
||||
version "7.26.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.8.tgz#55c4f4aae4970ae127f7a12369182ed6250e6f09"
|
||||
integrity sha512-3tBctaHRW6xSub26z7n8uyOTwwUsCdvIug/oxBH9n6yCO5hMj2vwDJAo7RbBMKrM7P+W2j61zLKviJQFGOYKMg==
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.10.tgz#4423cb3f84c26978439feabfe23c5aa929400737"
|
||||
integrity sha512-QsfQZr4AiLpKqn7fz+j7SN+f43z2DZCgGyYbNJ2vJOqKfG4E6MZer1+jqGZqKJaxq/gdO2DC/nUu45+pOL5p2Q==
|
||||
dependencies:
|
||||
"@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
|
||||
eslint-visitor-keys "^2.1.0"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/eslint-plugin@^7.12.10":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.25.9.tgz#a5b6cc46085e0a7d45c5dae36055ce30c5125dab"
|
||||
integrity sha512-MWg1lz+JiP9l1fXkE0qCUVo+1XwgNRPs6GTc88hmw6qN3AdgmfTSkyHt0e1xOTsKdXW5xlh2Lsk3wrFZbW5rzQ==
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.26.10.tgz#63c70823e8f4875dab8b976aa03d7246ca23d531"
|
||||
integrity sha512-vOFlmZX0tzi58ggN8w2LGNOY2VOoqgYBtj2FRy+3BjFSvNvU+RnapXywHACzLqzbDi9yvS0CZEBokJ26ioUVLA==
|
||||
dependencies:
|
||||
eslint-rule-composer "^0.3.0"
|
||||
|
||||
@@ -129,13 +129,13 @@
|
||||
"@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==
|
||||
"@babel/generator@^7.26.10", "@babel/generator@^7.26.9":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7"
|
||||
integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
"@babel/parser" "^7.26.10"
|
||||
"@babel/types" "^7.26.10"
|
||||
"@jridgewell/gen-mapping" "^0.3.5"
|
||||
"@jridgewell/trace-mapping" "^0.3.25"
|
||||
jsesc "^3.0.2"
|
||||
@@ -190,7 +190,7 @@
|
||||
regexpu-core "^6.2.0"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-define-polyfill-provider@^0.6.2", "@babel/helper-define-polyfill-provider@^0.6.3":
|
||||
"@babel/helper-define-polyfill-provider@^0.6.3":
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz#f4f2792fae2ef382074bc2d713522cf24e6ddb21"
|
||||
integrity sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==
|
||||
@@ -311,13 +311,13 @@
|
||||
"@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==
|
||||
"@babel/helpers@^7.26.10":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384"
|
||||
integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
|
||||
dependencies:
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
"@babel/types" "^7.26.10"
|
||||
|
||||
"@babel/highlight@^7.24.7":
|
||||
version "7.25.9"
|
||||
@@ -348,7 +348,14 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.26.7"
|
||||
|
||||
"@babel/parser@^7.26.5", "@babel/parser@^7.26.9":
|
||||
"@babel/parser@^7.26.10", "@babel/parser@^7.26.9":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
|
||||
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.26.10"
|
||||
|
||||
"@babel/parser@^7.26.5":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5"
|
||||
integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==
|
||||
@@ -877,14 +884,14 @@
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/plugin-transform-runtime@^7.12.10":
|
||||
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==
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz#6b4504233de8238e7d666c15cde681dc62adff87"
|
||||
integrity sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^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-corejs3 "^0.11.0"
|
||||
babel-plugin-polyfill-regenerator "^0.6.1"
|
||||
semver "^6.3.1"
|
||||
|
||||
@@ -1093,7 +1100,20 @@
|
||||
"@babel/parser" "^7.24.7"
|
||||
"@babel/types" "^7.24.7"
|
||||
|
||||
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9":
|
||||
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380"
|
||||
integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.26.2"
|
||||
"@babel/generator" "^7.26.10"
|
||||
"@babel/parser" "^7.26.10"
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/types" "^7.26.10"
|
||||
debug "^4.3.1"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@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==
|
||||
@@ -1133,10 +1153,10 @@
|
||||
"@babel/helper-validator-identifier" "^7.24.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@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==
|
||||
"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259"
|
||||
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
@@ -1149,6 +1169,14 @@
|
||||
"@babel/helper-string-parser" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
|
||||
"@babel/types@^7.26.5", "@babel/types@^7.26.7", "@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"
|
||||
|
||||
"@bcoe/v8-coverage@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
@@ -1170,6 +1198,28 @@
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "0.3.9"
|
||||
|
||||
"@emnapi/core@^1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.3.1.tgz#9c62d185372d1bddc94682b87f376e03dfac3f16"
|
||||
integrity sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==
|
||||
dependencies:
|
||||
"@emnapi/wasi-threads" "1.0.1"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/runtime@^1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60"
|
||||
integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/wasi-threads@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz#d7ae71fd2166b1c916c6cd2d0df2ef565a2e1a5b"
|
||||
integrity sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@es-joy/jsdoccomment@~0.49.0":
|
||||
version "0.49.0"
|
||||
resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz#e5ec1eda837c802eca67d3b29e577197f14ba1db"
|
||||
@@ -1187,9 +1237,9 @@
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56"
|
||||
integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==
|
||||
version "4.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz#b0fc7e06d0c94f801537fd4237edc2706d3b8e4c"
|
||||
integrity sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
@@ -1223,14 +1273,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
|
||||
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
|
||||
|
||||
"@gerrit0/mini-shiki@^1.24.0":
|
||||
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==
|
||||
"@gerrit0/mini-shiki@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@gerrit0/mini-shiki/-/mini-shiki-3.2.1.tgz#d8a0df1044466acf7e94c3b103dbb194fd7770fd"
|
||||
integrity sha512-HbzRC6MKB6U8kQhczz0APKPIzFHTrcqhaC7es2EXInq1SpjPVnpVSIsBe6hNoLWqqCx1n5VKiPXq6PfXnHZKOQ==
|
||||
dependencies:
|
||||
"@shikijs/engine-oniguruma" "^1.27.2"
|
||||
"@shikijs/types" "^1.27.2"
|
||||
"@shikijs/vscode-textmate" "^10.0.1"
|
||||
"@shikijs/engine-oniguruma" "^3.2.1"
|
||||
"@shikijs/types" "^3.2.1"
|
||||
"@shikijs/vscode-textmate" "^10.0.2"
|
||||
|
||||
"@humanwhocodes/config-array@^0.13.0":
|
||||
version "0.13.0"
|
||||
@@ -1562,6 +1612,15 @@
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz#d4f6937353bc4568292654efb0a0e0532adbcba2"
|
||||
integrity sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==
|
||||
|
||||
"@napi-rs/wasm-runtime@^0.2.7":
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz#288f03812a408bc53c2c3686c65f38fe90f295eb"
|
||||
integrity sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==
|
||||
dependencies:
|
||||
"@emnapi/core" "^1.3.1"
|
||||
"@emnapi/runtime" "^1.3.1"
|
||||
"@tybys/wasm-util" "^0.9.0"
|
||||
|
||||
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
|
||||
version "2.1.8-no-fsevents.3"
|
||||
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b"
|
||||
@@ -1616,11 +1675,6 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@nolyfill/is-core-module@1.0.39":
|
||||
version "1.0.39"
|
||||
resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
|
||||
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
|
||||
|
||||
"@peculiar/asn1-schema@^2.3.8":
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz#04b38832a814e25731232dd5be883460a156da3b"
|
||||
@@ -1663,23 +1717,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
|
||||
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
|
||||
|
||||
"@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==
|
||||
"@shikijs/engine-oniguruma@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-3.2.1.tgz#be07d9f09a4b5e35af1896ff232001b9d4f5466e"
|
||||
integrity sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==
|
||||
dependencies:
|
||||
"@shikijs/types" "1.29.2"
|
||||
"@shikijs/vscode-textmate" "^10.0.1"
|
||||
"@shikijs/types" "3.2.1"
|
||||
"@shikijs/vscode-textmate" "^10.0.2"
|
||||
|
||||
"@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==
|
||||
"@shikijs/types@3.2.1", "@shikijs/types@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-3.2.1.tgz#8d289ad95d62833d7f9af536c073312d7afe8182"
|
||||
integrity sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==
|
||||
dependencies:
|
||||
"@shikijs/vscode-textmate" "^10.0.1"
|
||||
"@shikijs/vscode-textmate" "^10.0.2"
|
||||
"@types/hast" "^3.0.4"
|
||||
|
||||
"@shikijs/vscode-textmate@^10.0.1":
|
||||
"@shikijs/vscode-textmate@^10.0.2":
|
||||
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==
|
||||
@@ -1753,6 +1807,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||
|
||||
"@tybys/wasm-util@^0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355"
|
||||
integrity sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@types/babel__core@^7.1.14":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
|
||||
@@ -1876,9 +1937,9 @@
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/node@18":
|
||||
version "18.19.79"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.79.tgz#82fde7ac17809f4738a494b22273f0f7e6754f6e"
|
||||
integrity sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==
|
||||
version "18.19.80"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.80.tgz#6d6008e8920dddcd23f9dd33da24684ef57d487c"
|
||||
integrity sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
@@ -1930,29 +1991,29 @@
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^8.0.0":
|
||||
version "8.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz#7e880faf91f89471c30c141951e15f0eb3a0599e"
|
||||
integrity sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz#3e48eb847924161843b092c87a9b65176b53782f"
|
||||
integrity sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.10.0"
|
||||
"@typescript-eslint/scope-manager" "8.26.0"
|
||||
"@typescript-eslint/type-utils" "8.26.0"
|
||||
"@typescript-eslint/utils" "8.26.0"
|
||||
"@typescript-eslint/visitor-keys" "8.26.0"
|
||||
"@typescript-eslint/scope-manager" "8.26.1"
|
||||
"@typescript-eslint/type-utils" "8.26.1"
|
||||
"@typescript-eslint/utils" "8.26.1"
|
||||
"@typescript-eslint/visitor-keys" "8.26.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.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.0.tgz#9b4d2198e89f64fb81e83167eedd89a827d843a9"
|
||||
integrity sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.1.tgz#0e2f915a497519fc43f52cf2ecbfa607ff56f72e"
|
||||
integrity sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.26.0"
|
||||
"@typescript-eslint/types" "8.26.0"
|
||||
"@typescript-eslint/typescript-estree" "8.26.0"
|
||||
"@typescript-eslint/visitor-keys" "8.26.0"
|
||||
"@typescript-eslint/scope-manager" "8.26.1"
|
||||
"@typescript-eslint/types" "8.26.1"
|
||||
"@typescript-eslint/typescript-estree" "8.26.1"
|
||||
"@typescript-eslint/visitor-keys" "8.26.1"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.21.0":
|
||||
@@ -1971,13 +2032,21 @@
|
||||
"@typescript-eslint/types" "8.26.0"
|
||||
"@typescript-eslint/visitor-keys" "8.26.0"
|
||||
|
||||
"@typescript-eslint/type-utils@8.26.0":
|
||||
version "8.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz#9ee8cc98184b5f66326578de9c097edc89da6f68"
|
||||
integrity sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==
|
||||
"@typescript-eslint/scope-manager@8.26.1":
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz#5e6ad0ac258ccf79462e91c3f43a3f1f7f31a6cc"
|
||||
integrity sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "8.26.0"
|
||||
"@typescript-eslint/utils" "8.26.0"
|
||||
"@typescript-eslint/types" "8.26.1"
|
||||
"@typescript-eslint/visitor-keys" "8.26.1"
|
||||
|
||||
"@typescript-eslint/type-utils@8.26.1":
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz#462f0bae09de72ac6e8e1af2ebe588c23224d7f8"
|
||||
integrity sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "8.26.1"
|
||||
"@typescript-eslint/utils" "8.26.1"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
@@ -1991,6 +2060,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.26.0.tgz#c4e93a8faf3a38a8d8adb007dc7834f1c89ee7bf"
|
||||
integrity sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==
|
||||
|
||||
"@typescript-eslint/types@8.26.1":
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.26.1.tgz#d5978721670cff263348d5062773389231a64132"
|
||||
integrity sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.21.0":
|
||||
version "8.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.21.0.tgz#5ce71acdbed3b97b959f6168afba5a03c88f69a9"
|
||||
@@ -2019,15 +2093,29 @@
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
"@typescript-eslint/utils@8.26.0", "@typescript-eslint/utils@^8.23.0":
|
||||
version "8.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.26.0.tgz#845d20ed8378a5594e6445f54e53b972aee7b3e6"
|
||||
integrity sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==
|
||||
"@typescript-eslint/typescript-estree@8.26.1":
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz#eb0e4ce31753683d83be53441a409fd5f0b34afd"
|
||||
integrity sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.26.1"
|
||||
"@typescript-eslint/visitor-keys" "8.26.1"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
"@typescript-eslint/utils@8.26.1":
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.26.1.tgz#54cc58469955f25577f659753b71a0e117a0539f"
|
||||
integrity sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "8.26.0"
|
||||
"@typescript-eslint/types" "8.26.0"
|
||||
"@typescript-eslint/typescript-estree" "8.26.0"
|
||||
"@typescript-eslint/scope-manager" "8.26.1"
|
||||
"@typescript-eslint/types" "8.26.1"
|
||||
"@typescript-eslint/typescript-estree" "8.26.1"
|
||||
|
||||
"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0":
|
||||
version "8.21.0"
|
||||
@@ -2039,6 +2127,16 @@
|
||||
"@typescript-eslint/types" "8.21.0"
|
||||
"@typescript-eslint/typescript-estree" "8.21.0"
|
||||
|
||||
"@typescript-eslint/utils@^8.23.0":
|
||||
version "8.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.26.0.tgz#845d20ed8378a5594e6445f54e53b972aee7b3e6"
|
||||
integrity sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "8.26.0"
|
||||
"@typescript-eslint/types" "8.26.0"
|
||||
"@typescript-eslint/typescript-estree" "8.26.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.21.0":
|
||||
version "8.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.21.0.tgz#a89744c4cdc83b5c761eb5878befe6c33d1481b2"
|
||||
@@ -2055,11 +2153,76 @@
|
||||
"@typescript-eslint/types" "8.26.0"
|
||||
eslint-visitor-keys "^4.2.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.26.1":
|
||||
version "8.26.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz#c5267fcc82795cf10280363023837deacad2647c"
|
||||
integrity sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.26.1"
|
||||
eslint-visitor-keys "^4.2.0"
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||
|
||||
"@unrs/rspack-resolver-binding-darwin-arm64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.2.1.tgz#ef76ae8ee116684ef84be7636dacbdfc53f27947"
|
||||
integrity sha512-xgSjy64typsn/lhQk/uKaS363H7ZeIBlWSh25FJFWXSCeLMHpEZ0umDo5Vzqi5iS26OZ5R1SpQkwiS78GhQRjw==
|
||||
|
||||
"@unrs/rspack-resolver-binding-darwin-x64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-darwin-x64/-/rspack-resolver-binding-darwin-x64-1.2.1.tgz#691fabdf45ae8084f8daf84eb2d4bf4ac0a98b07"
|
||||
integrity sha512-3maDtW0vehzciEbuLxc2g+0FmDw5LGfCt+yMN1ZDn0lW0ikEBEFp6ul3h2fRphtfuCc7IvBJE9WWTt1UHkS7Nw==
|
||||
|
||||
"@unrs/rspack-resolver-binding-freebsd-x64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-freebsd-x64/-/rspack-resolver-binding-freebsd-x64-1.2.1.tgz#ac80565aa59cbc3d2e469143976079b5b06644a7"
|
||||
integrity sha512-aN6ifws9rNLjK2+6sIU9wvHyjXEf3S5+EZTHRarzd4jfa8i5pA7Mwt28un2DZVrBtIxhWDQvUPVKGI7zSBfVCA==
|
||||
|
||||
"@unrs/rspack-resolver-binding-linux-arm-gnueabihf@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-arm-gnueabihf/-/rspack-resolver-binding-linux-arm-gnueabihf-1.2.1.tgz#c5fce8cafe3eff7db1c859a02f7ee2a9213e6e94"
|
||||
integrity sha512-tKqu9VQyCO1yEUX6n6jgOHi7SJA9e6lvHczK60gur4VBITxnPmVYiCj2aekrOOIavvvjjuWAL2rqPQuc4g7RHQ==
|
||||
|
||||
"@unrs/rspack-resolver-binding-linux-arm64-gnu@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-arm64-gnu/-/rspack-resolver-binding-linux-arm64-gnu-1.2.1.tgz#6cd04c044057791ce8eed60b1d8b7eacc8be0ec2"
|
||||
integrity sha512-+xDI0kvwPiCR7334O83TPfaUXSe0UMVi5srQpQxP4+SDVYuONWsbwAC1IXe+yfOwRVGZsUdW9wE0ZiWs4Z+egw==
|
||||
|
||||
"@unrs/rspack-resolver-binding-linux-arm64-musl@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-arm64-musl/-/rspack-resolver-binding-linux-arm64-musl-1.2.1.tgz#46e55287e5022cbb56683da6b1075a9610ef76f6"
|
||||
integrity sha512-fcrVHlw+6UgQliMbI0znFD4ASWKuyY17FdH67ZmyNH62b0hRhhxQuJE0D6N3410m8lKVu4QW4EzFiHxYFUC0cg==
|
||||
|
||||
"@unrs/rspack-resolver-binding-linux-x64-gnu@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-x64-gnu/-/rspack-resolver-binding-linux-x64-gnu-1.2.1.tgz#3064ca8e109b187e9d209760f943e400d10f2724"
|
||||
integrity sha512-xISTyUJ2PiAT4x9nlh8FdciDcdKbsatgK9qO7EEsILt9VB7Y1mHYGaszj3ouxfZnaKQ13WwW+dFLGxkZLP/WVg==
|
||||
|
||||
"@unrs/rspack-resolver-binding-linux-x64-musl@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-x64-musl/-/rspack-resolver-binding-linux-x64-musl-1.2.1.tgz#1baee513e514fc8bdc0ba261d63f77a0821c74ff"
|
||||
integrity sha512-LE8EjE/iPlvSsFbZ6P9c0Jh5/pifAi03UYeXYwOnQqt1molKAPMB0R4kGWOM7dnDYaNgkk1MN9MOTCLsqe97Fw==
|
||||
|
||||
"@unrs/rspack-resolver-binding-wasm32-wasi@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-wasm32-wasi/-/rspack-resolver-binding-wasm32-wasi-1.2.1.tgz#3a31b8cd69e5ac1506924a598051fe2954b860d8"
|
||||
integrity sha512-XERT3B88+G55RgG96May8QvAdgGzHr8qtQ70cIdbuWTpIcA0I76cnxSZ8Qwx33y73jE5N/myX2YKDlFksn4z6w==
|
||||
dependencies:
|
||||
"@napi-rs/wasm-runtime" "^0.2.7"
|
||||
|
||||
"@unrs/rspack-resolver-binding-win32-arm64-msvc@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-win32-arm64-msvc/-/rspack-resolver-binding-win32-arm64-msvc-1.2.1.tgz#0dad1f2dfc26903e28727dd7c0dcc3a171870d74"
|
||||
integrity sha512-I8OLI6JbmNx2E/SG8MOEuo/d6rNx8dwgL09rcItSMcP82v1oZ8AY8HNA+axxuxEH95nkb6MPJU09p63isDvzrA==
|
||||
|
||||
"@unrs/rspack-resolver-binding-win32-x64-msvc@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-win32-x64-msvc/-/rspack-resolver-binding-win32-x64-msvc-1.2.1.tgz#6fcb04b7012edd4f8bd97641780012af1ec81c04"
|
||||
integrity sha512-s5WvCljhFqiE3McvaD3lDIsQpmk7gEJRUHy1PRwLPzEB7snq9P2xQeqgzdjGhJQq62jBFz7NDy7NbMkocWr2pw==
|
||||
|
||||
abab@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
|
||||
@@ -2091,9 +2254,9 @@ acorn@^8.1.0, acorn@^8.11.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.9.0:
|
||||
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
|
||||
|
||||
acorn@^8.14.0:
|
||||
version "8.14.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
||||
version "8.14.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
|
||||
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
@@ -2343,14 +2506,6 @@ babel-plugin-polyfill-corejs2@^0.4.10:
|
||||
"@babel/helper-define-polyfill-provider" "^0.6.3"
|
||||
semver "^6.3.1"
|
||||
|
||||
babel-plugin-polyfill-corejs3@^0.10.6:
|
||||
version "0.10.6"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7"
|
||||
integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==
|
||||
dependencies:
|
||||
"@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"
|
||||
@@ -2434,7 +2589,7 @@ braces@^3.0.3, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.24.0, browserslist@^4.24.3:
|
||||
browserslist@^4.24.0, browserslist@^4.24.3, browserslist@^4.24.4:
|
||||
version "4.24.4"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
|
||||
integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
|
||||
@@ -2495,9 +2650,9 @@ camelcase@^6.2.0:
|
||||
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
||||
|
||||
caniuse-lite@^1.0.30001688:
|
||||
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==
|
||||
version "1.0.30001706"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz#902c3f896f4b2968031c3a546ab2ef8b465a2c8f"
|
||||
integrity sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==
|
||||
|
||||
chalk@5.2.0:
|
||||
version "5.2.0"
|
||||
@@ -2683,13 +2838,20 @@ 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.40.0:
|
||||
core-js-compat@^3.38.1:
|
||||
version "3.40.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38"
|
||||
integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==
|
||||
dependencies:
|
||||
browserslist "^4.24.3"
|
||||
|
||||
core-js-compat@^3.40.0:
|
||||
version "3.41.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.41.0.tgz#4cdfce95f39a8f27759b667cf693d96e5dda3d17"
|
||||
integrity sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==
|
||||
dependencies:
|
||||
browserslist "^4.24.4"
|
||||
|
||||
core-js@^3.0.0:
|
||||
version "3.37.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9"
|
||||
@@ -2789,7 +2951,7 @@ debug@^3.2.7:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.6, debug@^4.3.7, debug@^4.4.0:
|
||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.6, debug@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
|
||||
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
|
||||
@@ -2907,9 +3069,9 @@ easy-table@1.2.0:
|
||||
wcwidth "^1.0.1"
|
||||
|
||||
electron-to-chromium@^1.5.73:
|
||||
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==
|
||||
version "1.5.120"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.120.tgz#ccfdd28e9795fb8c2221cefa2c9a071501c86247"
|
||||
integrity sha512-oTUp3gfX1gZI+xfD2djr2rzQdHCwHzPQrrK0CD7WpTdF0nPdQ/INcRVjWgLdCT4a9W3jFObR9DAfsuyFQnI8CQ==
|
||||
|
||||
emittery@^0.13.1:
|
||||
version "0.13.1"
|
||||
@@ -2931,7 +3093,7 @@ 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, enhanced-resolve@^5.18.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==
|
||||
@@ -3099,9 +3261,9 @@ eslint-config-google@^0.14.0:
|
||||
integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
|
||||
|
||||
eslint-config-prettier@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz#fbb03bfc8db0651df9ce4e8b7150d11c5fe3addf"
|
||||
integrity sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz#cf0ff6e5c4e7e15f129f1f1ce2a5ecba92dec132"
|
||||
integrity sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==
|
||||
|
||||
eslint-import-resolver-node@^0.3.9:
|
||||
version "0.3.9"
|
||||
@@ -3112,18 +3274,16 @@ eslint-import-resolver-node@^0.3.9:
|
||||
is-core-module "^2.13.0"
|
||||
resolve "^1.22.4"
|
||||
|
||||
eslint-import-resolver-typescript@^3.5.1:
|
||||
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==
|
||||
eslint-import-resolver-typescript@^4.0.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.2.1.tgz#a48bbe43490dea494eed34c2bf5152249aba7d84"
|
||||
integrity sha512-jAAKR08YRFtlRpxK3OnixV0JB88lH3Xo7HWn1KpKlEvtVUlLMAcRCcBvWT1KAnfT4jPAs4veyFxuUqSdg/Vd3g==
|
||||
dependencies:
|
||||
"@nolyfill/is-core-module" "1.0.39"
|
||||
debug "^4.3.7"
|
||||
enhanced-resolve "^5.15.0"
|
||||
debug "^4.4.0"
|
||||
get-tsconfig "^4.10.0"
|
||||
is-bun-module "^1.0.2"
|
||||
stable-hash "^0.0.4"
|
||||
tinyglobby "^0.2.10"
|
||||
rspack-resolver "^1.2.0"
|
||||
stable-hash "^0.0.5"
|
||||
tinyglobby "^0.2.12"
|
||||
|
||||
eslint-module-utils@^2.12.0:
|
||||
version "2.12.0"
|
||||
@@ -3173,9 +3333,9 @@ eslint-plugin-jest@^28.0.0:
|
||||
"@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
|
||||
eslint-plugin-jsdoc@^50.0.0:
|
||||
version "50.6.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.3.tgz#668dc4d32e823c84ede7310cffbf70c9d370d291"
|
||||
integrity sha512-NxbJyt1M5zffPcYZ8Nb53/8nnbIScmiLAMdoe0/FAszwb7lcSiX3iYBTsuF7RV84dZZJC8r3NghomrUXsmWvxQ==
|
||||
version "50.6.8"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.8.tgz#c541c1eaaeb14e97c53397a8495ee60889943c62"
|
||||
integrity sha512-PPZVqhoXaalMQwDGzcQrJtPSPIPOYsSMtvkjYAdsIazOW20yhYtVX4+jLL+XznD4zYTXyZbPWPRKkNev4D4lyw==
|
||||
dependencies:
|
||||
"@es-joy/jsdoccomment" "~0.49.0"
|
||||
are-docs-informative "^0.0.2"
|
||||
@@ -3189,7 +3349,7 @@ eslint-plugin-jsdoc@^50.0.0:
|
||||
spdx-expression-parse "^4.0.0"
|
||||
synckit "^0.9.1"
|
||||
|
||||
eslint-plugin-matrix-org@^2.1.0:
|
||||
eslint-plugin-matrix-org@2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-2.1.0.tgz#9053726119f107154f5210665c2f84219425c190"
|
||||
integrity sha512-YjVQ0qunzVV34tpUchLWhOrOalGfRLm0tclS4dPYnXS8Ui+p12o/YtRHt+26Mg5tJ0QH76HsGC0LJKLVLNoqfg==
|
||||
@@ -3486,14 +3646,7 @@ fast-levenshtein@^2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||
|
||||
fastq@^1.15.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.0.tgz#a82c6b7c2bb4e44766d865f07997785fecfdcb89"
|
||||
integrity sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
fastq@^1.6.0:
|
||||
fastq@^1.15.0, fastq@^1.6.0:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5"
|
||||
integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
|
||||
@@ -3988,13 +4141,6 @@ is-builtin-module@^3.2.1:
|
||||
dependencies:
|
||||
builtin-modules "^3.3.0"
|
||||
|
||||
is-bun-module@^1.0.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-1.3.0.tgz#ea4d24fdebfcecc98e81bcbcb506827fee288760"
|
||||
integrity sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==
|
||||
dependencies:
|
||||
semver "^7.6.3"
|
||||
|
||||
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
|
||||
@@ -4790,9 +4936,9 @@ kleur@^3.0.3:
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
knip@^5.0.0:
|
||||
version "5.44.1"
|
||||
resolved "https://registry.yarnpkg.com/knip/-/knip-5.44.1.tgz#712cb670af20185a1133eb4d33302105e062836e"
|
||||
integrity sha512-YJrz7K6AA3SwXJ5LL9ZlzQX9wcihMNwKoGBAaY5kUn4rLNOZ5ehsjFV+xm8gQuIJdjsFIKcFsy0K3U3yM/mabw==
|
||||
version "5.46.0"
|
||||
resolved "https://registry.yarnpkg.com/knip/-/knip-5.46.0.tgz#8444881f9382901b4022775ba99e2df4de1df991"
|
||||
integrity sha512-WedHSK5xNBWYgm64Rt5B9b0CVXL2kRBcyCeet3NHgdv9en3QE4AWSDPEiX48NoPUBW3h//9S0VwLF5MG/MPi3g==
|
||||
dependencies:
|
||||
"@nodelib/fs.walk" "3.0.1"
|
||||
"@snyk/github-codeowners" "1.1.0"
|
||||
@@ -4842,9 +4988,9 @@ linkify-it@^5.0.0:
|
||||
uc.micro "^2.0.0"
|
||||
|
||||
lint-staged@^15.0.2:
|
||||
version "15.4.3"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.4.3.tgz#e73587cc857f580c99f907abefe9ac8d8d5e74c1"
|
||||
integrity sha512-FoH1vOeouNh1pw+90S+cnuoFwRfUD9ijY2GKy5h7HS3OR7JVir2N2xrsa0+Twc1B7cW72L+88geG5cW4wIhn7g==
|
||||
version "15.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.5.0.tgz#fa6464cfb06e0faf5bb167f83186e952ff6e569e"
|
||||
integrity sha512-WyCzSbfYGhK7cU+UuDDkzUiytbfbi0ZdPy2orwtM75P3WTtQBzmG40cCxIa8Ii2+XjfxzLH6Be46tUfWS85Xfg==
|
||||
dependencies:
|
||||
chalk "^5.4.1"
|
||||
commander "^13.1.0"
|
||||
@@ -4988,9 +5134,9 @@ matrix-mock-request@^2.5.0:
|
||||
expect "^28.1.0"
|
||||
|
||||
matrix-widget-api@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.10.0.tgz#d31ea073a5871a1fb1a511ef900b0c125a37bf55"
|
||||
integrity sha512-rkAJ29briYV7TJnfBVLVSKtpeBrBju15JZFSDP6wj8YdbCu1bdmlplJayQ+vYaw1x4fzI49Q+Nz3E85s46sRDw==
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.13.1.tgz#5b1caeed2fc58148bcd2984e0546d2d06a1713ad"
|
||||
integrity sha512-mkOHUVzaN018TCbObfGOSaMW2GoUxOfcxNNlTVx5/HeMk3OSQPQM0C9oEME5Liiv/dBUoSrEB64V8wF7e/gb1w==
|
||||
dependencies:
|
||||
"@types/events" "^3.0.0"
|
||||
events "^3.2.0"
|
||||
@@ -5196,9 +5342,9 @@ object.values@^1.2.0:
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
oidc-client-ts@^3.0.1:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.1.0.tgz#28d3254951a1c64cc9780042c61492a71b2240dd"
|
||||
integrity sha512-IDopEXjiwjkmJLYZo6BTlvwOtnlSniWZkKZoXforC/oLZHC9wkIxd25Kwtmo5yKFMMVcsp3JY6bhcNJqdYk8+g==
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.2.0.tgz#37e6e563bfeabc4aa1497a9a963f8e3d73168316"
|
||||
integrity sha512-wUvVcG3SXzZDKHxi/VGQGaTUk9qguMKfYh26Y1zOVrQsu1zp85JWx/SjZzKSXK5j3NA1RcasgMoaHe6gt1WNtw==
|
||||
dependencies:
|
||||
jwt-decode "^4.0.0"
|
||||
|
||||
@@ -5422,10 +5568,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.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.1.tgz#22fac9d0b18c0b92055ac8fb619ac1c7bef02fb7"
|
||||
integrity sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==
|
||||
prettier@3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5"
|
||||
integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
|
||||
|
||||
pretty-format@^28.1.3:
|
||||
version "28.1.3"
|
||||
@@ -5717,6 +5863,23 @@ rimraf@^6.0.0:
|
||||
glob "^11.0.0"
|
||||
package-json-from-dist "^1.0.0"
|
||||
|
||||
rspack-resolver@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/rspack-resolver/-/rspack-resolver-1.2.1.tgz#158a87d58ce4eca9eda80f647ee21ca83703ffaf"
|
||||
integrity sha512-yTaWGUvHOjcoyFMdVTdYt2nq2Hu8sw6ia3X9szloXFJlWLQZnQ9g/4TPhL3Bb3qN58Mkye8mFG7MCaKhya7fOw==
|
||||
optionalDependencies:
|
||||
"@unrs/rspack-resolver-binding-darwin-arm64" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-darwin-x64" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-freebsd-x64" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-linux-arm-gnueabihf" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-linux-arm64-gnu" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-linux-arm64-musl" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-linux-x64-gnu" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-linux-x64-musl" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-wasm32-wasi" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-win32-arm64-msvc" "1.2.1"
|
||||
"@unrs/rspack-resolver-binding-win32-x64-msvc" "1.2.1"
|
||||
|
||||
run-parallel@^1.1.9, run-parallel@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||
@@ -5927,10 +6090,10 @@ sprintf-js@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
|
||||
|
||||
stable-hash@^0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.4.tgz#55ae7dadc13e4b3faed13601587cec41859b42f7"
|
||||
integrity sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==
|
||||
stable-hash@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269"
|
||||
integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==
|
||||
|
||||
stack-utils@^2.0.3:
|
||||
version "2.0.6"
|
||||
@@ -6137,10 +6300,10 @@ 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==
|
||||
tinyglobby@^0.2.12:
|
||||
version "0.2.12"
|
||||
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5"
|
||||
integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==
|
||||
dependencies:
|
||||
fdir "^6.4.3"
|
||||
picomatch "^4.0.2"
|
||||
@@ -6225,12 +6388,12 @@ tsconfig-paths@^3.15.0:
|
||||
minimist "^1.2.6"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@^2.0.0, tslib@^2.4.0, tslib@^2.6.1:
|
||||
tslib@^2.0.0, tslib@^2.6.1:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||
|
||||
tslib@^2.6.2:
|
||||
tslib@^2.4.0, tslib@^2.6.2:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
@@ -6321,21 +6484,21 @@ typedoc-plugin-mdn-links@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-5.0.1.tgz#2ff23f997e71a2c77cd43590455754e29e4875f0"
|
||||
integrity sha512-eofdcc2nZZpipz/ubjG+7UYMi6Xu95svUwnZ+ClJh6NJdrv7kAOerL9N3iDOpo5kwQeK86GqPWwnv6LUGo5Wrw==
|
||||
|
||||
typedoc-plugin-missing-exports@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-3.1.0.tgz#cab4952c19cae1ab3f91cbbf2d7d17564682b023"
|
||||
integrity sha512-Sogbaj+qDa21NjB3SlIw4JXSwmcl/WOjwiPNaVEcPhpNG/MiRTtpwV81cT7h1cbu9StpONFPbddYWR0KV/fTWA==
|
||||
typedoc-plugin-missing-exports@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-4.0.0.tgz#01916ce83173cb2e7b2942edfbe484f3bfbb0c8c"
|
||||
integrity sha512-Z4ei+853xppDEhcqzyeyRs4+R0kUuKQWnMK1EtSTEd5LFkgkdW5Bdn8vfo/rsCGbYVJxOWU99fxgM1mROw5Fug==
|
||||
|
||||
typedoc@^0.27.0:
|
||||
version "0.27.9"
|
||||
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.27.9.tgz#5e0a7bc32bfc7bd0b70a353f4f1d5cba3d667c46"
|
||||
integrity sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==
|
||||
typedoc@^0.28.1:
|
||||
version "0.28.1"
|
||||
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.28.1.tgz#23526f452f3824ae1f1b7615dccb7d3e4aa90d83"
|
||||
integrity sha512-Mn2VPNMaxoe/hlBiLriG4U55oyAa3Xo+8HbtEwV7F5WEOPXqtxzGuMZhJYHaqFJpajeQ6ZDUC2c990NAtTbdgw==
|
||||
dependencies:
|
||||
"@gerrit0/mini-shiki" "^1.24.0"
|
||||
"@gerrit0/mini-shiki" "^3.2.1"
|
||||
lunr "^2.3.9"
|
||||
markdown-it "^14.1.0"
|
||||
minimatch "^9.0.5"
|
||||
yaml "^2.6.1"
|
||||
yaml "^2.7.0 "
|
||||
|
||||
typescript@^5.4.2:
|
||||
version "5.8.2"
|
||||
@@ -6396,9 +6559,9 @@ universalify@^0.2.0:
|
||||
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
|
||||
|
||||
update-browserslist-db@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580"
|
||||
integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
|
||||
integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
|
||||
dependencies:
|
||||
escalade "^3.2.0"
|
||||
picocolors "^1.1.1"
|
||||
@@ -6419,9 +6582,9 @@ url-parse@^1.5.3:
|
||||
requires-port "^1.0.0"
|
||||
|
||||
uuid@11:
|
||||
version "11.0.5"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.5.tgz#07b46bdfa6310c92c3fb3953a8720f170427fc62"
|
||||
integrity sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
|
||||
integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
|
||||
|
||||
uuid@8.3.2:
|
||||
version "8.3.2"
|
||||
@@ -6647,7 +6810,7 @@ yallist@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
|
||||
yaml@^2.6.1, yaml@^2.7.0:
|
||||
yaml@^2.7.0, "yaml@^2.7.0 ":
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"
|
||||
integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
|
||||
|
||||
Reference in New Issue
Block a user