Remove automatic rageshakes on UTD labs feature (#32778)

* Remove automatic rageshakes on UTD feature

* remove unused setting name

* Remove unused lab group

* Reduce count by 1
This commit is contained in:
Will Hunt
2026-03-13 13:55:30 +00:00
committed by GitHub
parent 09bbf796dc
commit 772a443486
13 changed files with 3 additions and 326 deletions
-1
View File
@@ -18,7 +18,6 @@
"https://scalar-staging.vector.im/api"
],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": false,
"room_directory": {
"servers": ["matrix.org", "gitter.im"]
-1
View File
@@ -18,7 +18,6 @@
"https://scalar-staging.vector.im/api"
],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": true,
"room_directory": {
"servers": ["matrix.org", "gitter.im"]
-2
View File
@@ -39,7 +39,6 @@ import { type SetupEncryptionStore } from "../stores/SetupEncryptionStore";
import { type RoomScrollStateStore } from "../stores/RoomScrollStateStore";
import { type ConsoleLogger, type IndexedDBLogStore } from "../rageshake/rageshake";
import type ActiveWidgetStore from "../stores/ActiveWidgetStore";
import type AutoRageshakeStore from "../stores/AutoRageshakeStore";
import { type IConfigOptions } from "../IConfigOptions";
import { type MatrixDispatcher } from "../dispatcher/dispatcher";
import { type DeepReadonly } from "./common";
@@ -85,7 +84,6 @@ declare global {
matrixChat?: MatrixChat;
mxSendSentryReport: (userText: string, issueUrl: string, error: Error) => Promise<void>;
mxLoginWithAccessToken: (hsUrl: string, accessToken: string) => Promise<void>;
mxAutoRageshakeStore?: AutoRageshakeStore;
mxDispatcher: MatrixDispatcher;
mxMatrixClientPeg: IMatrixClientPeg;
mxReactSdkConfig: DeepReadonly<IConfigOptions>;
-1
View File
@@ -108,7 +108,6 @@ export interface IConfigOptions {
* Bug report endpoint URL. "local" means the logs should not be uploaded.
*/
bug_report_endpoint_url?: typeof BugReportEndpointURLLocal | string; // omission disables bug reporting
uisi_autorageshake_app?: string; // defaults to "element-auto-uisi"
sentry?: {
dsn: string;
environment?: string; // "production", etc
-1
View File
@@ -22,7 +22,6 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
help_key_storage_url: "https://element.io/help#encryption5",
integrations_ui_url: "https://scalar.vector.im/",
integrations_rest_url: "https://scalar.vector.im/api",
uisi_autorageshake_app: "element-auto-uisi",
show_labs_settings: false,
force_verification: false,
@@ -44,7 +44,6 @@ import * as Rooms from "../../Rooms";
import * as Lifecycle from "../../Lifecycle";
// LifecycleStore is not used but does listen to and dispatch actions
import "../../stores/LifecycleStore";
import "../../stores/AutoRageshakeStore";
import PageType from "../../PageTypes";
import createRoom, { type IOpts } from "../../createRoom";
import { _t, _td } from "../../languageHandler";
@@ -75,16 +75,6 @@ export default class LabsUserSettingsTab extends React.Component<EmptyObject> {
.getOrCreate(LabGroup.Experimental, [])
.push(<SettingsFlag key="lowBandwidth" name="lowBandwidth" level={SettingLevel.DEVICE} />);
groups
.getOrCreate(LabGroup.Analytics, [])
.push(
<SettingsFlag
key="automaticDecryptionErrorReporting"
name="automaticDecryptionErrorReporting"
level={SettingLevel.DEVICE}
/>,
);
labsSections = (
<>
{sortBy(Array.from(groups.entries()), "0").map(([group, flags]) => (
-1
View File
@@ -1495,7 +1495,6 @@
},
"labs": {
"ask_to_join": "Enable ask to join",
"automatic_debug_logs_decryption": "Automatically send debug logs on decryption errors",
"beta_description": "What's next for %(brand)s? Labs are the best way to get things early, test out new features and help shape them before they actually launch.",
"beta_feature": "This is a beta feature",
"beta_feedback_leave_button": "To leave the beta, visit your settings.",
-9
View File
@@ -89,7 +89,6 @@ export enum LabGroup {
Threads,
VoiceAndVideo,
Moderation,
Analytics,
Themes,
Encryption,
Experimental,
@@ -110,7 +109,6 @@ export const labGroupNames: Record<LabGroup, TranslationKey> = {
[LabGroup.Threads]: _td("labs|group_threads"),
[LabGroup.VoiceAndVideo]: _td("labs|group_voip"),
[LabGroup.Moderation]: _td("labs|group_moderation"),
[LabGroup.Analytics]: _td("common|analytics"),
[LabGroup.Themes]: _td("labs|group_themes"),
[LabGroup.Encryption]: _td("labs|group_encryption"),
[LabGroup.Experimental]: _td("labs|group_experimental"),
@@ -348,7 +346,6 @@ export interface Settings {
"Spaces.enabledMetaSpaces": IBaseSetting<Partial<Record<MetaSpace, boolean>>>;
"Spaces.showPeopleInSpace": IBaseSetting<boolean>;
"developerMode": IBaseSetting<boolean>;
"automaticDecryptionErrorReporting": IBaseSetting<boolean>;
"debug_scroll_panel": IBaseSetting<boolean>;
"debug_timeline_panel": IBaseSetting<boolean>;
"debug_registration": IBaseSetting<boolean>;
@@ -1307,12 +1304,6 @@ export const SETTINGS: Settings = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: false,
},
"automaticDecryptionErrorReporting": {
displayName: _td("labs|automatic_debug_logs_decryption"),
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: false,
controller: new ReloadOnChangeController(),
},
"debug_scroll_panel": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: false,
-186
View File
@@ -1,186 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import {
ClientEvent,
type MatrixEvent,
MatrixEventEvent,
type SyncStateData,
type SyncState,
ToDeviceMessageId,
} from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";
import { v4 as uuidv4 } from "uuid";
import { logger } from "matrix-js-sdk/src/logger";
import SdkConfig from "../SdkConfig";
import sendBugReport from "../rageshake/submit-rageshake";
import defaultDispatcher from "../dispatcher/dispatcher";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import { type ActionPayload } from "../dispatcher/payloads";
import SettingsStore from "../settings/SettingsStore";
// Minimum interval of 1 minute between reports
const RAGESHAKE_INTERVAL = 60000;
// Before rageshaking, wait 5 seconds and see if the message has successfully decrypted
const GRACE_PERIOD = 5000;
// Event type for to-device messages requesting sender auto-rageshakes
const AUTO_RS_REQUEST = "im.vector.auto_rs_request";
interface IState {
reportedSessionIds: Set<string>;
lastRageshakeTime: number;
initialSyncCompleted: boolean;
}
/**
* Watches for decryption errors to auto-report if the relevant lab is
* enabled, and keeps track of session IDs that have already been
* reported.
*/
export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
const instance = new AutoRageshakeStore();
instance.start();
return instance;
})();
private constructor() {
super(defaultDispatcher, {
reportedSessionIds: new Set<string>(),
lastRageshakeTime: 0,
initialSyncCompleted: false,
});
this.onDecryptionAttempt = this.onDecryptionAttempt.bind(this);
this.onDeviceMessage = this.onDeviceMessage.bind(this);
this.onSyncStateChange = this.onSyncStateChange.bind(this);
}
public static get instance(): AutoRageshakeStore {
return AutoRageshakeStore.internalInstance;
}
protected async onAction(_payload: ActionPayload): Promise<void> {}
protected async onReady(): Promise<void> {
if (!SettingsStore.getValue("automaticDecryptionErrorReporting")) return;
if (this.matrixClient) {
this.matrixClient.on(MatrixEventEvent.Decrypted, this.onDecryptionAttempt);
this.matrixClient.on(ClientEvent.ToDeviceEvent, this.onDeviceMessage);
this.matrixClient.on(ClientEvent.Sync, this.onSyncStateChange);
}
}
protected async onNotReady(): Promise<void> {
if (this.matrixClient) {
this.matrixClient.removeListener(ClientEvent.ToDeviceEvent, this.onDeviceMessage);
this.matrixClient.removeListener(MatrixEventEvent.Decrypted, this.onDecryptionAttempt);
this.matrixClient.removeListener(ClientEvent.Sync, this.onSyncStateChange);
}
}
private onDecryptionAttempt = async (ev: MatrixEvent): Promise<void> => {
if (!this.state.initialSyncCompleted) {
return;
}
const wireContent = ev.getWireContent();
const sessionId = wireContent.session_id;
if (ev.isDecryptionFailure() && !this.state.reportedSessionIds.has(sessionId)) {
await sleep(GRACE_PERIOD);
if (!ev.isDecryptionFailure()) {
return;
}
const newReportedSessionIds = new Set(this.state.reportedSessionIds);
await this.updateState({ reportedSessionIds: newReportedSessionIds.add(sessionId) });
const now = new Date().getTime();
if (now - this.state.lastRageshakeTime < RAGESHAKE_INTERVAL) {
logger.info(
`Not sending recipient-side autorageshake for event ${ev.getId()}/session ${sessionId}: last rageshake was too recent`,
);
return;
}
await this.updateState({ lastRageshakeTime: now });
const senderUserId = ev.getSender()!;
const eventInfo = {
event_id: ev.getId(),
room_id: ev.getRoomId(),
session_id: sessionId,
device_id: wireContent.device_id,
user_id: senderUserId,
sender_key: wireContent.sender_key,
};
logger.info(`Sending recipient-side autorageshake for event ${ev.getId()}/session ${sessionId}`);
// XXX: the rageshake server returns the URL for the github issue... which is typically absent for
// auto-uisis, because we've disabled creation of GH issues for them. So the `recipient_rageshake`
// field is broken.
const rageshakeURL = await sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
userText: "Auto-reporting decryption error (recipient)",
sendLogs: true,
labels: ["Z-UISI", "web", "uisi-recipient"],
customApp: SdkConfig.get().uisi_autorageshake_app,
customFields: { auto_uisi: JSON.stringify(eventInfo) },
});
const messageContent = {
...eventInfo,
recipient_rageshake: rageshakeURL,
[ToDeviceMessageId]: uuidv4(),
};
this.matrixClient?.sendToDevice(
AUTO_RS_REQUEST,
new Map([[senderUserId, new Map([[messageContent.device_id, messageContent]])]]),
);
}
};
private onSyncStateChange = async (
_state: SyncState,
_prevState: SyncState | null,
data?: SyncStateData,
): Promise<void> => {
if (!this.state.initialSyncCompleted) {
await this.updateState({ initialSyncCompleted: !!data?.nextSyncToken });
}
};
private onDeviceMessage = async (ev: MatrixEvent): Promise<void> => {
if (ev.getType() !== AUTO_RS_REQUEST) return;
const messageContent = ev.getContent();
const recipientRageshake = messageContent["recipient_rageshake"] || "";
const now = new Date().getTime();
if (now - this.state.lastRageshakeTime > RAGESHAKE_INTERVAL) {
await this.updateState({ lastRageshakeTime: now });
logger.info(
`Sending sender-side autorageshake for event ${messageContent["event_id"]}/session ${messageContent["session_id"]}`,
);
await sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
userText: `Auto-reporting decryption error (sender)\nRecipient rageshake: ${recipientRageshake}`,
sendLogs: true,
labels: ["Z-UISI", "web", "uisi-sender"],
customApp: SdkConfig.get().uisi_autorageshake_app,
customFields: {
recipient_rageshake: recipientRageshake,
auto_uisi: JSON.stringify(messageContent),
},
});
} else {
logger.info(
`Not sending sender-side autorageshake for event ${messageContent["event_id"]}/session ${messageContent["session_id"]}: last rageshake was too recent`,
);
}
};
}
window.mxAutoRageshakeStore = AutoRageshakeStore.instance;
@@ -48,6 +48,6 @@ describe("<LabsUserSettingsTab />", () => {
// non-beta labs section
expect(screen.getByText("Early previews")).toBeInTheDocument();
const labsSections = container.getElementsByClassName("mx_SettingsSubsection");
expect(labsSections).toHaveLength(10);
expect(labsSections).toHaveLength(9);
});
});
@@ -1,107 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { mocked } from "jest-mock";
import {
ClientEvent,
EventType,
type MatrixClient,
type MatrixEvent,
MatrixEventEvent,
SyncState,
} from "matrix-js-sdk/src/matrix";
import SettingsStore from "../../../src/settings/SettingsStore";
import AutoRageshakeStore from "../../../src/stores/AutoRageshakeStore";
import { mkEvent, stubClient } from "../../test-utils";
jest.mock("../../../src/rageshake/submit-rageshake");
jest.mock("../../../src/stores/WidgetStore");
jest.mock("../../../src/stores/widgets/WidgetLayoutStore");
const TEST_SENDER = "@sender@example.com";
describe("AutoRageshakeStore", () => {
const roomId = "!room:example.com";
let client: MatrixClient;
let utdEvent: MatrixEvent;
let autoRageshakeStore: AutoRageshakeStore;
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
client = stubClient();
// @ts-ignore bypass private ctor for tests
autoRageshakeStore = new AutoRageshakeStore();
autoRageshakeStore.start();
utdEvent = mkEvent({
event: true,
content: {},
room: roomId,
user: TEST_SENDER,
type: EventType.RoomMessage,
});
jest.spyOn(utdEvent, "isDecryptionFailure").mockReturnValue(true);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("when the initial sync completed", () => {
beforeEach(() => {
client.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Stopped, { nextSyncToken: "abc123" });
});
describe("and an undecryptable event occurs", () => {
beforeEach(() => {
client.emit(MatrixEventEvent.Decrypted, utdEvent);
// simulate event grace period
jest.advanceTimersByTime(5500);
});
it("should send a to-device message", () => {
expect(mocked(client).sendToDevice.mock.calls).toEqual([
[
"im.vector.auto_rs_request",
new Map([
[
TEST_SENDER,
new Map([
[
undefined,
{
"device_id": undefined,
"event_id": utdEvent.getId(),
"org.matrix.msgid": expect.any(String),
"recipient_rageshake": undefined,
"room_id": "!room:example.com",
"sender_key": undefined,
"session_id": undefined,
"user_id": TEST_SENDER,
},
],
]),
],
]),
],
]);
});
});
});
});