mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(subscriptions): improve reliability of list loading and button responsiveness
- Implemented optimistic UI updates for the channel subscribe button to improve responsiveness. - Fixed a race condition in `js/subscriptions.js` and `js/services/hashtagPreferencesService.js` where lists were wiped on refresh due to `profileCache.activePubkey` not being initialized in time. Now using `profileCache.getProfileData(pubkey, ...)` to ensure the correct cache entry is loaded. - Validated with targeted unit tests. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
560dcf96b0
commit
73541168f7
4 files changed
+81
-3
No files matched your search
+18
-1
@@ -4163,11 +4163,28 @@ function renderSubscribeButton(channelHex) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimistic UI update
|
||||
const wasSubscribed = toggleBtn.dataset.state === "subscribed";
|
||||
const nextState = wasSubscribed ? "unsubscribed" : "subscribed";
|
||||
const label = toggleBtn.querySelector("span");
|
||||
|
||||
toggleBtn.dataset.state = nextState;
|
||||
if (label) {
|
||||
label.textContent = nextState === "subscribed" ? "Unsubscribe" : "Subscribe";
|
||||
}
|
||||
|
||||
try {
|
||||
await subscriptions.toggleChannel(channelHex, currentApp.pubkey);
|
||||
// Re-render the button so it toggles state
|
||||
// Re-render to ensure state is perfectly synced with the service
|
||||
renderSubscribeButton(channelHex);
|
||||
} catch (err) {
|
||||
// Revert UI on failure
|
||||
toggleBtn.dataset.state = wasSubscribed ? "subscribed" : "unsubscribed";
|
||||
if (label) {
|
||||
label.textContent = wasSubscribed ? "Unsubscribe" : "Subscribe";
|
||||
}
|
||||
|
||||
userLogger.error("Failed to update subscription:", err);
|
||||
const permissionErrorCodes = new Set([
|
||||
"extension-permission-denied",
|
||||
|
||||
@@ -285,7 +285,7 @@ class HashtagPreferencesService {
|
||||
loadFromCache(pubkey) {
|
||||
// profileCache internally uses activePubkey, but we should verify if it matches
|
||||
try {
|
||||
let cached = profileCache.get("interests");
|
||||
let cached = profileCache.getProfileData(pubkey, "interests");
|
||||
|
||||
// Legacy fallback migration
|
||||
if (!cached && typeof localStorage !== "undefined") {
|
||||
|
||||
+1
-1
@@ -310,7 +310,7 @@ class SubscriptionsManager {
|
||||
const normalizedUserPubkey = normalizeHexPubkey(userPubkey) || userPubkey;
|
||||
|
||||
// 1. Attempt to load from cache first
|
||||
const cached = profileCache.get("subscriptions");
|
||||
const cached = profileCache.getProfileData(normalizedUserPubkey, "subscriptions");
|
||||
if (Array.isArray(cached)) {
|
||||
devLogger.log("[SubscriptionsManager] Loaded subscriptions from cache.");
|
||||
this.subscribedPubkeys = new Set(cached);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
import { subscriptions } from './js/subscriptions.js';
|
||||
import { hashtagPreferences } from './js/services/hashtagPreferencesService.js';
|
||||
import { profileCache } from './js/state/profileCache.js';
|
||||
|
||||
// Mock profileCache.getProfileData
|
||||
const originalGetProfileData = profileCache.getProfileData;
|
||||
const originalGet = profileCache.get;
|
||||
|
||||
let calls = [];
|
||||
|
||||
profileCache.getProfileData = (pubkey, section) => {
|
||||
calls.push({ method: 'getProfileData', pubkey, section });
|
||||
return null;
|
||||
};
|
||||
|
||||
profileCache.get = (section) => {
|
||||
calls.push({ method: 'get', section });
|
||||
return null;
|
||||
};
|
||||
|
||||
async function runVerify() {
|
||||
console.log("Verifying fix...");
|
||||
|
||||
// Test SubscriptionsManager
|
||||
const subPubkey = "0000000000000000000000000000000000000000000000000000000000000001";
|
||||
// We need to mock updateFromRelays to avoid network calls, or just catch error
|
||||
subscriptions.updateFromRelays = async () => {};
|
||||
|
||||
await subscriptions.loadSubscriptions(subPubkey);
|
||||
|
||||
const subCall = calls.find(c => c.section === "subscriptions");
|
||||
if (subCall && subCall.method === "getProfileData" && subCall.pubkey === subPubkey) {
|
||||
console.log("PASS: subscriptions.loadSubscriptions used getProfileData with correct pubkey.");
|
||||
} else {
|
||||
console.error("FAIL: subscriptions.loadSubscriptions did not use getProfileData correctly.", calls);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
calls = []; // Reset
|
||||
|
||||
// Test HashtagPreferencesService
|
||||
const tagPubkey = "0000000000000000000000000000000000000000000000000000000000000002";
|
||||
// Mock load logic to avoid network
|
||||
hashtagPreferences.loadFromCache(tagPubkey);
|
||||
|
||||
const tagCall = calls.find(c => c.section === "interests");
|
||||
if (tagCall && tagCall.method === "getProfileData" && tagCall.pubkey === tagPubkey) {
|
||||
console.log("PASS: hashtagPreferences.loadFromCache used getProfileData with correct pubkey.");
|
||||
} else {
|
||||
console.error("FAIL: hashtagPreferences.loadFromCache did not use getProfileData correctly.", calls);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("All verifications passed.");
|
||||
}
|
||||
|
||||
runVerify().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in new issue
Block a user