You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Support MSC3391: Account data deletion (#2967)
* add deleteAccountData endpoint * check server support and test * test current state of memorystore * interpret account data events with empty content as deleted * add handling for (future) stable version of endpoint * add getSafeUserId * user getSafeUserId in deleteAccountData * better jsdoc for throws documentation
This commit is contained in:
@ -57,6 +57,7 @@ import {
|
||||
import { IOlmDevice } from "../../src/crypto/algorithms/megolm";
|
||||
import { QueryDict } from "../../src/utils";
|
||||
import { SyncState } from "../../src/sync";
|
||||
import * as featureUtils from "../../src/feature";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@ -281,6 +282,23 @@ describe("MatrixClient", function () {
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
describe("getSafeUserId()", () => {
|
||||
it("returns the logged in user id", () => {
|
||||
expect(client.getSafeUserId()).toEqual(userId);
|
||||
});
|
||||
|
||||
it("throws when there is not logged in user", () => {
|
||||
const notLoggedInClient = new MatrixClient({
|
||||
baseUrl: "https://my.home.server",
|
||||
idBaseUrl: identityServerUrl,
|
||||
fetchFn: function () {} as any, // NOP
|
||||
store: store,
|
||||
scheduler: scheduler,
|
||||
});
|
||||
expect(() => notLoggedInClient.getSafeUserId()).toThrow("Expected logged in user but found none.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendEvent", () => {
|
||||
const roomId = "!room:example.org";
|
||||
const body = "This is the body";
|
||||
@ -1828,4 +1846,68 @@ describe("MatrixClient", function () {
|
||||
expect(client.getUseE2eForGroupCall()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("delete account data", () => {
|
||||
afterEach(() => {
|
||||
jest.spyOn(featureUtils, "buildFeatureSupportMap").mockRestore();
|
||||
});
|
||||
it("makes correct request when deletion is supported by server in unstable versions", async () => {
|
||||
const eventType = "im.vector.test";
|
||||
const versionsResponse = {
|
||||
versions: ["1"],
|
||||
unstable_features: {
|
||||
"org.matrix.msc3391": true,
|
||||
},
|
||||
};
|
||||
jest.spyOn(client.http, "request").mockResolvedValue(versionsResponse);
|
||||
const requestSpy = jest.spyOn(client.http, "authedRequest").mockImplementation(() => Promise.resolve());
|
||||
const unstablePrefix = "/_matrix/client/unstable/org.matrix.msc3391";
|
||||
const path = `/user/${encodeURIComponent(userId)}/account_data/${eventType}`;
|
||||
|
||||
// populate version support
|
||||
await client.getVersions();
|
||||
await client.deleteAccountData(eventType);
|
||||
|
||||
expect(requestSpy).toHaveBeenCalledWith(Method.Delete, path, undefined, undefined, {
|
||||
prefix: unstablePrefix,
|
||||
});
|
||||
});
|
||||
|
||||
it("makes correct request when deletion is supported by server based on matrix version", async () => {
|
||||
const eventType = "im.vector.test";
|
||||
// we don't have a stable version for account data deletion yet to test this code path with
|
||||
// so mock the support map to fake stable support
|
||||
const stableSupportedDeletionMap = new Map();
|
||||
stableSupportedDeletionMap.set(featureUtils.Feature.AccountDataDeletion, featureUtils.ServerSupport.Stable);
|
||||
jest.spyOn(featureUtils, "buildFeatureSupportMap").mockResolvedValue(new Map());
|
||||
const requestSpy = jest.spyOn(client.http, "authedRequest").mockImplementation(() => Promise.resolve());
|
||||
const path = `/user/${encodeURIComponent(userId)}/account_data/${eventType}`;
|
||||
|
||||
// populate version support
|
||||
await client.getVersions();
|
||||
await client.deleteAccountData(eventType);
|
||||
|
||||
expect(requestSpy).toHaveBeenCalledWith(Method.Delete, path, undefined, undefined, undefined);
|
||||
});
|
||||
|
||||
it("makes correct request when deletion is not supported by server", async () => {
|
||||
const eventType = "im.vector.test";
|
||||
const versionsResponse = {
|
||||
versions: ["1"],
|
||||
unstable_features: {
|
||||
"org.matrix.msc3391": false,
|
||||
},
|
||||
};
|
||||
jest.spyOn(client.http, "request").mockResolvedValue(versionsResponse);
|
||||
const requestSpy = jest.spyOn(client.http, "authedRequest").mockImplementation(() => Promise.resolve());
|
||||
const path = `/user/${encodeURIComponent(userId)}/account_data/${eventType}`;
|
||||
|
||||
// populate version support
|
||||
await client.getVersions();
|
||||
await client.deleteAccountData(eventType);
|
||||
|
||||
// account data updated with empty content
|
||||
expect(requestSpy).toHaveBeenCalledWith(Method.Put, path, undefined, {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
65
spec/unit/stores/memory.spec.ts
Normal file
65
spec/unit/stores/memory.spec.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2022 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 { MatrixEvent, MemoryStore } from "../../../src";
|
||||
|
||||
describe("MemoryStore", () => {
|
||||
const event1 = new MatrixEvent({ type: "event1-type", content: { test: 1 } });
|
||||
const event2 = new MatrixEvent({ type: "event2-type", content: { test: 1 } });
|
||||
const event3 = new MatrixEvent({ type: "event3-type", content: { test: 1 } });
|
||||
const event4 = new MatrixEvent({ type: "event4-type", content: { test: 1 } });
|
||||
const event4Updated = new MatrixEvent({ type: "event4-type", content: { test: 2 } });
|
||||
const event1Empty = new MatrixEvent({ type: "event1-type", content: {} });
|
||||
|
||||
describe("account data", () => {
|
||||
it("sets account data events correctly", () => {
|
||||
const store = new MemoryStore();
|
||||
store.storeAccountDataEvents([event1, event2]);
|
||||
expect(store.getAccountData(event1.getType())).toEqual(event1);
|
||||
expect(store.getAccountData(event2.getType())).toEqual(event2);
|
||||
});
|
||||
|
||||
it("returns undefined when no account data event exists for type", () => {
|
||||
const store = new MemoryStore();
|
||||
expect(store.getAccountData("my-event-type")).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("updates account data events correctly", () => {
|
||||
const store = new MemoryStore();
|
||||
// init store with event1, event2
|
||||
store.storeAccountDataEvents([event1, event2, event4]);
|
||||
// remove event1, add event3
|
||||
store.storeAccountDataEvents([event1Empty, event3, event4Updated]);
|
||||
// removed
|
||||
expect(store.getAccountData(event1.getType())).toEqual(undefined);
|
||||
// not removed
|
||||
expect(store.getAccountData(event2.getType())).toEqual(event2);
|
||||
// added
|
||||
expect(store.getAccountData(event3.getType())).toEqual(event3);
|
||||
// updated
|
||||
expect(store.getAccountData(event4.getType())).toEqual(event4Updated);
|
||||
});
|
||||
|
||||
it("removes all account data from state on deleteAllData", async () => {
|
||||
const store = new MemoryStore();
|
||||
store.storeAccountDataEvents([event1, event2]);
|
||||
await store.deleteAllData();
|
||||
|
||||
// empty object
|
||||
expect(store.accountData).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
@ -1672,6 +1672,20 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user-id of the logged-in user
|
||||
*
|
||||
* @returns MXID for the logged-in user
|
||||
* @throws Error if not logged in
|
||||
*/
|
||||
public getSafeUserId(): string {
|
||||
const userId = this.getUserId();
|
||||
if (!userId) {
|
||||
throw new Error("Expected logged in user but found none.");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain for this client's MXID
|
||||
* @returns Domain of this MXID
|
||||
@ -3766,6 +3780,24 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteAccountData(eventType: string): Promise<void> {
|
||||
const msc3391DeleteAccountDataServerSupport = this.canSupport.get(Feature.AccountDataDeletion);
|
||||
// if deletion is not supported overwrite with empty content
|
||||
if (msc3391DeleteAccountDataServerSupport === ServerSupport.Unsupported) {
|
||||
await this.setAccountData(eventType, {});
|
||||
return;
|
||||
}
|
||||
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
||||
$userId: this.getSafeUserId(),
|
||||
$type: eventType,
|
||||
});
|
||||
const options =
|
||||
msc3391DeleteAccountDataServerSupport === ServerSupport.Unstable
|
||||
? { prefix: "/_matrix/client/unstable/org.matrix.msc3391" }
|
||||
: undefined;
|
||||
return await this.http.authedRequest(Method.Delete, path, undefined, undefined, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the users that are ignored by this client
|
||||
* @returns The array of users that are ignored (empty if none)
|
||||
|
@ -26,6 +26,7 @@ export enum Feature {
|
||||
Thread = "Thread",
|
||||
ThreadUnreadNotifications = "ThreadUnreadNotifications",
|
||||
LoginTokenRequest = "LoginTokenRequest",
|
||||
AccountDataDeletion = "AccountDataDeletion",
|
||||
}
|
||||
|
||||
type FeatureSupportCondition = {
|
||||
@ -45,6 +46,9 @@ const featureSupportResolver: Record<string, FeatureSupportCondition> = {
|
||||
[Feature.LoginTokenRequest]: {
|
||||
unstablePrefixes: ["org.matrix.msc3882"],
|
||||
},
|
||||
[Feature.AccountDataDeletion]: {
|
||||
unstablePrefixes: ["org.matrix.msc3391"],
|
||||
},
|
||||
};
|
||||
|
||||
export async function buildFeatureSupportMap(versions: IServerVersions): Promise<Map<Feature, ServerSupport>> {
|
||||
|
@ -286,7 +286,13 @@ export class MemoryStore implements IStore {
|
||||
*/
|
||||
public storeAccountDataEvents(events: MatrixEvent[]): void {
|
||||
events.forEach((event) => {
|
||||
// MSC3391: an event with content of {} should be interpreted as deleted
|
||||
const isDeleted = !Object.keys(event.getContent()).length;
|
||||
if (isDeleted) {
|
||||
delete this.accountData[event.getType()];
|
||||
} else {
|
||||
this.accountData[event.getType()] = event;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user