dm calls mvp: phase 5.35 polish: cancel notif on null session, redact token-leak in JSON parse log, guard drain from resume race
This commit is contained in:
parent
91e3dd95ec
commit
13ec1e0054
2 changed files with 28 additions and 11 deletions
|
|
@ -36,11 +36,11 @@ import java.util.concurrent.Executors;
|
|||
* - On non-2xx or exception: leave tombstone; flusher drains on next resume.
|
||||
*
|
||||
* Null-session edge case (fresh reinstall + first push before first login):
|
||||
* we deliberately do NOT cancel the notification so the ring keeps playing.
|
||||
* The user will unlock, MainActivity will boot, and the existing JS-path
|
||||
* consumer (pendingCallActionAtom) can still handle the decline through the
|
||||
* `pushNotificationActionPerformed` listener — silently dropping the ring
|
||||
* here would be worse UX than preserving it. See techdebt §5.35 Item 6.
|
||||
* we cannot send the decline at all — there's no access token, and the
|
||||
* JS-path can't cover us either (a logged-out client has no Matrix session
|
||||
* to call sendRtcDecline against). Cancelling the notification is the only
|
||||
* feedback we can give; leaving the ring would trap the user on a call they
|
||||
* can't accept or decline until the A-side times out. See techdebt §5.35.
|
||||
*
|
||||
* Note on idempotency: the flusher's retry generates a new txnId, so on a
|
||||
* split-success-fail sequence (receiver HTTP timed out, flusher succeeds)
|
||||
|
|
@ -89,11 +89,15 @@ public class CallDeclineReceiver extends BroadcastReceiver {
|
|||
|
||||
final String sessionJson = prefs.getString(SESSION_KEY, null);
|
||||
if (sessionJson == null) {
|
||||
// Fresh reinstall / logged-out state. Do NOT cancel the notification:
|
||||
// the ring keeps playing, user unlocks, MainActivity boots, and the
|
||||
// JS-path consumer handles the decline. Silent-drop here would leave
|
||||
// A-side spinning to no-answer timeout with no feedback on B.
|
||||
Log.w(TAG, "onReceive: no session in prefs, leaving ring intact");
|
||||
// Fresh reinstall / logged-out: no access token, no Matrix client.
|
||||
// We can't send m.call.decline and neither can the JS-path (no
|
||||
// session to decline from). Cancel the notif so the user isn't
|
||||
// stuck on a ring they can't action. Skip the tombstone — a
|
||||
// retry without a session would be equally impotent.
|
||||
Log.w(TAG, "onReceive: no session in prefs, cancelling notif without HTTP");
|
||||
if (notifTag != null && notifId != -1) {
|
||||
NotificationManagerCompat.from(appContext).cancel(notifTag, notifId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +108,9 @@ public class CallDeclineReceiver extends BroadcastReceiver {
|
|||
accessToken = session.optString("accessToken", null);
|
||||
baseUrl = session.optString("baseUrl", null);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onReceive: prefs JSON parse failed", t);
|
||||
// Do NOT pass the Throwable — JSONException.getMessage() embeds
|
||||
// the malformed input, which here contains the access token.
|
||||
Log.e(TAG, "onReceive: prefs JSON parse failed: " + t.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
if (accessToken == null || accessToken.isEmpty() || baseUrl == null || baseUrl.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,18 @@ export const usePendingDeclinesFlusher = (): void => {
|
|||
if (!isNativePlatform()) return undefined;
|
||||
|
||||
let disposed = false;
|
||||
let inFlight = false;
|
||||
let resumeHandle: { remove: () => Promise<void> } | undefined;
|
||||
|
||||
// Mount + an immediate `resume` (e.g. receiver fired, app foregrounded
|
||||
// right after the hook remounts) can both call drain() before the first
|
||||
// run finishes its per-key `get → sendRtcDecline → remove` loop. Without
|
||||
// this lock both passes observe the same tombstone and fire parallel
|
||||
// sendRtcDecline requests — benign at the server (idempotent on the
|
||||
// rel event_id) but it doubles the timeline noise.
|
||||
const drain = async () => {
|
||||
if (inFlight) return;
|
||||
inFlight = true;
|
||||
try {
|
||||
const { Preferences } = await import('@capacitor/preferences');
|
||||
const { keys } = await Preferences.keys();
|
||||
|
|
@ -60,6 +69,8 @@ export const usePendingDeclinesFlusher = (): void => {
|
|||
}
|
||||
} catch {
|
||||
/* preferences import failed — non-fatal */
|
||||
} finally {
|
||||
inFlight = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue