mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
fix(app): submit message edits in background
This commit is contained in:
1 parent
c0048504db
commit
8e17970632
6 files changed
+160
-214
No files matched your search
@@ -10,15 +10,11 @@ import * as DraftCommands from '@app/features/messaging/commands/DraftCommands';
|
||||
import * as MessageCommands from '@app/features/messaging/commands/MessageCommands';
|
||||
import {AttachmentUploadConnectivityModal} from '@app/features/messaging/components/alerts/AttachmentUploadConnectivityModal';
|
||||
import {FileSizeTooLargeModal} from '@app/features/messaging/components/alerts/FileSizeTooLargeModal';
|
||||
import {MessageEditFailedModal} from '@app/features/messaging/components/alerts/MessageEditFailedModal';
|
||||
import {MessageEditTooQuickModal} from '@app/features/messaging/components/alerts/MessageEditTooQuickModal';
|
||||
import {MessageSendFailedModal} from '@app/features/messaging/components/alerts/MessageSendFailedModal';
|
||||
import {MessageSendTooQuickModal} from '@app/features/messaging/components/alerts/MessageSendTooQuickModal';
|
||||
import {
|
||||
type MessageLocalSendRateLimitState,
|
||||
type MessageQueueRequestOutcomeStatus,
|
||||
resolveMessageLocalSendRateLimitDecision,
|
||||
resolveMessageQueuePayloadRouteDecision,
|
||||
resolveMessageQueueRequestOutcomeDecision,
|
||||
resolveMessageQueueSendExecutionDecision,
|
||||
} from '@app/features/messaging/state/MessageQueueStateMachine';
|
||||
@@ -33,11 +29,8 @@ import {exceedsMultipartFallbackRequestSize} from '@app/features/messaging/utils
|
||||
import {prepareAttachmentsForNonce} from '@app/features/messaging/utils/MessageAttachmentUtils';
|
||||
import {
|
||||
type ApiAttachmentMetadata,
|
||||
type ApiMessageEditAttachmentMetadata,
|
||||
buildMessageCreateRequest,
|
||||
type MessageCreateRequest,
|
||||
type MessageEditRequest,
|
||||
normalizeMessageEditContent,
|
||||
} from '@app/features/messaging/utils/MessageRequestUtils';
|
||||
import {resolveRetryAfterMs} from '@app/features/messaging/utils/RetryAfterUtils';
|
||||
import {MatureContentRejectedModal} from '@app/features/moderation/components/alerts/MatureContentRejectedModal';
|
||||
@@ -120,17 +113,7 @@ interface SendMessagePayload extends BaseMessagePayload {
|
||||
tts?: boolean;
|
||||
}
|
||||
|
||||
interface EditMessagePayload extends BaseMessagePayload {
|
||||
type: 'edit';
|
||||
messageId: string;
|
||||
rateLimitRetryCount?: number;
|
||||
content?: string;
|
||||
allowedMentions?: AllowedMentions;
|
||||
flags?: number;
|
||||
attachments?: Array<ApiMessageEditAttachmentMetadata>;
|
||||
}
|
||||
|
||||
export type MessageQueuePayload = SendMessagePayload | EditMessagePayload;
|
||||
export type MessageQueuePayload = SendMessagePayload;
|
||||
type MessageQueueCompletion<TResult> = {
|
||||
retry: RetryError | null;
|
||||
result?: TResult;
|
||||
@@ -260,11 +243,6 @@ function isRateLimitError(error: HttpError): boolean {
|
||||
return !isSlowmodeError(error);
|
||||
}
|
||||
|
||||
function getRequestErrorOutcomeStatus(error: HttpError): MessageQueueRequestOutcomeStatus {
|
||||
if (isRateLimitError(error)) return 'rateLimit';
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
function isSlowmodeError(error: HttpError): boolean {
|
||||
if (error.status !== 400 && error.status !== 429) return false;
|
||||
const body = getApiErrorBody(error);
|
||||
@@ -351,13 +329,7 @@ export class MessageQueue extends Queue<MessageQueuePayload, RestResponse<Messag
|
||||
}
|
||||
const error = new Error(`Message queue capacity of ${this.maxSize} entries was reached`);
|
||||
logger.error('Rejected message queue entry because capacity was reached', error);
|
||||
if (message.type === 'send') {
|
||||
this.handleSendError(message.channelId, message.nonce, error, i18n, message.hasAttachments);
|
||||
} else {
|
||||
ModalCommands.push(
|
||||
modal(() => <MessageEditFailedModal data-flx="messaging.message-queue.message-edit-failed-modal--capacity" />),
|
||||
);
|
||||
}
|
||||
this.handleSendError(message.channelId, message.nonce, error, i18n, message.hasAttachments);
|
||||
try {
|
||||
success(undefined, error);
|
||||
} catch (callbackError) {
|
||||
@@ -407,19 +379,7 @@ export class MessageQueue extends Queue<MessageQueuePayload, RestResponse<Messag
|
||||
message: MessageQueuePayload,
|
||||
completed: (err: RetryError | null, result?: RestResponse<Message>, error?: unknown) => void,
|
||||
): Promise<unknown> | undefined {
|
||||
const route = resolveMessageQueuePayloadRouteDecision({
|
||||
payloadType: (message as {type?: string}).type,
|
||||
});
|
||||
switch (route.type) {
|
||||
case 'send':
|
||||
return this.handleSend(message as SendMessagePayload, completed);
|
||||
case 'edit':
|
||||
return this.handleEdit(message as EditMessagePayload, completed);
|
||||
case 'unknown':
|
||||
logger.error('Unknown message type, completing with null');
|
||||
completed(null, undefined, new Error('Unknown message queue payload'));
|
||||
return undefined;
|
||||
}
|
||||
return this.handleSend(message, completed);
|
||||
}
|
||||
|
||||
private consumeLocalSendAllowance(channelId: string): boolean {
|
||||
@@ -1429,127 +1389,6 @@ export class MessageQueue extends Queue<MessageQueuePayload, RestResponse<Messag
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async handleEdit(
|
||||
payload: EditMessagePayload,
|
||||
completed: (err: RetryError | null, result?: RestResponse<Message>, error?: unknown) => void,
|
||||
): Promise<void> {
|
||||
const {channelId, messageId, content, allowedMentions, flags, attachments} = payload;
|
||||
const abortController = new AbortController();
|
||||
this.abortControllers.set(messageId, abortController);
|
||||
try {
|
||||
logger.debug(`Editing message ${messageId} in channel ${channelId}`);
|
||||
const body = this.buildEditRequestBody(content, allowedMentions, flags, attachments);
|
||||
const response = await http.patch<Message>(Endpoints.CHANNEL_MESSAGE(channelId, messageId), {
|
||||
body,
|
||||
signal: abortController.signal,
|
||||
suppressContentBlockedModal: true,
|
||||
});
|
||||
logger.debug(`Successfully edited message ${messageId} in channel ${channelId}`);
|
||||
completed(null, response);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to edit message ${messageId} in channel ${channelId}:`, error);
|
||||
if (!(error instanceof HttpError)) {
|
||||
ModalCommands.push(
|
||||
modal(() => <MessageEditFailedModal data-flx="messaging.message-queue.message-edit-failed-modal--request" />),
|
||||
);
|
||||
completed(null, undefined, error);
|
||||
return;
|
||||
}
|
||||
const responseErr = error;
|
||||
const outcomeDecision = resolveMessageQueueRequestOutcomeDecision({
|
||||
status: getRequestErrorOutcomeStatus(responseErr),
|
||||
});
|
||||
switch (outcomeDecision.type) {
|
||||
case 'retryRateLimit':
|
||||
this.handleEditRateLimit(payload, responseErr, completed);
|
||||
break;
|
||||
case 'completeFailure':
|
||||
case 'completeSuccess':
|
||||
this.showEditErrorModal(responseErr);
|
||||
completed(null, undefined, responseErr);
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
this.abortControllers.delete(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
private buildEditRequestBody(
|
||||
content?: string,
|
||||
allowedMentions?: AllowedMentions,
|
||||
flags?: number,
|
||||
attachments?: Array<ApiMessageEditAttachmentMetadata>,
|
||||
): MessageEditRequest {
|
||||
const body: MessageEditRequest = {};
|
||||
if (content !== undefined) {
|
||||
body.content = normalizeMessageEditContent(content);
|
||||
}
|
||||
if (allowedMentions !== undefined) {
|
||||
body.allowed_mentions = allowedMentions;
|
||||
}
|
||||
if (flags !== undefined) {
|
||||
body.flags = flags;
|
||||
}
|
||||
if (attachments !== undefined) {
|
||||
body.attachments = attachments;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
private handleEditRateLimit(
|
||||
payload: EditMessagePayload,
|
||||
error: HttpError,
|
||||
completed: (err: RetryError | null, result?: RestResponse<Message>, error?: unknown) => void,
|
||||
): void {
|
||||
const retry = resolveMessageRateLimitRetry(error);
|
||||
const retryCount = payload.rateLimitRetryCount === undefined ? 0 : payload.rateLimitRetryCount;
|
||||
if (retry.automaticRetryDelayMs !== null && retryCount < MESSAGE_SEND_RATE_LIMIT_MAX_AUTOMATIC_RETRIES) {
|
||||
payload.rateLimitRetryCount = retryCount + 1;
|
||||
completed({retryAfter: retry.automaticRetryDelayMs}, undefined, error);
|
||||
return;
|
||||
}
|
||||
completed(null, undefined, error);
|
||||
this.handleEditRateLimitError(retryAfterMsToWholeSeconds(retry.retryAfterMs));
|
||||
}
|
||||
|
||||
private showEditErrorModal(error: HttpError): void {
|
||||
if (isFeatureDisabledError(error)) {
|
||||
ModalCommands.push(
|
||||
modal(() => (
|
||||
<FeatureTemporarilyDisabledModal data-flx="messaging.message-queue.feature-temporarily-disabled-modal--2" />
|
||||
)),
|
||||
);
|
||||
} else if (getApiErrorBody(error)?.code === APIErrorCodes.CONTENT_BLOCKED) {
|
||||
void import('@app/features/auth/components/ContentBlockedHandler').then((m) => m.showContentBlockedModal());
|
||||
} else {
|
||||
ModalCommands.push(
|
||||
modal(() => <MessageEditFailedModal data-flx="messaging.message-queue.message-edit-failed-modal" />),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleEditRateLimitError(retryAfter: number | null, onRetry?: () => void): void {
|
||||
ModalCommands.push(
|
||||
modal(() => {
|
||||
if (retryAfter === null) {
|
||||
return (
|
||||
<MessageEditTooQuickModal
|
||||
onRetry={onRetry}
|
||||
data-flx="messaging.message-queue.message-edit-too-quick-modal"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MessageEditTooQuickModal
|
||||
retryAfter={retryAfter}
|
||||
onRetry={onRetry}
|
||||
data-flx="messaging.message-queue.message-edit-too-quick-modal"
|
||||
/>
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new MessageQueue();
|
||||
Reference in new issue
Block a user