Files
bitvid/js/ui/revertModalController.js
thePR0M3TH3ANandClaude Fable 5 1934f9386a publish: all-timeout content publishes are unconfirmed soft successes (#49)
signAndPublishEvent (video publish/edit/repost/mirror), rebroadcastEvent, and
revertVideo now use assertAnyRelayAcceptedOrUnconfirmed: when every relay
times out without rejecting, the event was sent and almost always persisted,
so it no longer surfaces as "Failed to share video". Explicit rejections
still throw.

Messaging stays honest at every consumer:
- relay tally carries `unconfirmed` (now attached on edits too);
  describePublishOutcome maps 0-accepted+unconfirmed to a WARNING ("sent,
  no relay confirmed yet") — never a fake relay count, never a hard error
- edit/revert controllers say "sent, not confirmed yet" when unconfirmed
- deletes stay CONSERVATIVE: "deleted successfully" only when >=1 relay
  ACKed a tombstone or kind-5 (anyRelayAcceptedInSummaries); an unconfirmed
  revert no longer aborts deleteAllVersions midway, so the kind-5 always runs

Tests: tests/relay-ack-unconfirmed-content.test.mjs (6).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-03 21:01:31 -04:00

133 lines
3.8 KiB
JavaScript

import { devLogger } from "../utils/logger.js";
export default class RevertModalController {
constructor({
revertModal,
services = {},
state = {},
ui = {},
callbacks = {},
helpers = {},
}) {
this.revertModal = revertModal;
this.services = services;
this.state = state;
this.ui = ui;
this.callbacks = callbacks;
this.helpers = helpers;
}
async open(target) {
try {
const normalizedTarget = this.helpers.normalizeActionTarget(target);
const { triggerElement } = normalizedTarget;
const activeVideos = await this.services.nostrService.fetchVideos({
blacklistedEventIds: this.state.getBlacklistedEventIds(),
isAuthorBlocked: (pubkey) => this.callbacks.isAuthorBlocked(pubkey),
});
const video = await this.helpers.resolveVideoActionTarget({
...normalizedTarget,
preloadedList: activeVideos,
});
const pubkey = this.state.getPubkey();
if (!pubkey) {
this.ui.showError("Please login to revert.");
return;
}
const userPubkey = (pubkey || "").toLowerCase();
const videoPubkey = (video?.pubkey || "").toLowerCase();
if (!video || !videoPubkey || videoPubkey !== userPubkey) {
this.ui.showError("You do not own this video.");
return;
}
if (!this.revertModal) {
this.ui.showError("Revert modal is not available right now.");
return;
}
const loaded = await this.revertModal.load();
if (!loaded) {
this.ui.showError("Revert modal is not available right now.");
return;
}
const history = await this.services.nostrClient.hydrateVideoHistory(video);
this.revertModal.setHistory(video, history);
this.revertModal.open({ video }, { triggerElement });
} catch (err) {
devLogger.error("Failed to revert video:", err);
this.ui.showError("Failed to load revision history. Please try again.");
}
}
async handleConfirm(event) {
const detail = event?.detail || {};
const target = detail.target;
const entries = Array.isArray(detail.entries)
? detail.entries.slice()
: [];
if (!target || !entries.length) {
return;
}
const pubkey = this.state.getPubkey();
if (!pubkey) {
this.ui.showError("Please login to revert.");
return;
}
if (!this.revertModal) {
this.ui.showError("Revert modal is not available right now.");
return;
}
this.revertModal.setBusy(true, "Reverting…");
try {
// #49: revertVideo no longer hard-fails when every relay merely times
// out (summary.unconfirmed) — track whether ANY relay actually ACKed so
// the success message stays honest.
let anyAccepted = false;
for (const entry of entries) {
const revertResult = await this.services.nostrClient.revertVideo(
{
id: entry.id,
pubkey: entry.pubkey,
tags: entry.tags,
},
pubkey,
);
const accepted = revertResult?.summary?.accepted;
if (Array.isArray(accepted) && accepted.length > 0) {
anyAccepted = true;
}
}
await this.callbacks.loadVideos();
const timestampLabel = this.helpers.formatAbsoluteTimestamp(target.created_at);
if (anyAccepted) {
this.ui.showSuccess(`Reverted to revision from ${timestampLabel}.`);
} else {
this.ui.showSuccess(
`Revert to the revision from ${timestampLabel} was sent, but no relay has confirmed it yet — check again in a moment.`,
);
}
this.revertModal.close();
this.callbacks.forceRefreshAllProfiles();
} catch (err) {
devLogger.error("Failed to revert video:", err);
this.ui.showError("Failed to revert video. Please try again.");
} finally {
this.revertModal.setBusy(false);
}
}
}