mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
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:
4 files changed
+366
-40
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -605,6 +605,7 @@ export default class ApplicationBootstrap {
|
||||
onPublishDmRelayPreferences: (payload) =>
|
||||
app.handleProfilePublishDmRelayPreferences(payload),
|
||||
onRequestPermissionPrompt: () => app.handlePermissionPromptRequest(),
|
||||
onRetryAuthSync: () => app.handleAuthSyncRetryRequest(),
|
||||
};
|
||||
|
||||
app.profileController = new ProfileModalController({
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user