diff --git a/.changeset/add_command_for_msc4268_e2ee_history_sharing.md b/.changeset/add_command_for_msc4268_e2ee_history_sharing.md
new file mode 100644
index 00000000..5ef3a5f5
--- /dev/null
+++ b/.changeset/add_command_for_msc4268_e2ee_history_sharing.md
@@ -0,0 +1,5 @@
+---
+default: minor
+---
+
+added a `/sharehistory` command to [share encrypted history with a user](https://github.com/matrix-org/matrix-spec-proposals/blob/rav/proposal/encrypted_history_sharing/proposals/4268-encrypted-history-sharing.md)
diff --git a/src/app/features/settings/experimental/Experimental.tsx b/src/app/features/settings/experimental/Experimental.tsx
index 1760bc9c..5516afbf 100644
--- a/src/app/features/settings/experimental/Experimental.tsx
+++ b/src/app/features/settings/experimental/Experimental.tsx
@@ -4,6 +4,7 @@ import { InfoCard } from '$components/info-card';
import { LanguageSpecificPronouns } from '../cosmetics/LanguageSpecificPronouns';
import { Sync } from '../general';
import { BandwidthSavingEmojis } from './BandwithSavingEmojis';
+import { MSC4268HistoryShare } from './MSC4268HistoryShare';
type ExperimentalProps = {
requestClose: () => void;
@@ -43,6 +44,7 @@ export function Experimental({ requestClose }: ExperimentalProps) {
+
diff --git a/src/app/features/settings/experimental/MSC4268HistoryShare.tsx b/src/app/features/settings/experimental/MSC4268HistoryShare.tsx
new file mode 100644
index 00000000..d93a4103
--- /dev/null
+++ b/src/app/features/settings/experimental/MSC4268HistoryShare.tsx
@@ -0,0 +1,42 @@
+import { SequenceCard } from '$components/sequence-card';
+import { SettingTile } from '$components/setting-tile';
+import { useSetting } from '$state/hooks/settings';
+import { settingsAtom } from '$state/settings';
+import { Box, Switch, Text } from 'folds';
+import { SequenceCardStyle } from '../styles.css';
+
+export function MSC4268HistoryShare() {
+ const [enabledMSC4268Command, setEnabledMSC4268Command] = useSetting(
+ settingsAtom,
+ 'enableMSC4268CMD'
+ );
+
+ return (
+
+ Enable Sharing of Encrypted History
+
+
+ }
+ />
+
+
+ );
+}
diff --git a/src/app/hooks/useCommands.ts b/src/app/hooks/useCommands.ts
index 0c2008c0..b74daf70 100644
--- a/src/app/hooks/useCommands.ts
+++ b/src/app/hooks/useCommands.ts
@@ -10,7 +10,6 @@ import {
Visibility,
RoomServerAclEventContent,
MsgType,
- MatrixEvent,
} from '$types/matrix-sdk';
import { useMemo } from 'react';
import { Membership, StateEvent } from '$types/matrix/room';
@@ -32,6 +31,7 @@ import { settingsAtom } from '$state/settings';
import { useOpenBugReportModal } from '$state/hooks/bugReportModal';
import { createRoomEncryptionState } from '$components/create-room';
import { parsePronounsInput } from '$utils/pronouns';
+import { sendFeedback } from '$utils/sendFeedbackToUser';
import { useRoomNavigate } from './useRoomNavigate';
import { enrichWidgetUrl } from './useRoomWidgets';
import { useUserProfile } from './useUserProfile';
@@ -251,6 +251,8 @@ export enum Command {
Headpat = 'headpat',
// Meta
Report = 'bugreport',
+ // Experimental
+ ShareE2EEHistory = 'sharehistory',
}
export type CommandContent = {
@@ -264,6 +266,7 @@ export type CommandRecord = Record;
export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const { navigateRoom } = useRoomNavigate();
const [developerTools] = useSetting(settingsAtom, 'developerTools');
+ const [enableMSC4268CMD] = useSetting(settingsAtom, 'enableMSC4268CMD');
const profile = useUserProfile(mx.getSafeUserId());
const openBugReport = useOpenBugReportModal();
@@ -641,17 +644,6 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const input = payload.trim().toLowerCase();
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~sable-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
try {
if (input === 'reset' || input === 'clear') {
await mx.sendStateEvent(
@@ -660,7 +652,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{},
userId
);
- sendFeedback('Room color has been reset.');
+ sendFeedback('Room color has been reset.', room, userId);
return;
}
@@ -671,14 +663,16 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{ color: input },
userId
);
- sendFeedback(`Room color set to ${input}.`);
+ sendFeedback(`Room color set to ${input}.`, room, userId);
} else {
- sendFeedback('Invalid format. Use #RRGGBB.');
+ sendFeedback('Invalid format. Use #RRGGBB.', room, userId);
}
} catch (e: any) {
if (e.errcode === 'M_FORBIDDEN') {
sendFeedback(
- 'Permission Denied. An admin must enable "Room Colors" in Settings > Cosmetics in app.sable.moe or another supported client.'
+ 'Permission Denied. An admin must enable "Room Colors" in Settings > Cosmetics in app.sable.moe or another supported client.',
+ room,
+ userId
);
}
}
@@ -692,17 +686,6 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const input = payload.trim().toLowerCase();
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~sable-g-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
const parents = room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
@@ -719,7 +702,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{},
userId
);
- sendFeedback('Global space color reset.');
+ sendFeedback('Global space color reset.', room, userId);
return;
}
@@ -730,14 +713,16 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{ color: input },
userId
);
- sendFeedback(`Global space color set to ${input}.`);
+ sendFeedback(`Global space color set to ${input}.`, room, userId);
} else {
- sendFeedback('Invalid format. Use #RRGGBB.');
+ sendFeedback('Invalid format. Use #RRGGBB.', room, userId);
}
} catch (e: any) {
if (e.errcode === 'M_FORBIDDEN') {
sendFeedback(
- 'Permission Denied. An admin must enable "Space-Wide Colors" in Settings > Cosmetics in app.sable.moe or another supported client.'
+ 'Permission Denied. An admin must enable "Space-Wide Colors" in Settings > Cosmetics in app.sable.moe or another supported client.',
+ room,
+ userId
);
}
}
@@ -753,21 +738,10 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
.slice(0, 32);
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~font-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
try {
if (input.toLowerCase() === 'reset' || input === '') {
await mx.sendStateEvent(room.roomId, StateEvent.RoomCosmeticsFont as any, {}, userId);
- sendFeedback('Room font reset.');
+ sendFeedback('Room font reset.', room, userId);
return;
}
@@ -777,11 +751,13 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{ font: input },
userId
);
- sendFeedback(`Room font set to "${input}".`);
+ sendFeedback(`Room font set to "${input}".`, room, userId);
} catch (e: any) {
if (e.errcode === 'M_FORBIDDEN') {
sendFeedback(
- 'Permission Denied. An admin must enable "Room Fonts" in Settings > Cosmetics in app.sable.moe or another supported client.'
+ 'Permission Denied. An admin must enable "Room Fonts" in Settings > Cosmetics in app.sable.moe or another supported client.',
+ room,
+ userId
);
}
}
@@ -797,17 +773,6 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
.slice(0, 32);
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~sfont-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
const parents = room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
@@ -824,7 +789,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{},
userId
);
- sendFeedback('Space font reset.');
+ sendFeedback('Space font reset.', room, userId);
return;
}
@@ -834,11 +799,13 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{ font: input },
userId
);
- sendFeedback(`Space font set to "${input}".`);
+ sendFeedback(`Space font set to "${input}".`, room, userId);
} catch (e: any) {
if (e.errcode === 'M_FORBIDDEN') {
sendFeedback(
- 'Permission Denied. An admin must enable "Space-Wide Fonts" in Settings > Cosmetics in app.sable.moe or another supported client.'
+ 'Permission Denied. An admin must enable "Space-Wide Fonts" in Settings > Cosmetics in app.sable.moe or another supported client.',
+ room,
+ userId
);
}
}
@@ -850,23 +817,12 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
exe: async (payload) => {
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~nullptr-widget-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
const parts = payload.trim().split(/\s+/);
const url = parts[0];
const name = parts.slice(1).join(' ') || 'Widget';
if (!url) {
- sendFeedback('Usage: /addwidget [name]');
+ sendFeedback('Usage: /addwidget [name]', room, userId);
return;
}
@@ -874,7 +830,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
try {
parsedUrl = new URL(url);
} catch {
- sendFeedback('Invalid URL. Please provide a valid widget URL.');
+ sendFeedback('Invalid URL. Please provide a valid widget URL.', room, userId);
return;
}
@@ -892,14 +848,16 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
} as any,
widgetId
);
- sendFeedback(`Widget "${name}" added.`);
+ sendFeedback(`Widget "${name}" added.`, room, userId);
} catch (e: any) {
if (e.errcode === 'M_FORBIDDEN') {
sendFeedback(
- 'Permission denied. You need permission to manage widgets in this room.'
+ 'Permission denied. You need permission to manage widgets in this room.',
+ room,
+ userId
);
} else {
- sendFeedback(`Failed to add widget: ${e.message || 'Unknown error'}`);
+ sendFeedback(`Failed to add widget: ${e.message || 'Unknown error'}`, room, userId);
}
}
},
@@ -913,17 +871,6 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const rawInput = match ? match[1].trim() : payload.trim();
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~pronoun-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
try {
if (['reset', 'clear', ''].includes(rawInput.toLowerCase())) {
await mx.sendStateEvent(
@@ -932,7 +879,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{},
userId
);
- sendFeedback('Room pronouns have been reset.');
+ sendFeedback('Room pronouns have been reset.', room, userId);
return;
}
@@ -949,10 +896,10 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
.map((p) => (p.language ? `for ${p.language} "${p.summary}" was set` : p.summary))
.join(', ');
- sendFeedback(`Room pronouns set: ${feedbackString}`);
+ sendFeedback(`Room pronouns set: ${feedbackString}`, room, userId);
} catch (e: any) {
if (e.errcode === 'M_FORBIDDEN') {
- sendFeedback('Permission Denied. Could not update room pronouns.');
+ sendFeedback('Permission Denied. Could not update room pronouns.', room, userId);
}
}
},
@@ -966,17 +913,6 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const rawInput = match ? match[1].trim() : payload.trim();
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~gpronoun-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
const parents = room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
@@ -993,7 +929,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
{},
userId
);
- sendFeedback('Global space pronouns reset.');
+ sendFeedback('Global space pronouns reset.', room, userId);
return;
}
@@ -1010,10 +946,10 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
.map((p) => (p.language ? `for ${p.language} "${p.summary}" was set` : p.summary))
.join(', ');
- sendFeedback(`Global space pronouns set: ${feedbackString}`);
+ sendFeedback(`Global space pronouns set: ${feedbackString}`, room, userId);
} catch (e: any) {
if (e.errcode === 'M_FORBIDDEN') {
- sendFeedback('Permission Denied. Could not update space pronouns.');
+ sendFeedback('Permission Denied. Could not update space pronouns.', room, userId);
}
}
},
@@ -1039,25 +975,15 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
'[Dev only] Send raw message event. Example: /rawmsg {"msgtype":"m.text", "body":"hello"}',
exe: async (payload) => {
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~rawmsg-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
if (!developerTools) {
- sendFeedback('Command available in Developer Mode only.');
+ sendFeedback('Command available in Developer Mode only.', room, userId);
return;
}
try {
const content = JSON.parse(payload);
await mx.sendMessage(room.roomId, content);
} catch (e: any) {
- sendFeedback(`Invalid JSON: ${e.message}`);
+ sendFeedback(`Invalid JSON: ${e.message}`, room, userId);
}
},
},
@@ -1066,19 +992,9 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: '[Dev only] Send any raw event. Usage: /raw [-s stateKey]',
exe: async (payload) => {
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~rawevent-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- room.addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
if (!developerTools) {
- sendFeedback('Command available in Developer Mode only.');
+ sendFeedback('Command available in Developer Mode only.', room, userId);
return;
}
@@ -1090,7 +1006,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const jsonString = mainPayload.trim().substring(eventType.length).trim();
if (!eventType || !jsonString) {
- sendFeedback('Usage: /rawevent [-s stateKey]');
+ sendFeedback('Usage: /rawevent [-s stateKey]', room, userId);
return;
}
@@ -1099,13 +1015,17 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
if (typeof stateKey === 'string') {
await mx.sendStateEvent(room.roomId, eventType as any, content, stateKey);
- sendFeedback(`State event "${eventType}" sent with state key "${stateKey}".`);
+ sendFeedback(
+ `State event "${eventType}" sent with state key "${stateKey}".`,
+ room,
+ userId
+ );
} else {
await mx.sendEvent(room.roomId, eventType as any, content);
- sendFeedback(`Event "${eventType}" sent.`);
+ sendFeedback(`Event "${eventType}" sent.`, room, userId);
}
} catch (e: any) {
- sendFeedback(`Error: ${e.message}`);
+ sendFeedback(`Error: ${e.message}`, room, userId);
}
},
},
@@ -1114,26 +1034,16 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: '[Dev only] Merge global account data. Usage: /rawacc ',
exe: async (payload) => {
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~rawacc-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- (room as any).addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
if (!developerTools) {
- sendFeedback('Command available in Developer Mode only.');
+ sendFeedback('Command available in Developer Mode only.', room, userId);
return;
}
const trimmed = payload.trim();
const firstSpaceIndex = trimmed.indexOf(' ');
if (firstSpaceIndex === -1) {
- sendFeedback('Usage: /rawacc ');
+ sendFeedback('Usage: /rawacc ', room, userId);
return;
}
@@ -1149,9 +1059,9 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const mergedContent = { ...existingContent, ...newContent };
await mx.setAccountData(type as any, mergedContent);
- sendFeedback(`Account data "${type}" merged successfully.`);
+ sendFeedback(`Account data "${type}" merged successfully.`, room, userId);
} catch (e: any) {
- sendFeedback(`Error: ${e.message}`);
+ sendFeedback(`Error: ${e.message}`, room, userId);
}
},
},
@@ -1160,38 +1070,28 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: '[Dev Only] Remove a key from account data. Usage: /delacc ',
exe: async (payload) => {
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~removeacc-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- room.addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
const parts = payload.trim().split(/\s+/);
if (parts.length < 2) {
- sendFeedback('Usage: /delacc ');
+ sendFeedback('Usage: /delacc ', room, userId);
return;
}
const [type, key] = parts;
try {
const existingEvent = mx.getAccountData(type as any);
if (!existingEvent) {
- sendFeedback(`No account data found for type "${type}".`);
+ sendFeedback(`No account data found for type "${type}".`, room, userId);
return;
}
const content = { ...existingEvent.getContent() };
if (!(key in content)) {
- sendFeedback(`Key "${key}" not found in "${type}".`);
+ sendFeedback(`Key "${key}" not found in "${type}".`, room, userId);
return;
}
delete content[key];
await mx.setAccountData(type as any, content as any);
- sendFeedback(`Key "${key}" removed from "${type}".`);
+ sendFeedback(`Key "${key}" removed from "${type}".`, room, userId);
} catch (e: any) {
- sendFeedback(`Error: ${e.message}`);
+ sendFeedback(`Error: ${e.message}`, room, userId);
}
},
},
@@ -1200,23 +1100,13 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: '[Dev Only] Set an extended profile property. Usage: /setext ',
exe: async (payload) => {
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~setext-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- room.addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
if (!developerTools) {
- sendFeedback('Command available in Developer Mode only.');
+ sendFeedback('Command available in Developer Mode only.', room, userId);
return;
}
const parts = payload.trim().split(/\s+/);
if (parts.length < 2) {
- sendFeedback('Usage: /setext ');
+ sendFeedback('Usage: /setext ', room, userId);
return;
}
const key = parts[0];
@@ -1228,12 +1118,16 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
try {
if (typeof mx.setExtendedProfileProperty === 'function') {
await mx.setExtendedProfileProperty(key, finalValue);
- sendFeedback(`Extended profile property "${key}" set to: ${finalValue}`);
+ sendFeedback(
+ `Extended profile property "${key}" set to: ${finalValue}`,
+ room,
+ userId
+ );
} else {
- sendFeedback('Error: setExtendedProfileProperty is not supported.');
+ sendFeedback('Error: setExtendedProfileProperty is not supported.', room, userId);
}
} catch (e: any) {
- sendFeedback(`Failed to set extended profile: ${e.message}`);
+ sendFeedback(`Failed to set extended profile: ${e.message}`, room, userId);
}
},
},
@@ -1244,36 +1138,25 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const userId = mx.getSafeUserId();
const key = payload.trim();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~removeext-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- room.addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
-
if (!developerTools) {
- sendFeedback('Command available in Developer Mode only.');
+ sendFeedback('Command available in Developer Mode only.', room, userId);
return;
}
if (!key) {
- sendFeedback('Usage: /delext ');
+ sendFeedback('Usage: /delext ', room, userId);
return;
}
try {
if (typeof mx.deleteExtendedProfileProperty === 'function') {
await mx.deleteExtendedProfileProperty(key);
- sendFeedback(`Extended profile property "${key}" removed.`);
+ sendFeedback(`Extended profile property "${key}" removed.`, room, userId);
} else {
- sendFeedback('Error: setExtendedProfileProperty is not supported.');
+ sendFeedback('Error: setExtendedProfileProperty is not supported.', room, userId);
}
} catch (e: any) {
- sendFeedback(`Failed to remove property: ${e.message}`);
+ sendFeedback(`Failed to remove property: ${e.message}`, room, userId);
}
},
},
@@ -1282,28 +1165,57 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: 'Force discard the current outbound E2EE session in this room.',
exe: async () => {
const userId = mx.getSafeUserId();
- const sendFeedback = (msg: string) => {
- const localNotice = new MatrixEvent({
- type: 'm.room.message',
- content: { msgtype: 'm.notice', body: msg },
- event_id: `~discard-${Date.now()}`,
- room_id: room.roomId,
- sender: userId,
- });
- room.addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
- };
try {
const crypto = mx.getCrypto();
if (!crypto) {
- sendFeedback('Encryption is not enabled on this client.');
+ sendFeedback('Encryption is not enabled on this client.', room, userId);
return;
}
await crypto.forceDiscardSession(room.roomId);
- sendFeedback('Outbound encryption session discarded.');
+ sendFeedback('Outbound encryption session discarded.', room, userId);
} catch (e: any) {
- sendFeedback(`Failed to discard session: ${e.message}`);
+ sendFeedback(`Failed to discard session: ${e.message}`, room, userId);
+ }
+ },
+ },
+ // Sharing E2EE History of a room with a user
+ [Command.ShareE2EEHistory]: {
+ name: Command.ShareE2EEHistory,
+ description:
+ 'Share E2EE history (MSC4268) of this room with a user. Example: /sharee2eehistory @user:example.org',
+ exe: async (payload) => {
+ const targetUserId = payload.trim();
+ const { roomId } = room;
+ if (!enableMSC4268CMD) {
+ sendFeedback(
+ 'This command is disabled. Enable it under experimental settings to use it.',
+ room,
+ mx.getSafeUserId()
+ );
+ return;
}
+ if (!targetUserId) {
+ sendFeedback('Usage: /sharee2eehistory @user:example.org', room, mx.getSafeUserId());
+ return;
+ }
+ const crypto = mx.getCrypto();
+ if (!crypto) {
+ sendFeedback('Encryption is not enabled on this client.', room, mx.getSafeUserId());
+ return;
+ }
+ crypto
+ .shareRoomHistoryWithUser(roomId, targetUserId)
+ .then(() => {
+ sendFeedback(
+ `E2EE history shared with ${targetUserId}. (Their client needs to support MSC4268)`,
+ room,
+ mx.getSafeUserId()
+ );
+ })
+ .catch((e) => {
+ sendFeedback(`Failed to share E2EE history: ${e.message}`, room, mx.getSafeUserId());
+ });
},
},
// Cute Events
@@ -1393,7 +1305,16 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
},
},
}),
- [mx, navigateRoom, room, profile.displayName, profile.avatarUrl, developerTools, openBugReport]
+ [
+ mx,
+ navigateRoom,
+ room,
+ profile.displayName,
+ profile.avatarUrl,
+ developerTools,
+ enableMSC4268CMD,
+ openBugReport,
+ ]
);
return commands;
diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts
index a5e373a1..ee09b3e2 100644
--- a/src/app/state/settings.ts
+++ b/src/app/state/settings.ts
@@ -64,6 +64,7 @@ export interface Settings {
dateFormatString: string;
developerTools: boolean;
+ enableMSC4268CMD: boolean;
// Cosmetics!
jumboEmojiSize: JumboEmojiSize;
@@ -129,6 +130,8 @@ const defaultSettings: Settings = {
legacyUsernameColor: false,
allowPipVideos: false,
+ enableMSC4268CMD: false,
+
// Push notifications (SW/Sygnal): default on for mobile, opt-in on desktop.
// In-app pill banner: default on for mobile (primary foreground alert), opt-in on desktop.
// System (OS) notifications: desktop-only; hidden and disabled on mobile.
diff --git a/src/app/utils/sendFeedbackToUser.ts b/src/app/utils/sendFeedbackToUser.ts
new file mode 100644
index 00000000..c1fa7ab4
--- /dev/null
+++ b/src/app/utils/sendFeedbackToUser.ts
@@ -0,0 +1,12 @@
+import { MatrixEvent, Room } from 'matrix-js-sdk';
+
+export function sendFeedback(msg: string, room: Room, userId: string) {
+ const localNotice = new MatrixEvent({
+ type: 'm.room.message',
+ content: { msgtype: 'm.notice', body: msg },
+ event_id: `~sable-feedback-${Date.now()}`,
+ room_id: room.roomId,
+ sender: userId,
+ });
+ room.addLiveEvents([localNotice], { duplicateStrategy: 'ignore' } as any);
+}