refactor(types): typed send helpers replace 16 as any casts (#210)
- sendRoomMessage (composer's fire-and-forget sends: text, location, voice, files, GIFs) and sendRoomEvent (polls, poll responses/ends, forwards, reactions, edits) in utils/room.ts carry the one cast each needs (`keyof TimelineEvents` / `RoomMessageEventContent`, no `any`). - sendRoomMessage also swallows the rejected promise: a failed send already shows on the local echo (Failed to send + Retry, or the consent prompt), so it no longer surfaces as an unhandled error in the console. - getAccountData narrows to `keyof AccountDataEvents`; ForwardMessageDialog's guard now narrows `contentToSend` itself (same behaviour). - `as any` 39 → 23; eslint warnings 46 → 36, ratchet tightened to 36. Verified in Chromium on a local Synapse: a text message, a quick reaction and an edit all reach the server with the right content; a consent-blocked send no longer logs an unhandled MatrixError. 1219 unit tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
39589b40f3
commit
e39714a6e0
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "npm run check:eslint && npm run check:prettier",
|
"lint": "npm run check:eslint && npm run check:prettier",
|
||||||
"check:eslint": "eslint src/* --max-warnings 46",
|
"check:eslint": "eslint src/* --max-warnings 36",
|
||||||
"check:prettier": "prettier --check .",
|
"check:prettier": "prettier --check .",
|
||||||
"fix:prettier": "prettier --write .",
|
"fix:prettier": "prettier --write .",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import React, { KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Box, Chip, color, config, Icon, Icons, Text, toRem } from 'folds';
|
import { Box, Chip, color, config, Icon, Icons, Text, toRem } from 'folds';
|
||||||
import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations';
|
import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations';
|
||||||
import { MatrixEvent, Room, RoomEvent, PollEvent } from 'matrix-js-sdk';
|
import { MatrixEvent, Room, RoomEvent, PollEvent } from 'matrix-js-sdk';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getMemberName } from '../../../utils/room';
|
import { getMemberName, sendRoomEvent } from '../../../utils/room';
|
||||||
import {
|
import {
|
||||||
ParsedPoll,
|
ParsedPoll,
|
||||||
PollResponse,
|
PollResponse,
|
||||||
@@ -220,7 +219,7 @@ export function PollContent({
|
|||||||
setPending(next);
|
setPending(next);
|
||||||
// Send the STABLE m.poll.response (matches Lotus's stable m.poll.start; the reader
|
// Send the STABLE m.poll.response (matches Lotus's stable m.poll.start; the reader
|
||||||
// accepts both namespaces).
|
// accepts both namespaces).
|
||||||
mx.sendEvent(roomId, 'm.poll.response' as any, {
|
sendRoomEvent(mx, roomId, null, 'm.poll.response', {
|
||||||
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
|
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
|
||||||
'm.selections': Array.from(next),
|
'm.selections': Array.from(next),
|
||||||
}).catch(() => setPending(null));
|
}).catch(() => setPending(null));
|
||||||
@@ -229,7 +228,7 @@ export function PollContent({
|
|||||||
const handleEndPoll = () => {
|
const handleEndPoll = () => {
|
||||||
if (!roomId || !eventId || ending) return;
|
if (!roomId || !eventId || ending) return;
|
||||||
setEnding(true);
|
setEnding(true);
|
||||||
mx.sendEvent(roomId, 'm.poll.end' as any, {
|
sendRoomEvent(mx, roomId, null, 'm.poll.end', {
|
||||||
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
|
'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
|
||||||
'm.poll.end': {},
|
'm.poll.end': {},
|
||||||
'm.text': 'The poll has ended.',
|
'm.text': 'The poll has ended.',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { Room } from 'matrix-js-sdk';
|
|||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { useModalStyle } from '../../hooks/useModalStyle';
|
import { useModalStyle } from '../../hooks/useModalStyle';
|
||||||
|
import { sendRoomEvent } from '../../utils/room';
|
||||||
|
|
||||||
interface PollCreatorProps {
|
interface PollCreatorProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
@@ -89,7 +90,7 @@ export function PollCreator({ roomId, threadRootId, onClose }: PollCreatorProps)
|
|||||||
);
|
);
|
||||||
// Pass the thread id explicitly (like the sticker path in RoomInput); the
|
// Pass the thread id explicitly (like the sticker path in RoomInput); the
|
||||||
// legacy 3-arg form always resolves to the main timeline.
|
// legacy 3-arg form always resolves to the main timeline.
|
||||||
await mx.sendEvent(roomId, threadRootId ?? null, 'm.poll.start' as any, {
|
await sendRoomEvent(mx, roomId, threadRootId ?? null, 'm.poll.start', {
|
||||||
'm.poll': {
|
'm.poll': {
|
||||||
question: { 'm.text': trimmedQuestion },
|
question: { 'm.text': trimmedQuestion },
|
||||||
answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })),
|
answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })),
|
||||||
|
|||||||
@@ -112,7 +112,12 @@ import {
|
|||||||
getImageMsgContent,
|
getImageMsgContent,
|
||||||
getVideoMsgContent,
|
getVideoMsgContent,
|
||||||
} from './msgContent';
|
} from './msgContent';
|
||||||
import { getMemberName, getMentionContent, trimReplyFromBody } from '../../utils/room';
|
import {
|
||||||
|
getMemberName,
|
||||||
|
getMentionContent,
|
||||||
|
sendRoomMessage,
|
||||||
|
trimReplyFromBody,
|
||||||
|
} from '../../utils/room';
|
||||||
import { CommandAutocomplete } from './CommandAutocomplete';
|
import { CommandAutocomplete } from './CommandAutocomplete';
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
@@ -324,7 +329,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
// MSC3488 extensible location: send the geo_uri (legacy) alongside the
|
// MSC3488 extensible location: send the geo_uri (legacy) alongside the
|
||||||
// m.location/m.asset/m.ts blocks so Element and other clients render a
|
// m.location/m.asset/m.ts blocks so Element and other clients render a
|
||||||
// proper "shared location" pin instead of falling back to plain text.
|
// proper "shared location" pin instead of falling back to plain text.
|
||||||
mx.sendMessage(roomId, threadRootId ?? null, {
|
sendRoomMessage(mx, roomId, threadRootId ?? null, {
|
||||||
msgtype: 'm.location',
|
msgtype: 'm.location',
|
||||||
body: `Shared a location: ${geoUri}`,
|
body: `Shared a location: ${geoUri}`,
|
||||||
geo_uri: geoUri,
|
geo_uri: geoUri,
|
||||||
@@ -332,7 +337,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
'org.matrix.msc3488.asset': { type: 'm.self' },
|
'org.matrix.msc3488.asset': { type: 'm.self' },
|
||||||
'org.matrix.msc3488.ts': ts,
|
'org.matrix.msc3488.ts': ts,
|
||||||
'm.ts': ts,
|
'm.ts': ts,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
setLocating(false);
|
setLocating(false);
|
||||||
@@ -363,19 +368,19 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
if (room.hasEncryptionStateEvent()) {
|
if (room.hasEncryptionStateEvent()) {
|
||||||
const { encInfo, file: encBlob } = await encryptFile(blob);
|
const { encInfo, file: encBlob } = await encryptFile(blob);
|
||||||
const uploadResult = await mx.uploadContent(encBlob);
|
const uploadResult = await mx.uploadContent(encBlob);
|
||||||
mx.sendMessage(roomId, threadRootId ?? null, {
|
sendRoomMessage(mx, roomId, threadRootId ?? null, {
|
||||||
...baseContent,
|
...baseContent,
|
||||||
file: { ...encInfo, url: uploadResult.content_uri },
|
file: { ...encInfo, url: uploadResult.content_uri },
|
||||||
} as any);
|
});
|
||||||
} else {
|
} else {
|
||||||
const uploadResult = await mx.uploadContent(blob, {
|
const uploadResult = await mx.uploadContent(blob, {
|
||||||
name: 'voice-message.ogg',
|
name: 'voice-message.ogg',
|
||||||
type: mimeType,
|
type: mimeType,
|
||||||
});
|
});
|
||||||
mx.sendMessage(roomId, threadRootId ?? null, {
|
sendRoomMessage(mx, roomId, threadRootId ?? null, {
|
||||||
...baseContent,
|
...baseContent,
|
||||||
url: uploadResult.content_uri,
|
url: uploadResult.content_uri,
|
||||||
} as any);
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mx, room, roomId, threadRootId],
|
[mx, room, roomId, threadRootId],
|
||||||
@@ -625,7 +630,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
});
|
});
|
||||||
handleCancelUpload(uploads);
|
handleCancelUpload(uploads);
|
||||||
const contents = fulfilledPromiseSettledResult(await Promise.allSettled(contentsPromises));
|
const contents = fulfilledPromiseSettledResult(await Promise.allSettled(contentsPromises));
|
||||||
contents.forEach((content) => mx.sendMessage(roomId, threadRootId ?? null, content as any));
|
contents.forEach((content) => sendRoomMessage(mx, roomId, threadRootId ?? null, content));
|
||||||
},
|
},
|
||||||
[mx, roomId, threadRootId, selectedFiles, handleCancelUpload],
|
[mx, roomId, threadRootId, selectedFiles, handleCancelUpload],
|
||||||
);
|
);
|
||||||
@@ -732,7 +737,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
content['m.relates_to'].is_falling_back = false;
|
content['m.relates_to'].is_falling_back = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mx.sendMessage(roomId, threadRootId ?? null, content as any);
|
sendRoomMessage(mx, roomId, threadRootId ?? null, content);
|
||||||
resetEditor(editor);
|
resetEditor(editor);
|
||||||
resetEditorHistory(editor);
|
resetEditorHistory(editor);
|
||||||
setCharCount(0);
|
setCharCount(0);
|
||||||
@@ -955,10 +960,10 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
const uploadRes = await mx.uploadContent(encBlob);
|
const uploadRes = await mx.uploadContent(encBlob);
|
||||||
const mxcUrl = (uploadRes as { content_uri: string }).content_uri;
|
const mxcUrl = (uploadRes as { content_uri: string }).content_uri;
|
||||||
if (!mxcUrl) return;
|
if (!mxcUrl) return;
|
||||||
mx.sendMessage(roomId, threadRootId ?? null, {
|
sendRoomMessage(mx, roomId, threadRootId ?? null, {
|
||||||
...baseContent,
|
...baseContent,
|
||||||
file: { ...encInfo, url: mxcUrl },
|
file: { ...encInfo, url: mxcUrl },
|
||||||
} as any);
|
});
|
||||||
} else {
|
} else {
|
||||||
const uploadRes = await mx.uploadContent(gifFile, {
|
const uploadRes = await mx.uploadContent(gifFile, {
|
||||||
type: 'image/gif',
|
type: 'image/gif',
|
||||||
@@ -967,10 +972,10 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
});
|
});
|
||||||
const mxcUrl = (uploadRes as { content_uri: string }).content_uri;
|
const mxcUrl = (uploadRes as { content_uri: string }).content_uri;
|
||||||
if (!mxcUrl) return;
|
if (!mxcUrl) return;
|
||||||
mx.sendMessage(roomId, threadRootId ?? null, {
|
sendRoomMessage(mx, roomId, threadRootId ?? null, {
|
||||||
...baseContent,
|
...baseContent,
|
||||||
url: mxcUrl,
|
url: mxcUrl,
|
||||||
} as any);
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('GIF send failed:', e instanceof Error ? e.message : 'unknown error');
|
console.error('GIF send failed:', e instanceof Error ? e.message : 'unknown error');
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ import {
|
|||||||
getReactionContent,
|
getReactionContent,
|
||||||
isMembershipChanged,
|
isMembershipChanged,
|
||||||
reactionOrEditEvent,
|
reactionOrEditEvent,
|
||||||
|
sendRoomEvent,
|
||||||
} from '../../utils/room';
|
} from '../../utils/room';
|
||||||
import { getLastEditDiff } from '../../utils/editDiff';
|
import { getLastEditDiff } from '../../utils/editDiff';
|
||||||
import { tick } from '../../utils/haptics';
|
import { tick } from '../../utils/haptics';
|
||||||
@@ -1160,9 +1161,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||||||
shortcode ||
|
shortcode ||
|
||||||
(reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined);
|
(reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined);
|
||||||
tick('reaction', hapticFeedback);
|
tick('reaction', hapticFeedback);
|
||||||
mx.sendEvent(
|
sendRoomEvent(
|
||||||
|
mx,
|
||||||
room.roomId,
|
room.roomId,
|
||||||
MessageEvent.Reaction as any,
|
null,
|
||||||
|
MessageEvent.Reaction,
|
||||||
getReactionContent(targetEventId, key, rShortcode),
|
getReactionContent(targetEventId, key, rShortcode),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ import { mxcUrlToHttp } from '../../../utils/matrix';
|
|||||||
import { RoomAvatar, RoomIcon } from '../../../components/room-avatar';
|
import { RoomAvatar, RoomIcon } from '../../../components/room-avatar';
|
||||||
import { UserAvatar } from '../../../components/user-avatar';
|
import { UserAvatar } from '../../../components/user-avatar';
|
||||||
import { ThumbnailContent } from '../../../components/message/content/ThumbnailContent';
|
import { ThumbnailContent } from '../../../components/message/content/ThumbnailContent';
|
||||||
import { getMemberAvatarMxc, getMemberName, trimReplyFromBody } from '../../../utils/room';
|
import {
|
||||||
|
getMemberAvatarMxc,
|
||||||
|
getMemberName,
|
||||||
|
sendRoomEvent,
|
||||||
|
trimReplyFromBody,
|
||||||
|
} from '../../../utils/room';
|
||||||
import { nameInitials } from '../../../utils/common';
|
import { nameInitials } from '../../../utils/common';
|
||||||
import {
|
import {
|
||||||
recentForwardTargetsAtom,
|
recentForwardTargetsAtom,
|
||||||
@@ -349,12 +354,12 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
|||||||
// content; unencrypted ones get the plaintext version, or fail outright if
|
// content; unencrypted ones get the plaintext version, or fail outright if
|
||||||
// that couldn't be built — never fall back to sending the encrypted `file`.
|
// that couldn't be built — never fall back to sending the encrypted `file`.
|
||||||
const contentToSend = fwdContent.file && !destEncrypted ? plaintextContent : fwdContent;
|
const contentToSend = fwdContent.file && !destEncrypted ? plaintextContent : fwdContent;
|
||||||
if (fwdContent.file && !destEncrypted && !contentToSend) {
|
// Only undefined when a plaintext copy was needed and couldn't be built.
|
||||||
|
if (!contentToSend) {
|
||||||
return Promise.reject(new Error(plaintextContentError));
|
return Promise.reject(new Error(plaintextContentError));
|
||||||
}
|
}
|
||||||
// threadId-aware overload (P3-8): explicit null = send to the main timeline.
|
// Explicit null thread = send to the main timeline.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const sendForward = () => sendRoomEvent(mx, id, null, mEvent.getType(), contentToSend);
|
||||||
const sendForward = () => mx.sendEvent(id, null, mEvent.getType() as any, contentToSend);
|
|
||||||
// Send the optional comment first so it reads as a note above the
|
// Send the optional comment first so it reads as a note above the
|
||||||
// forwarded content. The room counts as failed if either send rejects.
|
// forwarded content. The room counts as failed if either send rejects.
|
||||||
// Track rooms whose comment already landed so a retry (after the FORWARD
|
// Track rooms whose comment already landed so a retry (after the FORWARD
|
||||||
|
|||||||
@@ -21,7 +21,15 @@ import {
|
|||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { Editor, Transforms } from 'slate';
|
import { Editor, Transforms } from 'slate';
|
||||||
import { ReactEditor } from 'slate-react';
|
import { ReactEditor } from 'slate-react';
|
||||||
import { IContent, IMentions, MatrixEvent, MsgType, RelationType, Room } from 'matrix-js-sdk';
|
import {
|
||||||
|
EventType,
|
||||||
|
IContent,
|
||||||
|
IMentions,
|
||||||
|
MatrixEvent,
|
||||||
|
MsgType,
|
||||||
|
RelationType,
|
||||||
|
Room,
|
||||||
|
} from 'matrix-js-sdk';
|
||||||
import { isKeyHotkey } from 'is-hotkey';
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import {
|
import {
|
||||||
AUTOCOMPLETE_PREFIXES,
|
AUTOCOMPLETE_PREFIXES,
|
||||||
@@ -55,6 +63,7 @@ import {
|
|||||||
getEditedEvent,
|
getEditedEvent,
|
||||||
getMemberName,
|
getMemberName,
|
||||||
getMentionContent,
|
getMentionContent,
|
||||||
|
sendRoomEvent,
|
||||||
trimReplyFromFormattedBody,
|
trimReplyFromFormattedBody,
|
||||||
} from '../../../utils/room';
|
} from '../../../utils/room';
|
||||||
import { mobileOrTablet } from '../../../utils/user-agent';
|
import { mobileOrTablet } from '../../../utils/user-agent';
|
||||||
@@ -192,8 +201,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
|
|||||||
rel_type: RelationType.Replace,
|
rel_type: RelationType.Replace,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
return sendRoomEvent(mx, roomId, null, EventType.RoomMessage, content);
|
||||||
return mx.sendMessage(roomId, content as any);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [prevBody, prevCustomHtml, prevMentions] = getPrevBodyAndFormattedBody();
|
const [prevBody, prevCustomHtml, prevMentions] = getPrevBodyAndFormattedBody();
|
||||||
@@ -241,8 +249,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
return sendRoomEvent(mx, roomId, null, EventType.RoomMessage, content);
|
||||||
return mx.sendMessage(roomId, content as any);
|
|
||||||
}, [
|
}, [
|
||||||
stripTracking,
|
stripTracking,
|
||||||
mx,
|
mx,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import {
|
|||||||
getMemberName,
|
getMemberName,
|
||||||
getReactionContent,
|
getReactionContent,
|
||||||
reactionOrEditEvent,
|
reactionOrEditEvent,
|
||||||
|
sendRoomEvent,
|
||||||
} from '../../../utils/room';
|
} from '../../../utils/room';
|
||||||
import { useSetting } from '../../../state/hooks/settings';
|
import { useSetting } from '../../../state/hooks/settings';
|
||||||
import { MessageLayout, settingsAtom } from '../../../state/settings';
|
import { MessageLayout, settingsAtom } from '../../../state/settings';
|
||||||
@@ -576,12 +577,12 @@ export function ThreadTimeline({ room, thread, editor }: ThreadTimelineProps) {
|
|||||||
shortcode ||
|
shortcode ||
|
||||||
(reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined);
|
(reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined);
|
||||||
tick('reaction', hapticFeedback);
|
tick('reaction', hapticFeedback);
|
||||||
mx.sendEvent(
|
sendRoomEvent(
|
||||||
|
mx,
|
||||||
room.roomId,
|
room.roomId,
|
||||||
// A reaction on the root is a main-timeline event, not a thread reply.
|
// A reaction on the root is a main-timeline event, not a thread reply.
|
||||||
isRoot ? null : thread.id,
|
isRoot ? null : thread.id,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
MessageEvent.Reaction,
|
||||||
MessageEvent.Reaction as any,
|
|
||||||
getReactionContent(targetEventId, key, rShortcode),
|
getReactionContent(targetEventId, key, rShortcode),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+38
-1
@@ -1,9 +1,11 @@
|
|||||||
import { IconName, IconSrc } from 'folds';
|
import { IconName, IconSrc } from 'folds';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AccountDataEvents,
|
||||||
EventTimeline,
|
EventTimeline,
|
||||||
EventTimelineSet,
|
EventTimelineSet,
|
||||||
EventType,
|
EventType,
|
||||||
|
IContent,
|
||||||
IMentions,
|
IMentions,
|
||||||
IPowerLevelsContent,
|
IPowerLevelsContent,
|
||||||
IPushRule,
|
IPushRule,
|
||||||
@@ -17,8 +19,10 @@ import {
|
|||||||
RelationType,
|
RelationType,
|
||||||
Room,
|
Room,
|
||||||
RoomMember,
|
RoomMember,
|
||||||
|
TimelineEvents,
|
||||||
} from 'matrix-js-sdk';
|
} from 'matrix-js-sdk';
|
||||||
import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend';
|
import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend';
|
||||||
|
import type { RoomMessageEventContent } from 'matrix-js-sdk/lib/@types/events';
|
||||||
import { AccountDataEvent } from '../../types/matrix/accountData';
|
import { AccountDataEvent } from '../../types/matrix/accountData';
|
||||||
import {
|
import {
|
||||||
IRoomCreateContent,
|
IRoomCreateContent,
|
||||||
@@ -62,10 +66,43 @@ export function sendStateEvent<T extends object>(
|
|||||||
return mx.sendStateEvent(roomId, eventType as any, content, stateKey);
|
return mx.sendStateEvent(roomId, eventType as any, content, stateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Typed message send for the composer's fire-and-forget sends. The SDK's
|
||||||
|
// `sendMessage` wants its `RoomMessageEventContent` union, which the composer's
|
||||||
|
// extensible content (MSC3488 location, MSC3245 voice, `m.mentions`, encrypted
|
||||||
|
// `file`) doesn't narrow to; the single cast lives here. A failed send already
|
||||||
|
// shows on the local echo ("Failed to send" with Retry, or the consent prompt),
|
||||||
|
// so the rejection is swallowed rather than surfacing as an unhandled error.
|
||||||
|
export function sendRoomMessage(
|
||||||
|
mx: MatrixClient,
|
||||||
|
roomId: string,
|
||||||
|
threadId: string | null,
|
||||||
|
content: IContent,
|
||||||
|
): void {
|
||||||
|
mx.sendMessage(roomId, threadId, content as RoomMessageEventContent).catch(() => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typed send for events whose type isn't one of the SDK's typed timeline events
|
||||||
|
// (MSC3381 polls, forwarded arbitrary types, the fork's reaction enum) or whose
|
||||||
|
// content is extensible. Keeps the one cast out of every call site.
|
||||||
|
export function sendRoomEvent(
|
||||||
|
mx: MatrixClient,
|
||||||
|
roomId: string,
|
||||||
|
threadId: string | null,
|
||||||
|
eventType: string,
|
||||||
|
content: IContent,
|
||||||
|
): Promise<ISendEventResponse> {
|
||||||
|
return mx.sendEvent(
|
||||||
|
roomId,
|
||||||
|
threadId,
|
||||||
|
eventType as keyof TimelineEvents,
|
||||||
|
content as TimelineEvents[keyof TimelineEvents],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const getAccountData = (
|
export const getAccountData = (
|
||||||
mx: MatrixClient,
|
mx: MatrixClient,
|
||||||
eventType: AccountDataEvent | string,
|
eventType: AccountDataEvent | string,
|
||||||
): MatrixEvent | undefined => mx.getAccountData(eventType as any);
|
): MatrixEvent | undefined => mx.getAccountData(eventType as keyof AccountDataEvents);
|
||||||
|
|
||||||
export const getMDirects = (mDirectEvent: MatrixEvent): Set<string> => {
|
export const getMDirects = (mDirectEvent: MatrixEvent): Set<string> => {
|
||||||
const roomIds = new Set<string>();
|
const roomIds = new Set<string>();
|
||||||
|
|||||||
Reference in New Issue
Block a user