mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Defer subscription and hashtag decrypt prompts
This commit is contained in:
1 parent
2ed1a37518
commit
379287c87c
5 files changed
+346
-34
No files matched your search
@@ -1049,6 +1049,12 @@
|
||||
<p class="text-sm text-muted">
|
||||
These are the creators you’ve subscribed to directly in bitvid.
|
||||
</p>
|
||||
<p
|
||||
id="subscriptionsStatus"
|
||||
class="text-sm text-muted"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
></p>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h3 class="text-base font-semibold text-text">Subscriptions</h3>
|
||||
<button
|
||||
|
||||
@@ -2389,7 +2389,7 @@ class Application {
|
||||
this.tagPreferenceMenuController.refreshActiveMenus();
|
||||
}
|
||||
|
||||
async loadHashtagPreferencesForPubkey(pubkey) {
|
||||
async loadHashtagPreferencesForPubkey(pubkey, options = {}) {
|
||||
if (
|
||||
!this.hashtagPreferences ||
|
||||
typeof this.hashtagPreferences.load !== "function"
|
||||
@@ -2398,7 +2398,7 @@ class Application {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.hashtagPreferences.load(pubkey);
|
||||
await this.hashtagPreferences.load(pubkey, options);
|
||||
return true;
|
||||
} catch (error) {
|
||||
devLogger.error(
|
||||
@@ -4189,7 +4189,9 @@ class Application {
|
||||
}
|
||||
|
||||
const hashtagPreferencesPromise = Promise.resolve(
|
||||
this.loadHashtagPreferencesForPubkey(loginContext.pubkey),
|
||||
this.loadHashtagPreferencesForPubkey(loginContext.pubkey, {
|
||||
allowPermissionPrompt: false,
|
||||
}),
|
||||
).catch((error) => {
|
||||
devLogger.warn(
|
||||
"[Application] Background hashtag preferences load failed:",
|
||||
@@ -4343,7 +4345,9 @@ class Application {
|
||||
});
|
||||
|
||||
// Load subscriptions in the background to avoid blocking the main feed.
|
||||
subscriptions.ensureLoaded(activePubkey).catch((error) => {
|
||||
subscriptions
|
||||
.ensureLoaded(activePubkey, { allowPermissionPrompt: false })
|
||||
.catch((error) => {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to load subscriptions during login:",
|
||||
error
|
||||
|
||||
@@ -238,6 +238,7 @@ class HashtagPreferencesService {
|
||||
this.eventId = null;
|
||||
this.eventCreatedAt = null;
|
||||
this.loaded = false;
|
||||
this.backgroundLoading = false;
|
||||
this.preferencesVersion = DEFAULT_VERSION;
|
||||
this.decryptRetryTimeoutId = null;
|
||||
|
||||
@@ -245,12 +246,12 @@ class HashtagPreferencesService {
|
||||
if (event === "profileChanged") {
|
||||
this.reset();
|
||||
if (detail.pubkey) {
|
||||
this.load(detail.pubkey);
|
||||
this.load(detail.pubkey, { allowPermissionPrompt: false });
|
||||
}
|
||||
} else if (event === "runtimeCleared" && detail.pubkey === this.activePubkey) {
|
||||
this.reset();
|
||||
if (this.activePubkey) {
|
||||
this.load(this.activePubkey);
|
||||
this.load(this.activePubkey, { allowPermissionPrompt: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -266,6 +267,7 @@ class HashtagPreferencesService {
|
||||
this.eventId = null;
|
||||
this.eventCreatedAt = null;
|
||||
this.loaded = false;
|
||||
this.backgroundLoading = false;
|
||||
this.preferencesVersion = DEFAULT_VERSION;
|
||||
if (this.decryptRetryTimeoutId) {
|
||||
clearTimeout(this.decryptRetryTimeoutId);
|
||||
@@ -425,7 +427,7 @@ class HashtagPreferencesService {
|
||||
}
|
||||
}
|
||||
|
||||
scheduleDecryptRetry(pubkey, error) {
|
||||
scheduleDecryptRetry(pubkey, error, options = {}) {
|
||||
const normalized = normalizeHexPubkey(pubkey);
|
||||
if (!normalized) {
|
||||
return;
|
||||
@@ -438,7 +440,7 @@ class HashtagPreferencesService {
|
||||
if (this.activePubkey && this.activePubkey !== normalized) {
|
||||
return;
|
||||
}
|
||||
this.load(normalized).catch((retryError) => {
|
||||
this.load(normalized, options).catch((retryError) => {
|
||||
userLogger.warn(`${LOG_PREFIX} Decryption retry failed`, retryError);
|
||||
});
|
||||
}, DECRYPT_RETRY_DELAY_MS);
|
||||
@@ -448,14 +450,18 @@ class HashtagPreferencesService {
|
||||
);
|
||||
}
|
||||
|
||||
async load(pubkey) {
|
||||
async load(pubkey, options = {}) {
|
||||
const normalized = normalizeHexPubkey(pubkey);
|
||||
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
|
||||
let wasLoadedForUser =
|
||||
this.activePubkey &&
|
||||
this.activePubkey === normalized &&
|
||||
(this.interests.size > 0 || this.disinterests.size > 0 || this.loaded);
|
||||
|
||||
this.activePubkey = normalized;
|
||||
if (allowPermissionPrompt) {
|
||||
this.backgroundLoading = false;
|
||||
}
|
||||
|
||||
if (!normalized) {
|
||||
this.reset();
|
||||
@@ -466,7 +472,12 @@ class HashtagPreferencesService {
|
||||
// Try cache first
|
||||
if (this.loadFromCache(normalized)) {
|
||||
wasLoadedForUser = true;
|
||||
if (!allowPermissionPrompt && !this.backgroundLoading) {
|
||||
this.backgroundLoading = true;
|
||||
this.emitChange("background-loading", { background: true });
|
||||
}
|
||||
}
|
||||
const wasBackgroundLoading = this.backgroundLoading;
|
||||
|
||||
if (
|
||||
!nostrClient ||
|
||||
@@ -481,10 +492,18 @@ class HashtagPreferencesService {
|
||||
userLogger.warn(
|
||||
`${LOG_PREFIX} Keeping existing preferences despite client unavailability.`,
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -495,10 +514,18 @@ class HashtagPreferencesService {
|
||||
userLogger.warn(
|
||||
`${LOG_PREFIX} Keeping existing preferences; no relays available for refresh.`,
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -553,6 +580,10 @@ class HashtagPreferencesService {
|
||||
`${LOG_PREFIX} Failed to load hashtag preferences (keeping local state if any).`,
|
||||
fetchError,
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -563,11 +594,19 @@ class HashtagPreferencesService {
|
||||
userLogger.warn(
|
||||
`${LOG_PREFIX} Keeping existing preferences despite empty relay response.`,
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -600,10 +639,18 @@ class HashtagPreferencesService {
|
||||
|
||||
if (!latest) {
|
||||
if (wasLoadedForUser) {
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -616,12 +663,18 @@ class HashtagPreferencesService {
|
||||
devLogger.log(
|
||||
`${LOG_PREFIX} Ignoring stale/concurrent preferences event (remote: ${latestCreatedAt}, local: ${currentCreatedAt}).`,
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let decryptResult;
|
||||
try {
|
||||
const decryptPromise = this.decryptEvent(latest, normalized);
|
||||
const decryptPromise = this.decryptEvent(latest, normalized, {
|
||||
allowPermissionPrompt,
|
||||
});
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(
|
||||
() => {
|
||||
@@ -649,7 +702,15 @@ class HashtagPreferencesService {
|
||||
if (!wasLoadedForUser && !this.loaded) {
|
||||
this.loaded = true;
|
||||
}
|
||||
this.scheduleDecryptRetry(normalized, decryptResult.error);
|
||||
this.scheduleDecryptRetry(normalized, decryptResult.error, {
|
||||
allowPermissionPrompt,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!allowPermissionPrompt &&
|
||||
decryptResult.error?.code === "hashtag-preferences-permission-required"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -659,11 +720,19 @@ class HashtagPreferencesService {
|
||||
userLogger.warn(
|
||||
`${LOG_PREFIX} Preserving cached preferences despite decryption failure.`,
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitChange("background-loaded", { background: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -681,6 +750,9 @@ class HashtagPreferencesService {
|
||||
? latest.created_at
|
||||
: null;
|
||||
this.loaded = true;
|
||||
if (this.backgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
}
|
||||
this.saveToCache();
|
||||
this.emitChange("sync", { scheme: decryptResult.scheme });
|
||||
} catch (error) {
|
||||
@@ -693,7 +765,7 @@ class HashtagPreferencesService {
|
||||
}
|
||||
}
|
||||
|
||||
async decryptEvent(event, userPubkey) {
|
||||
async decryptEvent(event, userPubkey, options = {}) {
|
||||
const ciphertext = typeof event?.content === "string" ? event.content : "";
|
||||
if (!ciphertext) {
|
||||
const error = new Error("Preference event missing ciphertext content.");
|
||||
@@ -701,8 +773,13 @@ class HashtagPreferencesService {
|
||||
return { ok: false, error };
|
||||
}
|
||||
|
||||
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
|
||||
let signer = getActiveSigner();
|
||||
if (!signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function") {
|
||||
if (
|
||||
!signer &&
|
||||
typeof nostrClient?.ensureActiveSignerForPubkey === "function" &&
|
||||
allowPermissionPrompt
|
||||
) {
|
||||
signer = await nostrClient.ensureActiveSignerForPubkey(userPubkey);
|
||||
}
|
||||
const signerHasNip04 = typeof signer?.nip04Decrypt === "function";
|
||||
@@ -716,6 +793,13 @@ class HashtagPreferencesService {
|
||||
(!signerHasNip44 && requiresNip44) ||
|
||||
(!signerHasNip04 && requiresNip04)
|
||||
) {
|
||||
if (!allowPermissionPrompt) {
|
||||
const error = new Error(
|
||||
"Decrypt permissions are required to read hashtag preferences.",
|
||||
);
|
||||
error.code = "hashtag-preferences-permission-required";
|
||||
return { ok: false, error };
|
||||
}
|
||||
try {
|
||||
await requestDefaultExtensionPermissions();
|
||||
} catch (error) {
|
||||
@@ -766,7 +850,11 @@ class HashtagPreferencesService {
|
||||
);
|
||||
}
|
||||
|
||||
const nostrApi = typeof window !== "undefined" ? window?.nostr : null;
|
||||
const nostrApi = allowPermissionPrompt
|
||||
? typeof window !== "undefined"
|
||||
? window?.nostr
|
||||
: null
|
||||
: null;
|
||||
if (nostrApi) {
|
||||
if (typeof nostrApi.nip04?.decrypt === "function") {
|
||||
registerDecryptor(
|
||||
|
||||
+84
-14
@@ -329,6 +329,7 @@ class SubscriptionsManager {
|
||||
this.currentUserPubkey = null;
|
||||
this.loaded = false;
|
||||
this.loadingPromise = null;
|
||||
this.backgroundLoading = false;
|
||||
this.subscriptionListView = null;
|
||||
this.lastRunOptions = null;
|
||||
this.lastResult = null;
|
||||
@@ -350,7 +351,9 @@ class SubscriptionsManager {
|
||||
} else if (event === "runtimeCleared" && detail.pubkey === this.currentUserPubkey) {
|
||||
this.reset();
|
||||
if (this.currentUserPubkey) {
|
||||
this.loadSubscriptions(this.currentUserPubkey);
|
||||
this.loadSubscriptions(this.currentUserPubkey, {
|
||||
allowPermissionPrompt: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -359,12 +362,13 @@ class SubscriptionsManager {
|
||||
/**
|
||||
* Decrypt the subscription list from kind=30000 (d="subscriptions").
|
||||
*/
|
||||
async loadSubscriptions(userPubkey) {
|
||||
async loadSubscriptions(userPubkey, options = {}) {
|
||||
if (!userPubkey) {
|
||||
userLogger.warn("[SubscriptionsManager] No pubkey => cannot load subs.");
|
||||
return;
|
||||
}
|
||||
|
||||
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
|
||||
const normalizedUserPubkey = normalizeHexPubkey(userPubkey) || userPubkey;
|
||||
|
||||
// 1. Attempt to load from cache first
|
||||
@@ -379,14 +383,25 @@ class SubscriptionsManager {
|
||||
this.loaded = true;
|
||||
|
||||
// Trigger background update
|
||||
this.updateFromRelays(userPubkey).catch((err) => {
|
||||
if (!allowPermissionPrompt && !this.backgroundLoading) {
|
||||
this.backgroundLoading = true;
|
||||
this.emitter.emit("change", {
|
||||
action: "background-loading",
|
||||
subscribedPubkeys: Array.from(this.subscribedPubkeys),
|
||||
background: true,
|
||||
});
|
||||
} else if (allowPermissionPrompt) {
|
||||
this.backgroundLoading = false;
|
||||
}
|
||||
|
||||
this.updateFromRelays(userPubkey, { allowPermissionPrompt }).catch((err) => {
|
||||
devLogger.warn("[SubscriptionsManager] Background update failed:", err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If no cache, must wait for relays
|
||||
await this.updateFromRelays(userPubkey);
|
||||
await this.updateFromRelays(userPubkey, { allowPermissionPrompt });
|
||||
}
|
||||
|
||||
saveToCache(userPubkey) {
|
||||
@@ -402,7 +417,7 @@ class SubscriptionsManager {
|
||||
return this.emitter.on(eventName, handler);
|
||||
}
|
||||
|
||||
scheduleDecryptRetry(userPubkey, error) {
|
||||
scheduleDecryptRetry(userPubkey, error, options = {}) {
|
||||
const normalized = normalizeHexPubkey(userPubkey) || userPubkey;
|
||||
if (!normalized) {
|
||||
return;
|
||||
@@ -415,7 +430,7 @@ class SubscriptionsManager {
|
||||
if (this.currentUserPubkey && this.currentUserPubkey !== normalized) {
|
||||
return;
|
||||
}
|
||||
this.updateFromRelays(normalized).catch((retryError) => {
|
||||
this.updateFromRelays(normalized, options).catch((retryError) => {
|
||||
userLogger.warn("[SubscriptionsManager] Decryption retry failed:", retryError);
|
||||
});
|
||||
}, DECRYPT_RETRY_DELAY_MS);
|
||||
@@ -425,10 +440,12 @@ class SubscriptionsManager {
|
||||
);
|
||||
}
|
||||
|
||||
async updateFromRelays(userPubkey) {
|
||||
async updateFromRelays(userPubkey, options = {}) {
|
||||
if (!userPubkey) return;
|
||||
|
||||
try {
|
||||
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
|
||||
const wasBackgroundLoading = this.backgroundLoading;
|
||||
const normalizedUserPubkey = normalizeHexPubkey(userPubkey) || userPubkey;
|
||||
|
||||
const readRelays = relayManager.getReadRelayUrls();
|
||||
@@ -437,6 +454,13 @@ class SubscriptionsManager {
|
||||
devLogger.warn(
|
||||
"[SubscriptionsManager] No relay URLs available while loading subscriptions.",
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitter.emit("change", {
|
||||
action: "background-loaded",
|
||||
subscribedPubkeys: Array.from(this.subscribedPubkeys),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -516,6 +540,13 @@ class SubscriptionsManager {
|
||||
// This means no updates. We keep what we have.
|
||||
devLogger.log("[SubscriptionsManager] No updates from relays.");
|
||||
}
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitter.emit("change", {
|
||||
action: "background-loaded",
|
||||
subscribedPubkeys: Array.from(this.subscribedPubkeys),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -534,7 +565,9 @@ class SubscriptionsManager {
|
||||
|
||||
let decryptResult;
|
||||
try {
|
||||
const decryptPromise = this.decryptSubscriptionEvent(newest, userPubkey);
|
||||
const decryptPromise = this.decryptSubscriptionEvent(newest, userPubkey, {
|
||||
allowPermissionPrompt,
|
||||
});
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(
|
||||
() => {
|
||||
@@ -557,13 +590,28 @@ class SubscriptionsManager {
|
||||
if (!this.loaded && !cachedSnapshot.hasSnapshot) {
|
||||
this.loaded = true;
|
||||
}
|
||||
this.scheduleDecryptRetry(normalizedUserPubkey, decryptResult.error);
|
||||
this.scheduleDecryptRetry(normalizedUserPubkey, decryptResult.error, {
|
||||
allowPermissionPrompt,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!allowPermissionPrompt &&
|
||||
decryptResult.error?.code === "subscriptions-permission-required"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
userLogger.error(
|
||||
"[SubscriptionsManager] Failed to decrypt subscription list:",
|
||||
decryptResult.error,
|
||||
);
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
this.emitter.emit("change", {
|
||||
action: "background-loaded",
|
||||
subscribedPubkeys: Array.from(this.subscribedPubkeys),
|
||||
});
|
||||
}
|
||||
if (!this.loaded) {
|
||||
this.subscribedPubkeys.clear();
|
||||
this.subsEventId = null;
|
||||
@@ -583,6 +631,9 @@ class SubscriptionsManager {
|
||||
this.subscribedPubkeys = newSet;
|
||||
this.currentUserPubkey = normalizedUserPubkey;
|
||||
this.loaded = true;
|
||||
if (wasBackgroundLoading) {
|
||||
this.backgroundLoading = false;
|
||||
}
|
||||
|
||||
// Update persistent cache
|
||||
this.saveToCache(normalizedUserPubkey);
|
||||
@@ -610,6 +661,11 @@ class SubscriptionsManager {
|
||||
error
|
||||
);
|
||||
});
|
||||
} else if (wasBackgroundLoading) {
|
||||
this.emitter.emit("change", {
|
||||
action: "background-loaded",
|
||||
subscribedPubkeys: Array.from(this.subscribedPubkeys),
|
||||
});
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
@@ -623,6 +679,7 @@ class SubscriptionsManager {
|
||||
this.subsEventCreatedAt = null;
|
||||
this.currentUserPubkey = null;
|
||||
this.loaded = false;
|
||||
this.backgroundLoading = false;
|
||||
this.lastRunOptions = null;
|
||||
this.lastResult = null;
|
||||
this.hasRenderedOnce = false;
|
||||
@@ -633,7 +690,7 @@ class SubscriptionsManager {
|
||||
this.emitter.emit("change", { action: "reset", subscribedPubkeys: [] });
|
||||
}
|
||||
|
||||
async ensureLoaded(actorHex) {
|
||||
async ensureLoaded(actorHex, options = {}) {
|
||||
devLogger.log("[SubscriptionsManager] ensureLoaded start", actorHex);
|
||||
const normalizedActor = normalizeHexPubkey(actorHex) || actorHex;
|
||||
if (!normalizedActor) {
|
||||
@@ -655,7 +712,7 @@ class SubscriptionsManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = this.loadSubscriptions(normalizedActor);
|
||||
const loader = this.loadSubscriptions(normalizedActor, options);
|
||||
this.loadingPromise = loader;
|
||||
|
||||
try {
|
||||
@@ -709,7 +766,7 @@ class SubscriptionsManager {
|
||||
return Array.from(this.subscribedPubkeys);
|
||||
}
|
||||
|
||||
async decryptSubscriptionEvent(event, userPubkey) {
|
||||
async decryptSubscriptionEvent(event, userPubkey, options = {}) {
|
||||
const ciphertext = typeof event?.content === "string" ? event.content : "";
|
||||
if (!ciphertext) {
|
||||
const error = new Error("Subscription event is missing ciphertext content.");
|
||||
@@ -717,6 +774,7 @@ class SubscriptionsManager {
|
||||
return { ok: false, error };
|
||||
}
|
||||
|
||||
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
|
||||
const hints = extractEncryptionHints(event);
|
||||
const requiresNip44 = hints.includes("nip44") || hints.includes("nip44_v2");
|
||||
const requiresNip04 = !hints.length || hints.includes("nip04") || !requiresNip44;
|
||||
@@ -731,7 +789,8 @@ class SubscriptionsManager {
|
||||
if (
|
||||
!signer ||
|
||||
(!signerHasRequiredDecryptors(signer) &&
|
||||
typeof nostrClient?.ensureActiveSignerForPubkey === "function")
|
||||
typeof nostrClient?.ensureActiveSignerForPubkey === "function" &&
|
||||
allowPermissionPrompt)
|
||||
) {
|
||||
signer = await nostrClient.ensureActiveSignerForPubkey(userPubkey);
|
||||
}
|
||||
@@ -739,7 +798,11 @@ class SubscriptionsManager {
|
||||
const signerHasNip04 = typeof signer?.nip04Decrypt === "function";
|
||||
const signerHasNip44 = typeof signer?.nip44Decrypt === "function";
|
||||
|
||||
const nostrApi = typeof window !== "undefined" ? window?.nostr : null;
|
||||
const nostrApi = allowPermissionPrompt
|
||||
? typeof window !== "undefined"
|
||||
? window?.nostr
|
||||
: null
|
||||
: null;
|
||||
const windowHasNip04 = typeof nostrApi?.nip04?.decrypt === "function";
|
||||
const windowHasNip44 =
|
||||
(nostrApi?.nip44 && typeof nostrApi.nip44.decrypt === "function") ||
|
||||
@@ -749,6 +812,13 @@ class SubscriptionsManager {
|
||||
(!signerHasNip44 && !windowHasNip44 && requiresNip44) ||
|
||||
(!signerHasNip04 && !windowHasNip04 && requiresNip04)
|
||||
) {
|
||||
if (!allowPermissionPrompt) {
|
||||
const error = new Error(
|
||||
"Decrypt permissions are required to read subscriptions."
|
||||
);
|
||||
error.code = "subscriptions-permission-required";
|
||||
return { ok: false, error };
|
||||
}
|
||||
try {
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult?.ok) {
|
||||
|
||||
@@ -1314,6 +1314,7 @@ export class ProfileModalController {
|
||||
this.walletStatusText = null;
|
||||
this.profileWalletStatusText = null;
|
||||
this.hashtagStatusText = null;
|
||||
this.hashtagBackgroundLoading = false;
|
||||
this.hashtagInterestList = null;
|
||||
this.hashtagInterestEmpty = null;
|
||||
this.hashtagInterestInput = null;
|
||||
@@ -1324,6 +1325,8 @@ export class ProfileModalController {
|
||||
this.hashtagDisinterestInput = null;
|
||||
this.addHashtagDisinterestButton = null;
|
||||
this.profileHashtagDisinterestRefreshBtn = null;
|
||||
this.subscriptionsStatusText = null;
|
||||
this.subscriptionsBackgroundLoading = false;
|
||||
this.profileSubscriptionsRefreshBtn = null;
|
||||
this.profileFriendsRefreshBtn = null;
|
||||
this.profileBlockedRefreshBtn = null;
|
||||
@@ -1421,8 +1424,8 @@ export class ProfileModalController {
|
||||
this.subscriptionsService &&
|
||||
typeof this.subscriptionsService.on === "function"
|
||||
) {
|
||||
this.subscriptionsService.on("change", () => {
|
||||
void this.populateSubscriptionsList();
|
||||
this.subscriptionsService.on("change", (detail) => {
|
||||
this.handleSubscriptionsChange(detail);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1718,6 +1721,8 @@ export class ProfileModalController {
|
||||
document.getElementById("profileAddHashtagDisinterestBtn") || null;
|
||||
this.profileHashtagDisinterestRefreshBtn =
|
||||
document.getElementById("profileHashtagDisinterestRefreshBtn") || null;
|
||||
this.subscriptionsStatusText =
|
||||
document.getElementById("subscriptionsStatus") || null;
|
||||
|
||||
this.profileRelayList = this.relayList;
|
||||
this.profileRelayInput = this.relayInput;
|
||||
@@ -5343,7 +5348,7 @@ export class ProfileModalController {
|
||||
return;
|
||||
}
|
||||
void service
|
||||
.loadSubscriptions(activeHex)
|
||||
.loadSubscriptions(activeHex, { allowPermissionPrompt: true })
|
||||
.then(() => {
|
||||
void this.populateSubscriptionsList();
|
||||
})
|
||||
@@ -5397,7 +5402,9 @@ export class ProfileModalController {
|
||||
if (!service || typeof service.load !== "function") {
|
||||
return;
|
||||
}
|
||||
void service.load(activeHex).catch((error) => {
|
||||
void service
|
||||
.load(activeHex, { allowPermissionPrompt: true })
|
||||
.catch((error) => {
|
||||
devLogger.warn(
|
||||
"[profileModal] Failed to refresh hashtag preferences:",
|
||||
error,
|
||||
@@ -7072,11 +7079,20 @@ export class ProfileModalController {
|
||||
this.populateStoragePane();
|
||||
} else if (target === "hashtags") {
|
||||
this.populateHashtagPreferences();
|
||||
if (activeHex && this.hashtagPreferencesService) {
|
||||
this.hashtagPreferencesService
|
||||
.load(activeHex, { allowPermissionPrompt: true })
|
||||
.catch(noop);
|
||||
}
|
||||
this.refreshHashtagBackgroundStatus();
|
||||
} else if (target === "subscriptions") {
|
||||
if (activeHex && this.subscriptionsService) {
|
||||
this.subscriptionsService.loadSubscriptions(activeHex).catch(noop);
|
||||
this.subscriptionsService
|
||||
.loadSubscriptions(activeHex, { allowPermissionPrompt: true })
|
||||
.catch(noop);
|
||||
}
|
||||
void this.populateSubscriptionsList();
|
||||
this.refreshSubscriptionsBackgroundStatus();
|
||||
} else if (target === "blocked") {
|
||||
if (activeHex && this.services.userBlocks) {
|
||||
this.services.userBlocks.loadBlocks(activeHex).catch(noop);
|
||||
@@ -7762,6 +7778,89 @@ export class ProfileModalController {
|
||||
}
|
||||
}
|
||||
|
||||
setSubscriptionsStatus(message = "", tone = "muted") {
|
||||
if (!(this.subscriptionsStatusText instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const classList = this.subscriptionsStatusText.classList;
|
||||
classList.remove(
|
||||
"text-status-success",
|
||||
"text-status-warning",
|
||||
"text-status-danger",
|
||||
"text-status-info",
|
||||
"text-muted",
|
||||
);
|
||||
|
||||
const normalized =
|
||||
typeof message === "string" && message.trim() ? message.trim() : "";
|
||||
|
||||
if (!normalized) {
|
||||
this.subscriptionsStatusText.textContent = "";
|
||||
this.subscriptionsStatusText.classList.add("text-muted", "hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
this.subscriptionsStatusText.textContent = normalized;
|
||||
this.subscriptionsStatusText.classList.remove("hidden");
|
||||
|
||||
switch (tone) {
|
||||
case "success":
|
||||
classList.add("text-status-success");
|
||||
break;
|
||||
case "warning":
|
||||
case "error":
|
||||
classList.add("text-status-warning");
|
||||
break;
|
||||
case "info":
|
||||
classList.add("text-status-info");
|
||||
break;
|
||||
default:
|
||||
classList.add("text-muted");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
refreshHashtagBackgroundStatus() {
|
||||
const isBackground = this.hashtagPreferencesService?.backgroundLoading === true;
|
||||
const statusText = this.hashtagStatusText?.textContent?.trim?.() || "";
|
||||
|
||||
if (isBackground && !this.hashtagBackgroundLoading) {
|
||||
this.hashtagBackgroundLoading = true;
|
||||
if (!statusText) {
|
||||
this.setHashtagStatus("Loading in background…", "info");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isBackground && this.hashtagBackgroundLoading) {
|
||||
if (statusText === "Loading in background…") {
|
||||
this.setHashtagStatus("", "muted");
|
||||
}
|
||||
this.hashtagBackgroundLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
refreshSubscriptionsBackgroundStatus() {
|
||||
const isBackground = this.subscriptionsService?.backgroundLoading === true;
|
||||
const statusText = this.subscriptionsStatusText?.textContent?.trim?.() || "";
|
||||
|
||||
if (isBackground && !this.subscriptionsBackgroundLoading) {
|
||||
this.subscriptionsBackgroundLoading = true;
|
||||
if (!statusText) {
|
||||
this.setSubscriptionsStatus("Loading in background…", "info");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isBackground && this.subscriptionsBackgroundLoading) {
|
||||
if (statusText === "Loading in background…") {
|
||||
this.setSubscriptionsStatus("", "muted");
|
||||
}
|
||||
this.subscriptionsBackgroundLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
clearHashtagInputs() {
|
||||
if (this.hashtagInterestInput instanceof HTMLInputElement) {
|
||||
this.hashtagInterestInput.value = "";
|
||||
@@ -7780,6 +7879,7 @@ export class ProfileModalController {
|
||||
if (!snapshot.interests.length && !snapshot.disinterests.length) {
|
||||
this.setHashtagStatus("", "muted");
|
||||
}
|
||||
this.refreshHashtagBackgroundStatus();
|
||||
}
|
||||
|
||||
renderHashtagList(type, tags) {
|
||||
@@ -8097,7 +8197,45 @@ export class ProfileModalController {
|
||||
detail && typeof detail.preferences === "object"
|
||||
? detail.preferences
|
||||
: detail;
|
||||
const action = typeof detail?.action === "string" ? detail.action : "";
|
||||
|
||||
if (action === "background-loading") {
|
||||
this.hashtagBackgroundLoading = true;
|
||||
this.setHashtagStatus("Loading in background…", "info");
|
||||
} else if (
|
||||
(action === "sync" || action === "background-loaded" || action === "reset") &&
|
||||
this.hashtagBackgroundLoading
|
||||
) {
|
||||
const statusText = this.hashtagStatusText?.textContent?.trim?.() || "";
|
||||
if (statusText === "Loading in background…") {
|
||||
this.setHashtagStatus("", "muted");
|
||||
}
|
||||
this.hashtagBackgroundLoading = false;
|
||||
}
|
||||
|
||||
this.populateHashtagPreferences(preferences);
|
||||
this.refreshHashtagBackgroundStatus();
|
||||
}
|
||||
|
||||
handleSubscriptionsChange(detail = {}) {
|
||||
const action = typeof detail?.action === "string" ? detail.action : "";
|
||||
|
||||
if (action === "background-loading") {
|
||||
this.subscriptionsBackgroundLoading = true;
|
||||
this.setSubscriptionsStatus("Loading in background…", "info");
|
||||
} else if (
|
||||
(action === "sync" || action === "background-loaded" || action === "reset") &&
|
||||
this.subscriptionsBackgroundLoading
|
||||
) {
|
||||
const statusText = this.subscriptionsStatusText?.textContent?.trim?.() || "";
|
||||
if (statusText === "Loading in background…") {
|
||||
this.setSubscriptionsStatus("", "muted");
|
||||
}
|
||||
this.subscriptionsBackgroundLoading = false;
|
||||
}
|
||||
|
||||
void this.populateSubscriptionsList();
|
||||
this.refreshSubscriptionsBackgroundStatus();
|
||||
}
|
||||
|
||||
populateBlockedList(blocked = null) {
|
||||
@@ -8291,12 +8429,14 @@ export class ProfileModalController {
|
||||
const service = this.subscriptionsService;
|
||||
if (!service) {
|
||||
this.clearSubscriptionsList();
|
||||
this.refreshSubscriptionsBackgroundStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
const activeHex = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
if (!activeHex) {
|
||||
this.clearSubscriptionsList();
|
||||
this.refreshSubscriptionsBackgroundStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8395,6 +8535,7 @@ export class ProfileModalController {
|
||||
if (!deduped.length) {
|
||||
this.subscriptionListEmpty.classList.remove("hidden");
|
||||
this.subscriptionList.classList.add("hidden");
|
||||
this.refreshSubscriptionsBackgroundStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8491,6 +8632,7 @@ export class ProfileModalController {
|
||||
);
|
||||
}
|
||||
}
|
||||
this.refreshSubscriptionsBackgroundStatus();
|
||||
} catch (error) {
|
||||
devLogger.warn("[profileModal] Failed to populate subscriptions list:", error);
|
||||
}
|
||||
@@ -13005,7 +13147,9 @@ export class ProfileModalController {
|
||||
this.services.userBlocks.loadBlocks(activePubkey).catch(noop);
|
||||
}
|
||||
if (this.subscriptionsService) {
|
||||
this.subscriptionsService.loadSubscriptions(activePubkey).catch(noop);
|
||||
this.subscriptionsService
|
||||
.loadSubscriptions(activePubkey, { allowPermissionPrompt: false })
|
||||
.catch(noop);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user