fix(correctness): call-invite clock skew, forceState, upload cancel (COR-3/5/6)
COR-3 (CallEmbedProvider): the incoming-call lifetime guard distrusted a caller's sender_ts only when it was >20s AHEAD of the server ts. A caller clock that ran SLOW left sender_ts in the past, so the ring auto-dismissed/never showed for a fresh invite. Trust sender_ts only within ±20s of the server ts, else fall back to it (also fixes a NaN path when sender_ts is missing). COR-6 (CallControl): forceState rebuilt CallControlState with 5 args, silently defaulting screenshareAudioMuted to false; pass this.screenshareAudioMuted. COR-5 (uploadContent + useBindUploadAtom): cancelling during the retry back-off was a no-op (mx.cancelUpload only aborts an in-flight request), so the upload resurrected on the next attempt. Thread an AbortSignal: the back-off sleep resolves early on abort and the loop stops with an abort error; the hook aborts a per-upload AbortController on cancel (alongside mx.cancelUpload for the in-flight case). All verified by two review passes (no double-settle / no resurrection); includes their suggested abort-listener cleanup on normal sleep resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-12
@@ -1,7 +1,7 @@
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import { atomFamily } from 'jotai/utils';
|
||||
import { MatrixClient, UploadResponse, UploadProgress, MatrixError } from 'matrix-js-sdk';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useThrottle } from '../hooks/useThrottle';
|
||||
import { uploadContent, TUploadContent } from '../utils/matrix';
|
||||
|
||||
@@ -110,19 +110,25 @@ export const useBindUploadAtom = (
|
||||
{ immediate: true, wait: 200 },
|
||||
);
|
||||
|
||||
const startUpload = useCallback(
|
||||
() =>
|
||||
uploadContent(mx, file, {
|
||||
hideFilename,
|
||||
onPromise: (promise: Promise<UploadResponse>) => setUpload({ promise }),
|
||||
onProgress: handleProgress,
|
||||
onSuccess: (mxc) => setUpload({ mxc }),
|
||||
onError: (error) => setUpload({ error }),
|
||||
}),
|
||||
[mx, file, hideFilename, setUpload, handleProgress],
|
||||
);
|
||||
const abortRef = useRef<AbortController | undefined>(undefined);
|
||||
|
||||
const startUpload = useCallback(() => {
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
return uploadContent(mx, file, {
|
||||
hideFilename,
|
||||
signal: controller.signal,
|
||||
onPromise: (promise: Promise<UploadResponse>) => setUpload({ promise }),
|
||||
onProgress: handleProgress,
|
||||
onSuccess: (mxc) => setUpload({ mxc }),
|
||||
onError: (error) => setUpload({ error }),
|
||||
});
|
||||
}, [mx, file, hideFilename, setUpload, handleProgress]);
|
||||
|
||||
const cancelUpload = useCallback(async () => {
|
||||
// Abort the retry loop first (covers a cancel during the back-off sleep, when
|
||||
// there is no in-flight request), then abort any in-flight upload request.
|
||||
abortRef.current?.abort();
|
||||
if (upload.status === UploadStatus.Loading) {
|
||||
await mx.cancelUpload(upload.promise);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user