Files
bitvid/js/services/nip71MirrorFlags.js
T
thePR0M3TH3ANandClaude Opus 4.8 685e52656b nip71: account-level "auto-share new public videos" toggle
Adds an opt-in account preference (off by default, per pubkey) that
auto-mirrors newly published eligible public videos to NIP-71
(kinds 34235/34236) so they surface on other Nostr video clients —
no per-video opt-in needed.

- nip71MirrorFlags: isAutoShareEnabled/setAutoShareEnabled (persisted
  per pubkey) + pure resolvePublishSync({featureOn,autoShare,eligible}).
- nip71MirrorSync: syncNip71MirrorAfterPublish + a videos:published
  subscription in initNip71MirrorSync; converts the canonical 30078
  event, checks eligibility (public + hosted URL + ALLOW_NSFW gate),
  publishes the mirror, and records the per-video flag so later
  edits/deletes stay in lockstep. All best-effort.
- My Videos pane: an "Auto-share new public videos to other Nostr apps"
  checkbox wired to setAutoShareEnabled, gated by FEATURE_NIP71_MIRROR.

Tests (mutation-verified): auto-share persistence, resolvePublishSync
(eligible+on=>publish; off/ineligible=>none), and videos:published
subscription wiring.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 14:06:19 -04:00

149 lines
4.5 KiB
JavaScript

// Per-video opt-in state for the NIP-71 mirror, persisted locally per
// pubkey+videoRootId. Off by default — a publisher explicitly opts a video in
// (from the My Videos tab). Local-only is fine: it's a convenience for the
// management UI; the authoritative state is whether the mirror event exists on
// relays.
const FLAG_KEY = "bitvid:nip71-mirror:v1";
const AUTO_SHARE_KEY = "bitvid:nip71-autoshare:v1";
function readFlags() {
try {
if (typeof localStorage === "undefined") {
return {};
}
const raw = localStorage.getItem(FLAG_KEY);
const parsed = raw ? JSON.parse(raw) : {};
return parsed && typeof parsed === "object" ? parsed : {};
} catch (error) {
return {};
}
}
function writeFlags(flags) {
try {
if (typeof localStorage !== "undefined") {
localStorage.setItem(FLAG_KEY, JSON.stringify(flags || {}));
}
} catch (error) {
// Best-effort.
}
}
function norm(value) {
return typeof value === "string" ? value.trim() : "";
}
export function isMirrorEnabled(pubkey, videoRootId) {
const pk = norm(pubkey);
const root = norm(videoRootId);
if (!pk || !root) {
return false;
}
return readFlags()[pk]?.[root] === true;
}
export function setMirrorEnabled(pubkey, videoRootId, enabled) {
const pk = norm(pubkey);
const root = norm(videoRootId);
if (!pk || !root) {
return;
}
const flags = readFlags();
const entry = flags[pk] && typeof flags[pk] === "object" ? flags[pk] : {};
if (enabled === true) {
entry[root] = true;
} else {
delete entry[root];
}
flags[pk] = entry;
writeFlags(flags);
}
// Pure decision for a toggle click: given the current opt-in state and the
// service's eligibility check (canMirror), what should happen?
// { action: "remove" } — currently on → tear down
// { action: "publish" } — eligible + off → publish
// { action: "blocked", reason: "<why>" } — off but ineligible (private / nsfw / no-url)
export function resolveMirrorToggle({ enabled, eligibility } = {}) {
if (enabled === true) {
return { action: "remove" };
}
if (!eligibility || eligibility.ok !== true) {
return { action: "blocked", reason: eligibility?.reason || "ineligible" };
}
return { action: "publish" };
}
// Account-level "auto-share new public videos" preference (per pubkey, off by
// default). When on, newly published eligible public videos are mirrored
// automatically — no per-video opt-in needed.
function readAutoShare() {
try {
if (typeof localStorage === "undefined") {
return {};
}
const raw = localStorage.getItem(AUTO_SHARE_KEY);
const parsed = raw ? JSON.parse(raw) : {};
return parsed && typeof parsed === "object" ? parsed : {};
} catch (error) {
return {};
}
}
export function isAutoShareEnabled(pubkey) {
const pk = norm(pubkey);
return pk ? readAutoShare()[pk] === true : false;
}
export function setAutoShareEnabled(pubkey, enabled) {
const pk = norm(pubkey);
if (!pk) {
return;
}
const map = readAutoShare();
if (enabled === true) {
map[pk] = true;
} else {
delete map[pk];
}
try {
if (typeof localStorage !== "undefined") {
localStorage.setItem(AUTO_SHARE_KEY, JSON.stringify(map));
}
} catch (error) {
// best-effort
}
}
// On publish of a NEW video: mirror it only when auto-share is on AND it's
// eligible (public + hosted URL + allowed). Otherwise do nothing.
export function resolvePublishSync({ featureOn, autoShare, eligible } = {}) {
if (featureOn !== true || autoShare !== true) {
return { action: "none" };
}
return eligible === true ? { action: "publish" } : { action: "none" };
}
// Lifecycle decisions for keeping an opted-in mirror in sync (used by the
// nostrService edit/delete hooks). Pure so they're cheat-resistant to test.
//
// On EDIT of a mirrored video: re-publish if still eligible; if it became
// ineligible (e.g. flipped private), pull the mirror down and clear the flag.
export function resolveEditSync({ featureOn, enabled, eligible } = {}) {
if (featureOn !== true || enabled !== true) {
return { action: "none" };
}
return eligible === true ? { action: "publish" } : { action: "unshare" };
}
// On DELETE of a mirrored video: remove the mirror and clear the flag.
export function resolveDeleteSync({ featureOn, enabled } = {}) {
if (featureOn !== true || enabled !== true) {
return { action: "none" };
}
return { action: "unshare" };
}
export const NIP71_MIRROR_FLAG_KEY = FLAG_KEY;