Merge pull request #2252 from PR0M3TH3AN/codex/track-parallel-task-results-in-authsessioncoordinator

Track auth list sync outcomes and add consolidated auth-sync retry
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-08 13:55:48 -05:00
commit c785894943
4 files changed
+366 -40

No files matched your search

+89
View File
@@ -3480,6 +3480,95 @@ class Application {
this.updatePermissionPromptCta();
}
async handleAuthSyncRetryRequest() {
const activePubkey = this.normalizeHexPubkey(this.pubkey);
if (!activePubkey) {
return;
}
this.updateAuthLoadingState({ lists: "loading" });
const taskResults = await Promise.allSettled([
Promise.resolve()
.then(() => this.authService?.loadBlocksForPubkey?.(activePubkey, {
allowPermissionPrompt: true,
}))
.then((loaded) => ({ name: "blocks", ok: loaded !== false }))
.catch((error) => ({ name: "blocks", ok: false, error })),
Promise.resolve()
.then(() => subscriptions?.ensureLoaded?.(activePubkey, {
allowPermissionPrompt: true,
}))
.then(() => ({
name: "subscriptions",
ok: !subscriptions?.lastLoadError,
error: subscriptions?.lastLoadError || null,
}))
.catch((error) => ({ name: "subscriptions", ok: false, error })),
Promise.resolve()
.then(() => this.hashtagPreferences?.load?.(activePubkey, {
allowPermissionPrompt: true,
}))
.then(() => {
const error = this.hashtagPreferences?.lastLoadError || null;
if (!error) {
this.updateCachedHashtagPreferences();
}
return {
name: "hashtags",
ok: !error,
error,
};
})
.catch((error) => ({ name: "hashtags", ok: false, error })),
]);
const tasks = taskResults.map((result) =>
result.status === "fulfilled"
? result.value
: {
name: "unknown",
ok: false,
error: result.reason || null,
},
);
const hasFailure = tasks.some((task) => !task.ok);
this.updateAuthLoadingState({
lists: hasFailure ? "error" : "ready",
listsDetail: {
ready: !hasFailure,
degraded: hasFailure,
error: hasFailure,
retryScheduled: false,
retryCompleted: true,
tasks: tasks.map((task) => ({
name: task.name,
ok: task.ok,
fromCache: false,
error: task.error || null,
})),
},
});
this.dispatchAuthChange({
status: "auth-sync-retry",
loggedIn: true,
pubkey: activePubkey,
authLoadingState: this.authLoadingState,
});
if (this.profileController) {
this.profileController.populateBlockedList();
void this.profileController.populateSubscriptionsList();
this.profileController.populateHashtagPreferences();
}
if (!hasFailure) {
await this.onVideosShouldRefresh({ reason: "auth-sync-retry-success" });
}
}
async handleAuthLogin(...args) {
this._initCoordinators();
return this._auth.handleAuthLogin(...args);
+191 -40
View File
@@ -239,6 +239,51 @@ export function createAuthSessionCoordinator(deps) {
// for a retry timer.
const listStatePromise = Promise.all([relaysReadyPromise, permissionPromise]).then(async () => {
const allowPermissionPrompt = !preAuthSucceeded;
const hasValidBlocksCache = () => {
if (!activePubkey || !userBlocks) {
return false;
}
const activeCachePubkey = this.normalizeHexPubkey(userBlocks.activePubkey);
return userBlocks.loaded === true && activeCachePubkey === activePubkey;
};
const hasValidSubscriptionsCache = () => {
if (!activePubkey || !subscriptions) {
return false;
}
const activeCachePubkey = this.normalizeHexPubkey(
subscriptions.currentUserPubkey,
);
return subscriptions.loaded === true && activeCachePubkey === activePubkey;
};
const hasValidHashtagCache = () => {
if (!activePubkey || !this.hashtagPreferences) {
return false;
}
const activeCachePubkey = this.normalizeHexPubkey(
this.hashtagPreferences.activePubkey,
);
return this.hashtagPreferences.loaded === true && activeCachePubkey === activePubkey;
};
const runListTask = async (name, task, { hasValidCache }) => {
try {
const result = await task();
return {
name,
ok: Boolean(result?.ok),
error: result?.error || null,
hasCache: hasValidCache(),
};
} catch (error) {
return {
name,
ok: false,
error,
hasCache: hasValidCache(),
};
}
};
const parallelListTasks = [];
if (
@@ -246,16 +291,22 @@ export function createAuthSessionCoordinator(deps) {
typeof this.authService.loadBlocksForPubkey === "function"
) {
parallelListTasks.push(
this.authService
.loadBlocksForPubkey(activePubkey, {
allowPermissionPrompt,
})
.catch((error) => {
devLogger.warn(
"[Application] Failed to load blocks during login:",
error,
);
}),
runListTask(
"blocks",
async () => {
const loaded = await this.authService.loadBlocksForPubkey(activePubkey, {
allowPermissionPrompt,
});
const ok = loaded !== false;
if (!ok) {
const error = new Error("Block list sync returned a failed status.");
error.code = "blocks-sync-failed";
return { ok: false, error };
}
return { ok: true, error: null };
},
{ hasValidCache: hasValidBlocksCache },
),
);
}
@@ -265,20 +316,18 @@ export function createAuthSessionCoordinator(deps) {
typeof subscriptions.ensureLoaded === "function"
) {
parallelListTasks.push(
subscriptions
.ensureLoaded(activePubkey, { allowPermissionPrompt })
.then(() => {
this.capturePermissionPromptFromError(
subscriptions?.lastLoadError,
);
})
.catch((error) => {
devLogger.warn(
"[Application] Failed to load subscriptions during login:",
error,
);
runListTask(
"subscriptions",
async () => {
await subscriptions.ensureLoaded(activePubkey, {
allowPermissionPrompt,
});
const error = subscriptions?.lastLoadError || null;
this.capturePermissionPromptFromError(error);
}),
return { ok: !error, error };
},
{ hasValidCache: hasValidSubscriptionsCache },
),
);
}
@@ -288,28 +337,130 @@ export function createAuthSessionCoordinator(deps) {
typeof this.hashtagPreferences.load === "function"
) {
parallelListTasks.push(
this.hashtagPreferences
.load(activePubkey, { allowPermissionPrompt })
.then(() => {
this.capturePermissionPromptFromError(
this.hashtagPreferences?.lastLoadError,
);
this.updateCachedHashtagPreferences();
})
.catch((error) => {
devLogger.warn(
"[Application] Failed to load hashtag preferences during login:",
error,
);
runListTask(
"hashtags",
async () => {
await this.hashtagPreferences.load(activePubkey, {
allowPermissionPrompt,
});
const error = this.hashtagPreferences?.lastLoadError || null;
this.capturePermissionPromptFromError(error);
}),
this.updateCachedHashtagPreferences();
return { ok: !error, error };
},
{ hasValidCache: hasValidHashtagCache },
),
);
}
await Promise.allSettled(parallelListTasks);
const taskOutcomes = await Promise.all(parallelListTasks);
const requiredTaskOutcomes = taskOutcomes.filter((outcome) =>
["blocks", "subscriptions", "hashtags"].includes(outcome.name),
);
const failedRequiredOutcomes = requiredTaskOutcomes.filter(
(outcome) => !outcome.ok,
);
const ready = requiredTaskOutcomes.every(
(outcome) => outcome.ok || outcome.hasCache,
);
const degraded = failedRequiredOutcomes.length > 0;
const fatal = failedRequiredOutcomes.some((outcome) => !outcome.hasCache);
this.updateAuthLoadingState({ lists: "ready" });
return true;
const listSyncDetail = {
ready,
degraded,
error: fatal,
retryScheduled: false,
tasks: requiredTaskOutcomes.map((outcome) => ({
name: outcome.name,
ok: outcome.ok,
fromCache: outcome.hasCache && !outcome.ok,
error: outcome.error || null,
})),
};
if (degraded && activePubkey) {
listSyncDetail.retryScheduled = true;
Promise.resolve()
.then(() =>
Promise.all([
this.authService?.loadBlocksForPubkey?.(activePubkey, {
allowPermissionPrompt: true,
}),
subscriptions?.ensureLoaded?.(activePubkey, {
allowPermissionPrompt: true,
}),
this.hashtagPreferences?.load?.(activePubkey, {
allowPermissionPrompt: true,
}),
]),
)
.then(() => {
this.updateCachedHashtagPreferences();
const retryTasks = [
{
name: "blocks",
ok: hasValidBlocksCache(),
fromCache: false,
error: null,
},
{
name: "subscriptions",
ok:
hasValidSubscriptionsCache() &&
!subscriptions?.lastLoadError,
fromCache: false,
error: subscriptions?.lastLoadError || null,
},
{
name: "hashtags",
ok:
hasValidHashtagCache() &&
!this.hashtagPreferences?.lastLoadError,
fromCache: false,
error: this.hashtagPreferences?.lastLoadError || null,
},
];
const retryReady = retryTasks.every((task) => task.ok);
const retryDegraded = retryTasks.some((task) => !task.ok);
this.updateAuthLoadingState({
lists: retryReady ? "ready" : "degraded",
listsDetail: {
ready: retryReady,
degraded: retryDegraded,
error: retryDegraded,
retryScheduled: false,
retryCompleted: true,
tasks: retryTasks,
},
});
this.dispatchAuthChange({
status: "login-lists-sync",
loggedIn: true,
pubkey: activePubkey,
authLoadingState: this.authLoadingState,
});
})
.catch((error) => {
devLogger.warn(
"[Application] Consolidated list sync retry failed after login:",
error,
);
});
}
const listsState = ready ? (degraded ? "degraded" : "ready") : "error";
this.updateAuthLoadingState({
lists: listsState,
listsDetail: listSyncDetail,
});
this.dispatchAuthChange({
status: "login-lists-sync",
loggedIn: true,
pubkey: activePubkey,
authLoadingState: this.authLoadingState,
});
return listSyncDetail;
});
// DMs can wait for profile since they need encryption context.
+1
View File
@@ -605,6 +605,7 @@ export default class ApplicationBootstrap {
onPublishDmRelayPreferences: (payload) =>
app.handleProfilePublishDmRelayPreferences(payload),
onRequestPermissionPrompt: () => app.handlePermissionPromptRequest(),
onRetryAuthSync: () => app.handleAuthSyncRetryRequest(),
};
app.profileController = new ProfileModalController({
+85
View File
@@ -1199,6 +1199,7 @@ export class ProfileModalController {
onOpenRelays: callbacks.onOpenRelays || noop,
onPublishDmRelayPreferences: callbacks.onPublishDmRelayPreferences || noop,
onRequestPermissionPrompt: callbacks.onRequestPermissionPrompt || noop,
onRetryAuthSync: callbacks.onRetryAuthSync || noop,
};
this.profileModal = null;
@@ -1337,7 +1338,9 @@ export class ProfileModalController {
message: "",
buttonLabel: "Enable permissions",
busy: false,
action: "permission",
};
this.authLoadingStateListener = null;
this.profileSubscriptionsRefreshBtn = null;
this.profileFriendsRefreshBtn = null;
this.profileBlockedRefreshBtn = null;
@@ -1501,6 +1504,15 @@ export class ProfileModalController {
this.setupLayoutBreakpointObserver();
this.applyModalStackingOverrides();
this.registerEventListeners();
if (!this.authLoadingStateListener && typeof window !== "undefined") {
this.authLoadingStateListener = (event) => {
this.handleAuthLoadingStateChange(event?.detail || {});
};
window.addEventListener(
"bitvid:auth-loading-state",
this.authLoadingStateListener,
);
}
this.populateHashtagPreferences();
this.refreshModerationSettingsUi();
const preserveMenu = this.isMobileLayoutActive();
@@ -5445,6 +5457,10 @@ export class ProfileModalController {
if (this.permissionPromptCtaButton instanceof HTMLElement) {
this.permissionPromptCtaButton.addEventListener("click", () => {
if (this.permissionPromptCtaButton?.dataset?.action === "retry-auth-sync") {
this.callbacks.onRetryAuthSync({ controller: this });
return;
}
this.callbacks.onRequestPermissionPrompt({ controller: this });
});
}
@@ -8093,6 +8109,7 @@ export class ProfileModalController {
message,
buttonLabel,
busy,
action,
} = this.permissionPromptCtaState || {};
const normalizedMessage =
typeof message === "string" && message.trim()
@@ -8117,6 +8134,74 @@ export class ProfileModalController {
"aria-busy",
busy ? "true" : "false",
);
this.permissionPromptCtaButton.dataset.action =
action === "retry-auth-sync" ? "retry-auth-sync" : "permission";
}
}
handleAuthLoadingStateChange(detail = {}) {
const listsState =
typeof detail?.lists === "string" ? detail.lists.trim().toLowerCase() : "";
const listsDetail = detail?.listsDetail && typeof detail.listsDetail === "object"
? detail.listsDetail
: null;
if (!listsDetail || !listsState || listsState === "loading" || listsState === "idle") {
return;
}
const failedTasks = Array.isArray(listsDetail.tasks)
? listsDetail.tasks.filter((task) => task && task.ok === false)
: [];
const loadedFromCacheTasks = failedTasks.filter((task) => task.fromCache === true);
const hardFailTasks = failedTasks.filter((task) => task.fromCache !== true);
if (loadedFromCacheTasks.length > 0 && hardFailTasks.length === 0) {
this.setSubscriptionsStatus(
"Subscriptions loaded from cache; sync is retrying in background.",
"warning",
);
this.setHashtagStatus(
"Hashtag preferences loaded from cache; sync is retrying in background.",
"warning",
);
this.setBlockListLoadingState("error", {
message: "Blocked creators loaded from cache; syncing latest in background.",
});
} else if (hardFailTasks.length > 0) {
this.setSubscriptionsStatus(
"Some profile lists failed to sync. Use Retry sync.",
"warning",
);
this.setHashtagStatus(
"Some profile lists failed to sync. Use Retry sync.",
"warning",
);
this.setBlockListLoadingState("error", {
message: "Blocked creators failed to sync. Retry to fetch latest lists.",
});
}
if (failedTasks.length > 0) {
this.setPermissionPromptCtaState({
visible: true,
message:
loadedFromCacheTasks.length > 0
? "Some lists are currently from cache. Retry now to sync with relays."
: "Failed to sync required profile lists. Retry now.",
buttonLabel: "Retry list sync",
action: "retry-auth-sync",
busy: false,
});
return;
}
if (listsState === "ready") {
this.setPermissionPromptCtaState({
visible: false,
message: "",
action: "permission",
busy: false,
});
}
}