diff --git a/apps/web/element.io/app/config.json b/apps/web/element.io/app/config.json index 4f7bde39fb8..032712a5d88 100644 --- a/apps/web/element.io/app/config.json +++ b/apps/web/element.io/app/config.json @@ -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"] diff --git a/apps/web/element.io/develop/config.json b/apps/web/element.io/develop/config.json index 7cfe5444782..2db410355c5 100644 --- a/apps/web/element.io/develop/config.json +++ b/apps/web/element.io/develop/config.json @@ -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"] diff --git a/apps/web/src/@types/global.d.ts b/apps/web/src/@types/global.d.ts index 38f3d9ba4f8..f6b9ff81f9a 100644 --- a/apps/web/src/@types/global.d.ts +++ b/apps/web/src/@types/global.d.ts @@ -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; mxLoginWithAccessToken: (hsUrl: string, accessToken: string) => Promise; - mxAutoRageshakeStore?: AutoRageshakeStore; mxDispatcher: MatrixDispatcher; mxMatrixClientPeg: IMatrixClientPeg; mxReactSdkConfig: DeepReadonly; diff --git a/apps/web/src/IConfigOptions.ts b/apps/web/src/IConfigOptions.ts index 9123078d5d2..fd39e2f8df8 100644 --- a/apps/web/src/IConfigOptions.ts +++ b/apps/web/src/IConfigOptions.ts @@ -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 diff --git a/apps/web/src/SdkConfig.ts b/apps/web/src/SdkConfig.ts index 27fc8033291..1a26e4f815e 100644 --- a/apps/web/src/SdkConfig.ts +++ b/apps/web/src/SdkConfig.ts @@ -22,7 +22,6 @@ export const DEFAULTS: DeepReadonly = { 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, diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index da23b01bef1..ce5bfd4598b 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -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"; diff --git a/apps/web/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx index d7919b352cf..ab0bfd6314f 100644 --- a/apps/web/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx @@ -75,16 +75,6 @@ export default class LabsUserSettingsTab extends React.Component { .getOrCreate(LabGroup.Experimental, []) .push(); - groups - .getOrCreate(LabGroup.Analytics, []) - .push( - , - ); - labsSections = ( <> {sortBy(Array.from(groups.entries()), "0").map(([group, flags]) => ( diff --git a/apps/web/src/i18n/strings/en_EN.json b/apps/web/src/i18n/strings/en_EN.json index 5a996dd4df9..9237fdbcce9 100644 --- a/apps/web/src/i18n/strings/en_EN.json +++ b/apps/web/src/i18n/strings/en_EN.json @@ -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.", diff --git a/apps/web/src/settings/Settings.tsx b/apps/web/src/settings/Settings.tsx index f5030bf5892..4294279d173 100644 --- a/apps/web/src/settings/Settings.tsx +++ b/apps/web/src/settings/Settings.tsx @@ -89,7 +89,6 @@ export enum LabGroup { Threads, VoiceAndVideo, Moderation, - Analytics, Themes, Encryption, Experimental, @@ -110,7 +109,6 @@ export const labGroupNames: Record = { [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>>; "Spaces.showPeopleInSpace": IBaseSetting; "developerMode": IBaseSetting; - "automaticDecryptionErrorReporting": IBaseSetting; "debug_scroll_panel": IBaseSetting; "debug_timeline_panel": IBaseSetting; "debug_registration": IBaseSetting; @@ -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, diff --git a/apps/web/src/stores/AutoRageshakeStore.ts b/apps/web/src/stores/AutoRageshakeStore.ts deleted file mode 100644 index ddac3571aab..00000000000 --- a/apps/web/src/stores/AutoRageshakeStore.ts +++ /dev/null @@ -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; - 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 { - private static readonly internalInstance = (() => { - const instance = new AutoRageshakeStore(); - instance.start(); - return instance; - })(); - - private constructor() { - super(defaultDispatcher, { - reportedSessionIds: new Set(), - 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 {} - - protected async onReady(): Promise { - 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 { - 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 => { - 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 => { - if (!this.state.initialSyncCompleted) { - await this.updateState({ initialSyncCompleted: !!data?.nextSyncToken }); - } - }; - - private onDeviceMessage = async (ev: MatrixEvent): Promise => { - 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; diff --git a/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx b/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx index 17bc426a405..14dad87f00f 100644 --- a/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx +++ b/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx @@ -48,6 +48,6 @@ describe("", () => { // non-beta labs section expect(screen.getByText("Early previews")).toBeInTheDocument(); const labsSections = container.getElementsByClassName("mx_SettingsSubsection"); - expect(labsSections).toHaveLength(10); + expect(labsSections).toHaveLength(9); }); }); diff --git a/apps/web/test/unit-tests/stores/AutoRageshakeStore-test.ts b/apps/web/test/unit-tests/stores/AutoRageshakeStore-test.ts deleted file mode 100644 index e7f15233f40..00000000000 --- a/apps/web/test/unit-tests/stores/AutoRageshakeStore-test.ts +++ /dev/null @@ -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, - }, - ], - ]), - ], - ]), - ], - ]); - }); - }); - }); -}); diff --git a/docs/config.md b/docs/config.md index f7b2fd6e8da..cc67343a1eb 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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: