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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 3 additions and 326 deletions

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"]

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"]

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>;

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

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,

View File

@ -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";

View File

@ -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]) => (

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.",

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,

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;

View File

@ -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);
});
});

View File

@ -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,
},
],
]),
],
]),
],
]);
});
});
});
});

View File

@ -407,11 +407,8 @@ If you run your own rageshake server to collect bug reports, the following optio
not present in the config, the app will disable all rageshake functionality. Set to `https://rageshakes.element.io/api/submit` to submit
rageshakes to us, or use your own rageshake server.
You may also set the value to `"local"` if you wish to only store logs locally, in order to download them for debugging.
2. `uisi_autorageshake_app`: If a user has enabled the "automatically send debug logs on decryption errors" flag, this option will be sent
alongside the rageshake so the rageshake server can filter them by app name. By default, this will be `element-auto-uisi`
(in contrast to other rageshakes submitted by the app, which use `element-web`).
3. `existing_issues_url`: URL for where to find existing issues.
4. `new_issue_url`: URL for where to submit new issues.
2. `existing_issues_url`: URL for where to find existing issues.
3. `new_issue_url`: URL for where to submit new issues.
If you would like to use [Sentry](https://sentry.io/) for rageshake data, add a `sentry` object to your config with the following values: