mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 08:52:25 +00:00
Merge pull request #981 from PR0M3TH3AN/codex/fix-video-modal-profile-info-loading-issue
Improve video modal creator profile hydration
This commit is contained in:
10 files changed
+901
-85
No files matched your search
@@ -27,6 +27,7 @@ import { attachHealthBadges } from "./gridHealth.js";
|
||||
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
|
||||
import { updateVideoCardSourceVisibility } from "./utils/cardSourceVisibility.js";
|
||||
import { collectVideoTags } from "./utils/videoTags.js";
|
||||
import { sanitizeProfileMediaUrl } from "./utils/profileMedia.js";
|
||||
import { ADMIN_INITIAL_EVENT_BLACKLIST } from "./lists.js";
|
||||
import { userBlocks } from "./userBlocks.js";
|
||||
import { relayManager } from "./relayManager.js";
|
||||
@@ -215,6 +216,8 @@ class Application {
|
||||
const { modalManager } = this.bootstrapper.initialize();
|
||||
this.modalManager = modalManager;
|
||||
|
||||
this.modalCreatorProfileRequestToken = null;
|
||||
|
||||
this.commentController = null;
|
||||
this.initializeCommentController();
|
||||
|
||||
@@ -837,11 +840,12 @@ class Application {
|
||||
if (!video || typeof video !== "object") {
|
||||
return video;
|
||||
}
|
||||
let decoratedVideo = video;
|
||||
if (typeof this.decorateVideoModeration === "function") {
|
||||
try {
|
||||
const decorated = this.decorateVideoModeration(video);
|
||||
if (decorated && typeof decorated === "object") {
|
||||
return decorated;
|
||||
decoratedVideo = decorated;
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
@@ -850,7 +854,22 @@ class Application {
|
||||
);
|
||||
}
|
||||
}
|
||||
return video;
|
||||
if (typeof this.decorateVideoCreatorIdentity === "function") {
|
||||
try {
|
||||
const identityDecorated = this.decorateVideoCreatorIdentity(
|
||||
decoratedVideo,
|
||||
);
|
||||
if (identityDecorated && typeof identityDecorated === "object") {
|
||||
decoratedVideo = identityDecorated;
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to decorate similar content identity",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
return decoratedVideo;
|
||||
};
|
||||
|
||||
const target = activeVideo && typeof activeVideo === "object" ? decorateCandidate(activeVideo) : null;
|
||||
@@ -2739,12 +2758,50 @@ class Application {
|
||||
pubkey,
|
||||
profile,
|
||||
});
|
||||
} else {
|
||||
devLogger.warn(
|
||||
"[Application] ProfileIdentityController missing; profile identity was not refreshed in the DOM.",
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedPubkey =
|
||||
this.normalizeHexPubkey(pubkey) ||
|
||||
(typeof pubkey === "string" ? pubkey.trim() : "");
|
||||
if (!normalizedPubkey) {
|
||||
return;
|
||||
}
|
||||
|
||||
devLogger.warn(
|
||||
"[Application] ProfileIdentityController missing; profile identity was not refreshed in the DOM.",
|
||||
);
|
||||
const maybeDecorateVideo = (video) => {
|
||||
if (!video || typeof video !== "object") {
|
||||
return;
|
||||
}
|
||||
const videoPubkey =
|
||||
this.normalizeHexPubkey(video.pubkey) ||
|
||||
(typeof video.pubkey === "string" ? video.pubkey.trim() : "");
|
||||
if (videoPubkey !== normalizedPubkey) {
|
||||
return;
|
||||
}
|
||||
this.decorateVideoCreatorIdentity(video);
|
||||
};
|
||||
|
||||
if (this.videosMap instanceof Map) {
|
||||
for (const video of this.videosMap.values()) {
|
||||
maybeDecorateVideo(video);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.videoListView &&
|
||||
Array.isArray(this.videoListView.currentVideos)
|
||||
) {
|
||||
this.videoListView.currentVideos.forEach((video) => {
|
||||
maybeDecorateVideo(video);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.currentVideo) {
|
||||
maybeDecorateVideo(this.currentVideo);
|
||||
}
|
||||
}
|
||||
|
||||
async publishVideoNote(payload, { onSuccess, suppressModalClose } = {}) {
|
||||
@@ -5475,7 +5532,15 @@ class Application {
|
||||
}
|
||||
|
||||
const decoratedVideos = Array.isArray(videos)
|
||||
? videos.map((video) => this.decorateVideoModeration(video))
|
||||
? videos.map((video) => {
|
||||
const moderated = this.decorateVideoModeration(video);
|
||||
const targetVideo =
|
||||
moderated && typeof moderated === "object" ? moderated : video;
|
||||
const withIdentity = this.decorateVideoCreatorIdentity(targetVideo);
|
||||
return withIdentity && typeof withIdentity === "object"
|
||||
? withIdentity
|
||||
: targetVideo;
|
||||
})
|
||||
: [];
|
||||
|
||||
this.videoListView.render(decoratedVideos, metadata);
|
||||
@@ -7147,11 +7212,32 @@ class Application {
|
||||
? index
|
||||
: null;
|
||||
|
||||
const ensureCreatorIdentity = (video) => {
|
||||
if (
|
||||
!video ||
|
||||
typeof video !== "object" ||
|
||||
typeof this.decorateVideoCreatorIdentity !== "function"
|
||||
) {
|
||||
return video;
|
||||
}
|
||||
try {
|
||||
const decorated = this.decorateVideoCreatorIdentity(video);
|
||||
return decorated && typeof decorated === "object" ? decorated : video;
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to decorate video identity for action target:",
|
||||
error,
|
||||
);
|
||||
return video;
|
||||
}
|
||||
};
|
||||
|
||||
const initialVideo =
|
||||
providedVideo && typeof providedVideo === "object" ? providedVideo : null;
|
||||
const preparedInitialVideo = ensureCreatorIdentity(initialVideo);
|
||||
const providedId =
|
||||
initialVideo && typeof initialVideo.id === "string"
|
||||
? initialVideo.id.trim()
|
||||
preparedInitialVideo && typeof preparedInitialVideo.id === "string"
|
||||
? preparedInitialVideo.id.trim()
|
||||
: "";
|
||||
const targetEventId = trimmedEventId || providedId;
|
||||
|
||||
@@ -7160,9 +7246,9 @@ class Application {
|
||||
: [];
|
||||
|
||||
if (providedId) {
|
||||
this.videosMap.set(providedId, initialVideo);
|
||||
this.videosMap.set(providedId, preparedInitialVideo);
|
||||
if (!trimmedEventId || providedId === trimmedEventId) {
|
||||
return initialVideo;
|
||||
return preparedInitialVideo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7170,8 +7256,9 @@ class Application {
|
||||
if (targetEventId) {
|
||||
const match = list.find((video) => video?.id === targetEventId);
|
||||
if (match) {
|
||||
this.videosMap.set(match.id, match);
|
||||
return match;
|
||||
const preparedMatch = ensureCreatorIdentity(match);
|
||||
this.videosMap.set(preparedMatch.id, preparedMatch);
|
||||
return preparedMatch;
|
||||
}
|
||||
}
|
||||
if (
|
||||
@@ -7181,8 +7268,9 @@ class Application {
|
||||
) {
|
||||
const match = list[normalizedIndex];
|
||||
if (match) {
|
||||
this.videosMap.set(match.id, match);
|
||||
return match;
|
||||
const preparedMatch = ensureCreatorIdentity(match);
|
||||
this.videosMap.set(preparedMatch.id, preparedMatch);
|
||||
return preparedMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7196,20 +7284,23 @@ class Application {
|
||||
const activeVideos = nostrClient.getActiveVideos();
|
||||
const fromActive = activeVideos.find((video) => video.id === targetEventId);
|
||||
if (fromActive) {
|
||||
this.videosMap.set(fromActive.id, fromActive);
|
||||
return fromActive;
|
||||
const preparedActive = ensureCreatorIdentity(fromActive);
|
||||
this.videosMap.set(preparedActive.id, preparedActive);
|
||||
return preparedActive;
|
||||
}
|
||||
|
||||
const fromAll = nostrClient.allEvents.get(targetEventId);
|
||||
if (fromAll) {
|
||||
this.videosMap.set(fromAll.id, fromAll);
|
||||
return fromAll;
|
||||
const preparedFromAll = ensureCreatorIdentity(fromAll);
|
||||
this.videosMap.set(preparedFromAll.id, preparedFromAll);
|
||||
return preparedFromAll;
|
||||
}
|
||||
|
||||
const fetched = await nostrClient.getEventById(targetEventId);
|
||||
if (fetched) {
|
||||
this.videosMap.set(fetched.id, fetched);
|
||||
return fetched;
|
||||
const preparedFetched = ensureCreatorIdentity(fetched);
|
||||
this.videosMap.set(preparedFetched.id, preparedFetched);
|
||||
return preparedFetched;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7218,8 +7309,9 @@ class Application {
|
||||
if (normalizedIndex >= 0 && normalizedIndex < activeVideos.length) {
|
||||
const candidate = activeVideos[normalizedIndex];
|
||||
if (candidate) {
|
||||
this.videosMap.set(candidate.id, candidate);
|
||||
return candidate;
|
||||
const preparedCandidate = ensureCreatorIdentity(candidate);
|
||||
this.videosMap.set(preparedCandidate.id, preparedCandidate);
|
||||
return preparedCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8286,6 +8378,22 @@ class Application {
|
||||
? Math.floor(video.created_at)
|
||||
: null;
|
||||
|
||||
const normalizedCreatorPubkey =
|
||||
this.normalizeHexPubkey(video.pubkey) || video.pubkey;
|
||||
const cachedCreatorProfileEntry =
|
||||
normalizedCreatorPubkey && typeof this.getProfileCacheEntry === "function"
|
||||
? this.getProfileCacheEntry(normalizedCreatorPubkey)
|
||||
: null;
|
||||
const cachedCreatorProfile =
|
||||
cachedCreatorProfileEntry &&
|
||||
typeof cachedCreatorProfileEntry === "object"
|
||||
? cachedCreatorProfileEntry.profile || null
|
||||
: null;
|
||||
const initialLightningAddress =
|
||||
typeof video.lightningAddress === "string"
|
||||
? video.lightningAddress.trim()
|
||||
: "";
|
||||
|
||||
this.currentVideo = {
|
||||
...video,
|
||||
url: trimmedUrl,
|
||||
@@ -8294,7 +8402,7 @@ class Application {
|
||||
magnetCandidate || fallbackMagnetForCandidate || legacyInfoHash || "",
|
||||
torrentSupported: magnetSupported,
|
||||
legacyInfoHash: video.legacyInfoHash || legacyInfoHash,
|
||||
lightningAddress: null,
|
||||
lightningAddress: initialLightningAddress || null,
|
||||
lastEditedAt: normalizedEditedAt,
|
||||
};
|
||||
|
||||
@@ -8302,6 +8410,30 @@ class Application {
|
||||
|
||||
const modalTags = collectVideoTags(this.currentVideo);
|
||||
this.currentVideo.displayTags = modalTags;
|
||||
const creatorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey;
|
||||
const displayNpub = formatShortNpub(creatorNpub) || creatorNpub;
|
||||
const initialCreatorProfile = this.resolveModalCreatorProfile({
|
||||
video: this.currentVideo,
|
||||
pubkey: normalizedCreatorPubkey,
|
||||
cachedProfile: cachedCreatorProfile,
|
||||
});
|
||||
this.currentVideo.creatorName = initialCreatorProfile.name;
|
||||
this.currentVideo.creatorPicture = initialCreatorProfile.picture;
|
||||
this.currentVideo.creatorNpub = displayNpub;
|
||||
if (this.currentVideo.creator && typeof this.currentVideo.creator === "object") {
|
||||
this.currentVideo.creator = {
|
||||
...this.currentVideo.creator,
|
||||
name: initialCreatorProfile.name,
|
||||
picture: initialCreatorProfile.picture,
|
||||
pubkey: normalizedCreatorPubkey,
|
||||
};
|
||||
} else {
|
||||
this.currentVideo.creator = {
|
||||
name: initialCreatorProfile.name,
|
||||
picture: initialCreatorProfile.picture,
|
||||
pubkey: normalizedCreatorPubkey,
|
||||
};
|
||||
}
|
||||
this.updateModalSimilarContent({ activeVideo: this.currentVideo });
|
||||
|
||||
if (Number.isFinite(knownPostedAt)) {
|
||||
@@ -8352,8 +8484,8 @@ class Application {
|
||||
)}`;
|
||||
window.history.pushState({}, "", pushUrl);
|
||||
|
||||
this.zapController?.setVisibility(false);
|
||||
this.zapController?.resetState();
|
||||
this.zapController?.setVisibility(Boolean(this.currentVideo.lightningAddress));
|
||||
|
||||
const magnetInput =
|
||||
sanitizedMagnet ||
|
||||
@@ -8388,52 +8520,38 @@ class Application {
|
||||
playbackPromise = this.playVideoWithFallback(playbackOptions);
|
||||
}
|
||||
|
||||
let lightningAddress = "";
|
||||
let creatorProfile = {
|
||||
name: "Unknown",
|
||||
picture: `https://robohash.org/${video.pubkey}`,
|
||||
};
|
||||
try {
|
||||
const userEvents = await nostrClient.pool.list(nostrClient.relays, [
|
||||
{ kinds: [0], authors: [video.pubkey], limit: 1 },
|
||||
]);
|
||||
if (userEvents.length > 0 && userEvents[0]?.content) {
|
||||
const data = JSON.parse(userEvents[0].content);
|
||||
lightningAddress = (data.lud16 || data.lud06 || "").trim();
|
||||
creatorProfile = {
|
||||
name: data.name || data.display_name || "Unknown",
|
||||
picture: data.picture || `https://robohash.org/${video.pubkey}`,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.log("Error fetching creator profile:", error);
|
||||
}
|
||||
|
||||
this.zapController?.setVisibility(!!lightningAddress);
|
||||
if (this.currentVideo) {
|
||||
this.currentVideo.lightningAddress = lightningAddress || null;
|
||||
}
|
||||
|
||||
const creatorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey;
|
||||
if (this.videoModal) {
|
||||
const timestampPayload = this.buildModalTimestampPayload({
|
||||
postedAt: this.currentVideo?.rootCreatedAt ?? null,
|
||||
editedAt: normalizedEditedAt,
|
||||
});
|
||||
const displayNpub = formatShortNpub(creatorNpub);
|
||||
this.videoModal.updateMetadata({
|
||||
title: video.title || "Untitled",
|
||||
description: video.description || "No description available.",
|
||||
timestamps: timestampPayload,
|
||||
tags: modalTags,
|
||||
creator: {
|
||||
name: creatorProfile.name,
|
||||
avatarUrl: creatorProfile.picture,
|
||||
name: initialCreatorProfile.name,
|
||||
avatarUrl: initialCreatorProfile.picture,
|
||||
npub: displayNpub,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const profileRequestToken = Symbol("modal-profile-request");
|
||||
this.modalCreatorProfileRequestToken = profileRequestToken;
|
||||
this.fetchModalCreatorProfile({
|
||||
pubkey: normalizedCreatorPubkey,
|
||||
displayNpub,
|
||||
cachedProfile: cachedCreatorProfile,
|
||||
requestToken: profileRequestToken,
|
||||
}).catch((error) => {
|
||||
devLogger.error(
|
||||
"[Application] Failed to fetch creator profile for modal:",
|
||||
error,
|
||||
);
|
||||
});
|
||||
|
||||
this.ensureModalPostedTimestamp(this.currentVideo);
|
||||
|
||||
const playbackResult =
|
||||
@@ -8789,6 +8907,428 @@ class Application {
|
||||
return null;
|
||||
}
|
||||
|
||||
selectPreferredCreatorName(candidates = []) {
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = candidate.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
if (HEX64_REGEX.test(trimmed)) {
|
||||
continue;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
selectPreferredCreatorPicture(candidates = []) {
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate !== "string") {
|
||||
continue;
|
||||
}
|
||||
const sanitized = sanitizeProfileMediaUrl(candidate);
|
||||
if (sanitized) {
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
resolveCreatorProfileFromSources({
|
||||
video,
|
||||
pubkey,
|
||||
cachedProfile = null,
|
||||
fetchedProfile = null,
|
||||
fallbackAvatar,
|
||||
} = {}) {
|
||||
const normalizedPubkey =
|
||||
typeof pubkey === "string" && pubkey.trim() ? pubkey.trim() : "";
|
||||
const fallbackAvatarCandidate =
|
||||
typeof fallbackAvatar === "string" && fallbackAvatar.trim()
|
||||
? fallbackAvatar.trim()
|
||||
: normalizedPubkey
|
||||
? `https://robohash.org/${normalizedPubkey}`
|
||||
: "assets/svg/default-profile.svg";
|
||||
const defaultAvatar =
|
||||
sanitizeProfileMediaUrl(fallbackAvatarCandidate) ||
|
||||
fallbackAvatarCandidate ||
|
||||
"assets/svg/default-profile.svg";
|
||||
|
||||
const nameCandidates = [];
|
||||
const pictureCandidates = [];
|
||||
|
||||
const collectFromSource = (source) => {
|
||||
if (!source || typeof source !== "object") {
|
||||
return;
|
||||
}
|
||||
const names = [
|
||||
source.display_name,
|
||||
source.displayName,
|
||||
source.name,
|
||||
source.username,
|
||||
];
|
||||
names.forEach((value) => {
|
||||
if (typeof value === "string") {
|
||||
nameCandidates.push(value);
|
||||
}
|
||||
});
|
||||
const pictures = [source.picture, source.image, source.photo];
|
||||
pictures.forEach((value) => {
|
||||
if (typeof value === "string") {
|
||||
pictureCandidates.push(value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
collectFromSource(fetchedProfile);
|
||||
collectFromSource(cachedProfile);
|
||||
|
||||
if (video && typeof video === "object") {
|
||||
collectFromSource(video.creator);
|
||||
if (typeof video.creatorName === "string") {
|
||||
nameCandidates.push(video.creatorName);
|
||||
}
|
||||
if (typeof video.creatorPicture === "string") {
|
||||
pictureCandidates.push(video.creatorPicture);
|
||||
}
|
||||
collectFromSource(video.author);
|
||||
if (typeof video.authorName === "string") {
|
||||
nameCandidates.push(video.authorName);
|
||||
}
|
||||
if (typeof video.authorPicture === "string") {
|
||||
pictureCandidates.push(video.authorPicture);
|
||||
}
|
||||
collectFromSource(video.profile);
|
||||
const extraNames = [
|
||||
video.shortNpub,
|
||||
video.creatorNpub,
|
||||
video.npub,
|
||||
video.authorNpub,
|
||||
];
|
||||
extraNames.forEach((value) => {
|
||||
if (typeof value === "string") {
|
||||
nameCandidates.push(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const resolvedName =
|
||||
this.selectPreferredCreatorName(nameCandidates) || "Unknown";
|
||||
const resolvedPicture =
|
||||
this.selectPreferredCreatorPicture(pictureCandidates) || defaultAvatar;
|
||||
|
||||
return { name: resolvedName, picture: resolvedPicture };
|
||||
}
|
||||
|
||||
resolveModalCreatorProfile({
|
||||
video,
|
||||
pubkey,
|
||||
cachedProfile = null,
|
||||
fetchedProfile = null,
|
||||
} = {}) {
|
||||
return this.resolveCreatorProfileFromSources({
|
||||
video,
|
||||
pubkey,
|
||||
cachedProfile,
|
||||
fetchedProfile,
|
||||
});
|
||||
}
|
||||
|
||||
decorateVideoCreatorIdentity(video) {
|
||||
if (!video || typeof video !== "object") {
|
||||
return video;
|
||||
}
|
||||
|
||||
const normalizedPubkey =
|
||||
this.normalizeHexPubkey(video.pubkey) ||
|
||||
(typeof video.pubkey === "string" ? video.pubkey.trim() : "");
|
||||
if (!normalizedPubkey) {
|
||||
return video;
|
||||
}
|
||||
|
||||
let cachedProfile = null;
|
||||
if (typeof this.getProfileCacheEntry === "function") {
|
||||
const cacheEntry = this.getProfileCacheEntry(normalizedPubkey);
|
||||
if (cacheEntry && typeof cacheEntry === "object") {
|
||||
cachedProfile = cacheEntry.profile || null;
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedProfile = this.resolveCreatorProfileFromSources({
|
||||
video,
|
||||
pubkey: normalizedPubkey,
|
||||
cachedProfile,
|
||||
});
|
||||
|
||||
if (!video.creator || typeof video.creator !== "object") {
|
||||
video.creator = {};
|
||||
}
|
||||
|
||||
if (!video.creator.pubkey) {
|
||||
video.creator.pubkey = normalizedPubkey;
|
||||
}
|
||||
|
||||
if (resolvedProfile.name) {
|
||||
video.creator.name = resolvedProfile.name;
|
||||
if (
|
||||
typeof video.creatorName !== "string" ||
|
||||
!video.creatorName.trim() ||
|
||||
video.creatorName === "Unknown"
|
||||
) {
|
||||
video.creatorName = resolvedProfile.name;
|
||||
}
|
||||
if (
|
||||
typeof video.authorName !== "string" ||
|
||||
!video.authorName.trim() ||
|
||||
video.authorName === "Unknown"
|
||||
) {
|
||||
video.authorName = resolvedProfile.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedProfile.picture) {
|
||||
video.creator.picture = resolvedProfile.picture;
|
||||
video.creatorPicture = resolvedProfile.picture;
|
||||
if (
|
||||
typeof video.authorPicture !== "string" ||
|
||||
!video.authorPicture.trim()
|
||||
) {
|
||||
video.authorPicture = resolvedProfile.picture;
|
||||
}
|
||||
}
|
||||
|
||||
const encodedNpub = this.safeEncodeNpub(normalizedPubkey);
|
||||
if (encodedNpub) {
|
||||
const shortNpub = formatShortNpub(encodedNpub) || encodedNpub;
|
||||
if (typeof video.npub !== "string" || !video.npub.trim()) {
|
||||
video.npub = encodedNpub;
|
||||
}
|
||||
if (typeof video.shortNpub !== "string" || !video.shortNpub.trim()) {
|
||||
video.shortNpub = shortNpub;
|
||||
}
|
||||
if (
|
||||
typeof video.creatorNpub !== "string" ||
|
||||
!video.creatorNpub.trim()
|
||||
) {
|
||||
video.creatorNpub = shortNpub;
|
||||
}
|
||||
}
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
async fetchModalCreatorProfile({
|
||||
pubkey,
|
||||
displayNpub = "",
|
||||
cachedProfile = null,
|
||||
requestToken = null,
|
||||
} = {}) {
|
||||
const normalized = this.normalizeHexPubkey(pubkey);
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relayList =
|
||||
Array.isArray(nostrClient?.relays) && nostrClient.relays.length
|
||||
? nostrClient.relays
|
||||
: null;
|
||||
if (!relayList || !nostrClient?.pool || typeof nostrClient.pool.list !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const events = await nostrClient.pool.list(relayList, [
|
||||
{ kinds: [0], authors: [normalized], limit: 1 },
|
||||
]);
|
||||
|
||||
if (this.modalCreatorProfileRequestToken !== requestToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newest = Array.isArray(events)
|
||||
? events.reduce((latest, event) => {
|
||||
if (!event || typeof event !== "object") {
|
||||
return latest;
|
||||
}
|
||||
const eventPubkey = this.normalizeHexPubkey(event.pubkey);
|
||||
if (eventPubkey && eventPubkey !== normalized) {
|
||||
return latest;
|
||||
}
|
||||
const createdAt = Number.isFinite(event.created_at) ? event.created_at : 0;
|
||||
if (!latest || createdAt > latest.createdAt) {
|
||||
return { createdAt, event };
|
||||
}
|
||||
return latest;
|
||||
}, null)
|
||||
: null;
|
||||
|
||||
if (!newest || !newest.event) {
|
||||
if (this.modalCreatorProfileRequestToken === requestToken) {
|
||||
this.modalCreatorProfileRequestToken = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = newest.event.content ? JSON.parse(newest.event.content) : null;
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
`[Application] Failed to parse creator profile content for ${normalized}:`,
|
||||
error,
|
||||
);
|
||||
if (this.modalCreatorProfileRequestToken === requestToken) {
|
||||
this.modalCreatorProfileRequestToken = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.modalCreatorProfileRequestToken !== requestToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedLud16 =
|
||||
typeof parsed?.lud16 === "string" ? parsed.lud16.trim() : "";
|
||||
const parsedLud06 =
|
||||
typeof parsed?.lud06 === "string" ? parsed.lud06.trim() : "";
|
||||
const lightningAddressCandidate = (() => {
|
||||
const fields = [parsedLud16, parsedLud06];
|
||||
for (const field of fields) {
|
||||
if (typeof field !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = field.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
})();
|
||||
|
||||
const fetchedProfile = {
|
||||
display_name: parsed?.display_name,
|
||||
name: parsed?.name,
|
||||
username: parsed?.username,
|
||||
picture: parsed?.picture,
|
||||
image: parsed?.image,
|
||||
photo: parsed?.photo,
|
||||
};
|
||||
|
||||
const resolvedProfile = this.resolveModalCreatorProfile({
|
||||
video: this.currentVideo,
|
||||
pubkey: normalized,
|
||||
cachedProfile,
|
||||
fetchedProfile,
|
||||
});
|
||||
|
||||
if (this.modalCreatorProfileRequestToken !== requestToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeVideoPubkey = this.normalizeHexPubkey(this.currentVideo?.pubkey);
|
||||
if (activeVideoPubkey && activeVideoPubkey !== normalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextLightning = lightningAddressCandidate || "";
|
||||
const previousLightning =
|
||||
typeof this.currentVideo?.lightningAddress === "string"
|
||||
? this.currentVideo.lightningAddress
|
||||
: "";
|
||||
|
||||
if (this.currentVideo) {
|
||||
this.currentVideo.lightningAddress = nextLightning ? nextLightning : null;
|
||||
this.currentVideo.creatorName = resolvedProfile.name;
|
||||
this.currentVideo.creatorPicture = resolvedProfile.picture;
|
||||
this.currentVideo.creatorNpub = displayNpub;
|
||||
if (this.currentVideo.creator && typeof this.currentVideo.creator === "object") {
|
||||
this.currentVideo.creator = {
|
||||
...this.currentVideo.creator,
|
||||
name: resolvedProfile.name,
|
||||
picture: resolvedProfile.picture,
|
||||
pubkey: normalized,
|
||||
lightningAddress: nextLightning ? nextLightning : null,
|
||||
};
|
||||
} else {
|
||||
this.currentVideo.creator = {
|
||||
name: resolvedProfile.name,
|
||||
picture: resolvedProfile.picture,
|
||||
pubkey: normalized,
|
||||
lightningAddress: nextLightning ? nextLightning : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (this.videoModal) {
|
||||
this.videoModal.updateMetadata({
|
||||
creator: {
|
||||
name: resolvedProfile.name,
|
||||
avatarUrl: resolvedProfile.picture,
|
||||
npub: displayNpub,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.zapController?.setVisibility(Boolean(this.currentVideo?.lightningAddress));
|
||||
|
||||
const sanitizedFetchedPicture = sanitizeProfileMediaUrl(
|
||||
parsed?.picture || parsed?.image || parsed?.photo || "",
|
||||
);
|
||||
const fetchedNameCandidate = this.selectPreferredCreatorName([
|
||||
parsed?.display_name,
|
||||
parsed?.name,
|
||||
parsed?.username,
|
||||
]);
|
||||
|
||||
const cachedLightning =
|
||||
typeof cachedProfile?.lightningAddress === "string"
|
||||
? cachedProfile.lightningAddress.trim()
|
||||
: "";
|
||||
const shouldUpdateCache =
|
||||
Boolean(fetchedNameCandidate) ||
|
||||
Boolean(sanitizedFetchedPicture) ||
|
||||
cachedLightning !== nextLightning ||
|
||||
previousLightning !== nextLightning;
|
||||
|
||||
if (shouldUpdateCache) {
|
||||
try {
|
||||
const profileForCache = {
|
||||
name: fetchedNameCandidate || resolvedProfile.name,
|
||||
picture: sanitizedFetchedPicture || resolvedProfile.picture,
|
||||
};
|
||||
|
||||
if (parsedLud16) {
|
||||
profileForCache.lud16 = parsedLud16;
|
||||
}
|
||||
|
||||
if (parsedLud06) {
|
||||
profileForCache.lud06 = parsedLud06;
|
||||
}
|
||||
|
||||
if (nextLightning) {
|
||||
profileForCache.lightningAddress = nextLightning;
|
||||
}
|
||||
|
||||
this.setProfileCacheEntry(
|
||||
normalized,
|
||||
profileForCache,
|
||||
{ persist: false, reason: "modal-profile-fetch" },
|
||||
);
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
`[Application] Failed to update profile cache for ${normalized}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.modalCreatorProfileRequestToken === requestToken) {
|
||||
this.modalCreatorProfileRequestToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
normalizeReactionCount(value) {
|
||||
if (Number.isFinite(value)) {
|
||||
return Math.max(0, Math.round(Number(value)));
|
||||
|
||||
+62
-3
@@ -54,7 +54,8 @@ import { getPlatformLightningAddress } from "./payments/platformAddress.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
ensureWallet,
|
||||
sendPayment as sendWalletPayment
|
||||
sendPayment as sendWalletPayment,
|
||||
resetWalletClient
|
||||
} from "./payments/nwcClient.js";
|
||||
import {
|
||||
prepareStaticModal,
|
||||
@@ -110,11 +111,13 @@ function decorateChannelVideo(video, app = getApp()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let decoratedVideo = video;
|
||||
|
||||
if (typeof app?.decorateVideoModeration === "function") {
|
||||
try {
|
||||
const decorated = app.decorateVideoModeration(video);
|
||||
if (decorated && typeof decorated === "object") {
|
||||
return decorated;
|
||||
decoratedVideo = decorated;
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
@@ -124,7 +127,21 @@ function decorateChannelVideo(video, app = getApp()) {
|
||||
}
|
||||
}
|
||||
|
||||
return video;
|
||||
if (typeof app?.decorateVideoCreatorIdentity === "function") {
|
||||
try {
|
||||
const identityDecorated = app.decorateVideoCreatorIdentity(decoratedVideo);
|
||||
if (identityDecorated && typeof identityDecorated === "object") {
|
||||
decoratedVideo = identityDecorated;
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[ChannelProfile] Failed to decorate channel video identity",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return decoratedVideo;
|
||||
}
|
||||
|
||||
function collectChannelVideos(pubkey, app = getApp()) {
|
||||
@@ -2562,6 +2579,19 @@ function createZapDependencies({
|
||||
error
|
||||
});
|
||||
}
|
||||
if (
|
||||
typeof error?.message === "string" &&
|
||||
error.message.toLowerCase().includes("timed out")
|
||||
) {
|
||||
try {
|
||||
resetWalletClient();
|
||||
} catch (resetError) {
|
||||
devLogger?.warn?.(
|
||||
"[zap] Failed to reset wallet client after timeout",
|
||||
resetError
|
||||
);
|
||||
}
|
||||
}
|
||||
logZapError(
|
||||
"wallet.sendPayment",
|
||||
{
|
||||
@@ -4357,6 +4387,34 @@ export async function renderChannelVideosFromList({
|
||||
cardState = "critical";
|
||||
}
|
||||
|
||||
const identity = {
|
||||
name:
|
||||
typeof video.creatorName === "string" && video.creatorName
|
||||
? video.creatorName
|
||||
: typeof video.authorName === "string"
|
||||
? video.authorName
|
||||
: "",
|
||||
picture:
|
||||
typeof video.creatorPicture === "string" && video.creatorPicture
|
||||
? video.creatorPicture
|
||||
: typeof video.authorPicture === "string"
|
||||
? video.authorPicture
|
||||
: "",
|
||||
pubkey: typeof video.pubkey === "string" ? video.pubkey : "",
|
||||
npub:
|
||||
typeof video.npub === "string" && video.npub
|
||||
? video.npub
|
||||
: typeof video.authorNpub === "string"
|
||||
? video.authorNpub
|
||||
: "",
|
||||
shortNpub:
|
||||
typeof video.shortNpub === "string" && video.shortNpub
|
||||
? video.shortNpub
|
||||
: typeof video.creatorNpub === "string"
|
||||
? video.creatorNpub
|
||||
: "",
|
||||
};
|
||||
|
||||
const videoCard = new VideoCard({
|
||||
document,
|
||||
video,
|
||||
@@ -4365,6 +4423,7 @@ export async function renderChannelVideosFromList({
|
||||
pointerInfo,
|
||||
timeAgo,
|
||||
cardState,
|
||||
identity,
|
||||
capabilities: {
|
||||
canEdit,
|
||||
canDelete: canEdit,
|
||||
|
||||
@@ -1083,7 +1083,7 @@ function sanitizeAmount(amount) {
|
||||
return Math.max(0, rounded);
|
||||
}
|
||||
|
||||
function buildPayInvoiceParams({ invoice, amountSats, zapRequest }) {
|
||||
function buildPayInvoiceParams({ invoice, amountSats, zapRequest, lnurl }) {
|
||||
const params = { invoice };
|
||||
|
||||
const amount = sanitizeAmount(amountSats);
|
||||
@@ -1095,13 +1095,23 @@ function buildPayInvoiceParams({ invoice, amountSats, zapRequest }) {
|
||||
params.zap_request = zapRequest;
|
||||
}
|
||||
|
||||
if (typeof lnurl === "string") {
|
||||
const trimmed = lnurl.trim();
|
||||
if (trimmed) {
|
||||
params.lnurl = trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export async function sendPayment(bolt11, { settings, amountSats, zapRequest, timeoutMs } = {}) {
|
||||
export async function sendPayment(
|
||||
bolt11,
|
||||
{ settings, amountSats, zapRequest, lnurl, timeoutMs } = {}
|
||||
) {
|
||||
const context = await ensureWallet({ settings });
|
||||
const invoice = sanitizeInvoice(bolt11);
|
||||
const params = buildPayInvoiceParams({ invoice, amountSats, zapRequest });
|
||||
const params = buildPayInvoiceParams({ invoice, amountSats, zapRequest, lnurl });
|
||||
const payload = {
|
||||
id: `req-${Date.now()}-${++requestCounter}`,
|
||||
method: "pay_invoice",
|
||||
|
||||
@@ -370,6 +370,7 @@ async function processShare({
|
||||
const payment = await deps.wallet.sendPayment(invoice.invoice, {
|
||||
amountSats,
|
||||
zapRequest,
|
||||
lnurl: resolved.url,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
import { formatShortNpub } from "../../utils/formatters.js";
|
||||
import { sanitizeProfileMediaUrl } from "../../utils/profileMedia.js";
|
||||
|
||||
const DEFAULT_PROFILE_AVATAR = "assets/svg/default-profile.svg";
|
||||
|
||||
@@ -213,15 +214,26 @@ export class SimilarContentCard {
|
||||
continue;
|
||||
}
|
||||
const trimmed = entry.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const sanitized = sanitizeProfileMediaUrl(trimmed);
|
||||
if (sanitized) {
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
const fallback =
|
||||
typeof baseline.picture === "string" && baseline.picture.trim()
|
||||
? sanitizeProfileMediaUrl(baseline.picture) || baseline.picture.trim()
|
||||
: "";
|
||||
|
||||
return fallback;
|
||||
})();
|
||||
|
||||
return { name, npub, shortNpub, pubkey, picture };
|
||||
const resolvedPicture = picture || DEFAULT_PROFILE_AVATAR;
|
||||
|
||||
return { name, npub, shortNpub, pubkey, picture: resolvedPicture };
|
||||
}
|
||||
|
||||
applyIdentityToElements() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
import { updateVideoCardSourceVisibility } from "../../utils/cardSourceVisibility.js";
|
||||
import { sanitizeProfileMediaUrl } from "../../utils/profileMedia.js";
|
||||
import { userLogger } from "../../utils/logger.js";
|
||||
import {
|
||||
applyModerationContextDatasets,
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
import { buildModerationBadgeText } from "../moderationCopy.js";
|
||||
|
||||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
||||
const DEFAULT_PROFILE_AVATAR = "assets/svg/default-profile.svg";
|
||||
const HEX64_REGEX = /^[0-9a-f]{64}$/i;
|
||||
|
||||
export class VideoCard {
|
||||
constructor({
|
||||
@@ -27,6 +30,7 @@ export class VideoCard {
|
||||
assets = {},
|
||||
state = {},
|
||||
variant = "default",
|
||||
identity = null,
|
||||
ensureGlobalMoreMenuHandlers,
|
||||
onRequestCloseAllMenus,
|
||||
nsfwContext = null,
|
||||
@@ -91,6 +95,7 @@ export class VideoCard {
|
||||
: null;
|
||||
|
||||
this.designSystem = normalizeDesignSystemContext(designSystem);
|
||||
this.identity = this.normalizeIdentity(identity);
|
||||
|
||||
const normalizedVariant =
|
||||
typeof variant === "string" && variant.trim().toLowerCase() === "compact"
|
||||
@@ -461,6 +466,8 @@ export class VideoCard {
|
||||
header.appendChild(controls);
|
||||
}
|
||||
|
||||
this.applyIdentityToAuthorElements();
|
||||
|
||||
content.appendChild(title);
|
||||
content.appendChild(header);
|
||||
|
||||
@@ -501,6 +508,135 @@ export class VideoCard {
|
||||
this.refreshModerationUi();
|
||||
}
|
||||
|
||||
normalizeIdentity(nextIdentity = {}, fallback = null) {
|
||||
const baseline = fallback && typeof fallback === "object" ? fallback : {};
|
||||
const candidate =
|
||||
nextIdentity && typeof nextIdentity === "object" ? nextIdentity : {};
|
||||
const video = this.video && typeof this.video === "object" ? this.video : {};
|
||||
|
||||
const pickFirstString = (
|
||||
entries = [],
|
||||
{ allowHex = true, sanitizer = (value) => value } = {},
|
||||
) => {
|
||||
for (const entry of entries) {
|
||||
if (typeof entry !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
if (!allowHex && HEX64_REGEX.test(trimmed)) {
|
||||
continue;
|
||||
}
|
||||
const sanitized = sanitizer(trimmed);
|
||||
if (!sanitized) {
|
||||
continue;
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const pubkey = pickFirstString([
|
||||
candidate.pubkey,
|
||||
baseline.pubkey,
|
||||
video.pubkey,
|
||||
video.author?.pubkey,
|
||||
video.creator?.pubkey,
|
||||
video.profile?.pubkey,
|
||||
]);
|
||||
|
||||
const npub = pickFirstString([
|
||||
candidate.npub,
|
||||
baseline.npub,
|
||||
video.npub,
|
||||
video.authorNpub,
|
||||
video.profile?.npub,
|
||||
]);
|
||||
|
||||
const shortNpub = pickFirstString([
|
||||
candidate.shortNpub,
|
||||
baseline.shortNpub,
|
||||
video.shortNpub,
|
||||
video.creatorNpub,
|
||||
]);
|
||||
|
||||
const name = pickFirstString(
|
||||
[
|
||||
candidate.name,
|
||||
candidate.display_name,
|
||||
candidate.displayName,
|
||||
candidate.username,
|
||||
baseline.name,
|
||||
baseline.display_name,
|
||||
baseline.displayName,
|
||||
baseline.username,
|
||||
video.creatorName,
|
||||
video.authorName,
|
||||
video.creator?.name,
|
||||
video.author?.name,
|
||||
video.profile?.display_name,
|
||||
video.profile?.name,
|
||||
video.profile?.username,
|
||||
],
|
||||
{ allowHex: false },
|
||||
);
|
||||
|
||||
const picture = pickFirstString(
|
||||
[
|
||||
candidate.picture,
|
||||
candidate.image,
|
||||
candidate.photo,
|
||||
baseline.picture,
|
||||
baseline.image,
|
||||
baseline.photo,
|
||||
video.creatorPicture,
|
||||
video.authorPicture,
|
||||
video.creator?.picture,
|
||||
video.author?.picture,
|
||||
video.profile?.picture,
|
||||
video.profile?.image,
|
||||
video.profile?.photo,
|
||||
],
|
||||
{ sanitizer: (value) => sanitizeProfileMediaUrl(value) },
|
||||
);
|
||||
|
||||
const resolvedName = name || shortNpub || npub || pubkey || "";
|
||||
const resolvedPicture = picture || DEFAULT_PROFILE_AVATAR;
|
||||
|
||||
return {
|
||||
name: resolvedName,
|
||||
npub,
|
||||
shortNpub,
|
||||
pubkey,
|
||||
picture: resolvedPicture,
|
||||
};
|
||||
}
|
||||
|
||||
updateIdentity(nextIdentity = {}) {
|
||||
this.identity = this.normalizeIdentity(nextIdentity, this.identity);
|
||||
this.applyIdentityToAuthorElements();
|
||||
}
|
||||
|
||||
applyIdentityToAuthorElements() {
|
||||
const nameLabel = this.identity?.name || "Unknown";
|
||||
if (this.authorNameEl) {
|
||||
this.authorNameEl.textContent = nameLabel || "Unknown";
|
||||
}
|
||||
|
||||
if (this.authorPicEl) {
|
||||
const picture = this.identity?.picture || DEFAULT_PROFILE_AVATAR;
|
||||
if (this.authorPicEl.getAttribute("src") !== picture) {
|
||||
this.authorPicEl.src = picture;
|
||||
}
|
||||
|
||||
const altFallback =
|
||||
nameLabel || this.identity?.shortNpub || this.identity?.npub || "Channel";
|
||||
this.authorPicEl.alt = `${altFallback}'s avatar`;
|
||||
}
|
||||
}
|
||||
|
||||
setCardBackdropImage(src) {
|
||||
if (!this.root || !this.root.style) {
|
||||
return;
|
||||
|
||||
@@ -5063,12 +5063,30 @@ export class VideoModal {
|
||||
continue;
|
||||
}
|
||||
const trimmed = candidate.trim();
|
||||
if (trimmed) {
|
||||
picture = trimmed;
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const sanitized = sanitizeProfileMediaUrl(trimmed);
|
||||
if (sanitized) {
|
||||
picture = sanitized;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!picture && pubkey) {
|
||||
const robohash = `https://robohash.org/${pubkey}`;
|
||||
picture = sanitizeProfileMediaUrl(robohash) || robohash;
|
||||
}
|
||||
|
||||
if (!picture) {
|
||||
const fallbackAvatar =
|
||||
typeof this.DEFAULT_PROFILE_AVATAR === "string" &&
|
||||
this.DEFAULT_PROFILE_AVATAR.trim()
|
||||
? this.DEFAULT_PROFILE_AVATAR.trim()
|
||||
: "assets/svg/default-profile.svg";
|
||||
picture = sanitizeProfileMediaUrl(fallbackAvatar) || fallbackAvatar;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
npub,
|
||||
|
||||
@@ -556,6 +556,34 @@ export class VideoListView {
|
||||
this.utils.persistWatchHistoryMetadata(video, pointerInfo);
|
||||
}
|
||||
|
||||
const identity = {
|
||||
name:
|
||||
typeof video.creatorName === "string" && video.creatorName
|
||||
? video.creatorName
|
||||
: typeof video.authorName === "string"
|
||||
? video.authorName
|
||||
: "",
|
||||
picture:
|
||||
typeof video.creatorPicture === "string" && video.creatorPicture
|
||||
? video.creatorPicture
|
||||
: typeof video.authorPicture === "string"
|
||||
? video.authorPicture
|
||||
: "",
|
||||
pubkey: typeof video.pubkey === "string" ? video.pubkey : "",
|
||||
npub:
|
||||
typeof video.npub === "string" && video.npub
|
||||
? video.npub
|
||||
: typeof video.authorNpub === "string"
|
||||
? video.authorNpub
|
||||
: "",
|
||||
shortNpub:
|
||||
typeof video.shortNpub === "string" && video.shortNpub
|
||||
? video.shortNpub
|
||||
: typeof video.creatorNpub === "string"
|
||||
? video.creatorNpub
|
||||
: "",
|
||||
};
|
||||
|
||||
const videoCard = new VideoCard({
|
||||
document: this.document,
|
||||
video,
|
||||
@@ -566,6 +594,7 @@ export class VideoListView {
|
||||
postedAt: normalizedPostedAt,
|
||||
cardState,
|
||||
motionState,
|
||||
identity,
|
||||
nsfwContext: {
|
||||
isNsfw: video?.isNsfw === true,
|
||||
allowNsfw: this.allowNsfw,
|
||||
|
||||
+28
-19
@@ -23,6 +23,7 @@ import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
ensureWallet as ensureWalletDefault,
|
||||
sendPayment as sendPaymentDefault,
|
||||
resetWalletClient as resetWalletClientDefault,
|
||||
} from "../payments/nwcClient.js";
|
||||
|
||||
const DEFAULT_SPLIT_SUMMARY = "Enter an amount to view the split.";
|
||||
@@ -474,6 +475,10 @@ export default class ZapController {
|
||||
typeof this.payments?.sendPayment === "function"
|
||||
? (bolt11, params) => this.payments.sendPayment(bolt11, params)
|
||||
: (bolt11, params) => sendPaymentDefault(bolt11, params);
|
||||
const resetWalletClientFn =
|
||||
typeof this.payments?.resetWalletClient === "function"
|
||||
? () => this.payments.resetWalletClient()
|
||||
: () => resetWalletClientDefault();
|
||||
|
||||
let activeShare = null;
|
||||
|
||||
@@ -543,18 +548,18 @@ export default class ZapController {
|
||||
}
|
||||
},
|
||||
sendPayment: async (bolt11, params) => {
|
||||
const shareType = activeShare || "unknown";
|
||||
const amount =
|
||||
shareType === "platform"
|
||||
? shares.platformShare
|
||||
: shares.creatorShare;
|
||||
const address =
|
||||
shareType === "platform"
|
||||
? platformEntry?.address || getCachedPlatformLightningAddress()
|
||||
: creatorEntry?.address || lightningAddress;
|
||||
try {
|
||||
const payment = await sendPaymentFn(bolt11, params);
|
||||
if (Array.isArray(shareTracker)) {
|
||||
const shareType = activeShare || "unknown";
|
||||
const amount =
|
||||
shareType === "platform"
|
||||
? shares.platformShare
|
||||
: shares.creatorShare;
|
||||
const address =
|
||||
shareType === "platform"
|
||||
? platformEntry?.address || getCachedPlatformLightningAddress()
|
||||
: creatorEntry?.address || lightningAddress;
|
||||
shareTracker.push({
|
||||
type: shareType,
|
||||
status: "success",
|
||||
@@ -566,15 +571,6 @@ export default class ZapController {
|
||||
return payment;
|
||||
} catch (error) {
|
||||
if (Array.isArray(shareTracker)) {
|
||||
const shareType = activeShare || "unknown";
|
||||
const amount =
|
||||
shareType === "platform"
|
||||
? shares.platformShare
|
||||
: shares.creatorShare;
|
||||
const address =
|
||||
shareType === "platform"
|
||||
? platformEntry?.address || getCachedPlatformLightningAddress()
|
||||
: creatorEntry?.address || lightningAddress;
|
||||
shareTracker.push({
|
||||
type: shareType,
|
||||
status: "error",
|
||||
@@ -583,10 +579,23 @@ export default class ZapController {
|
||||
error,
|
||||
});
|
||||
}
|
||||
if (
|
||||
typeof error?.message === "string" &&
|
||||
error.message.toLowerCase().includes("timed out")
|
||||
) {
|
||||
try {
|
||||
resetWalletClientFn();
|
||||
} catch (resetError) {
|
||||
devLogger.warn(
|
||||
"[zap] Failed to reset wallet client after timeout",
|
||||
resetError
|
||||
);
|
||||
}
|
||||
}
|
||||
logZapError(
|
||||
"wallet.sendPayment",
|
||||
{
|
||||
shareType: activeShare || "unknown",
|
||||
shareType,
|
||||
amount,
|
||||
address,
|
||||
tracker: shareTracker,
|
||||
|
||||
@@ -13,11 +13,13 @@ await (async () => {
|
||||
invoice: "bolt11-invoice",
|
||||
amountSats: 123,
|
||||
zapRequest: "zap-request-json",
|
||||
lnurl: " https://pay.example/lnurlp/test ",
|
||||
});
|
||||
|
||||
assert.equal(params.invoice, "bolt11-invoice");
|
||||
assert.equal(params.amount, 123_000);
|
||||
assert.equal(params.zap_request, "zap-request-json");
|
||||
assert.equal(params.lnurl, "https://pay.example/lnurlp/test");
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
|
||||
Reference in new issue
Block a user