feat(kanban): reauthorize queued replay
req: sync/019 req: security/004 req: auth/005 req: operations/002
This commit is contained in:
@@ -23,11 +23,12 @@ let maxObservedInFlight = 0;
|
||||
let stopped = false;
|
||||
|
||||
class UploadError extends Error {
|
||||
constructor(status, retryable, reason) {
|
||||
constructor(status, retryable, kind, reason) {
|
||||
super(`sync upload failed with ${status}`);
|
||||
this.name = "UploadError";
|
||||
this.status = status;
|
||||
this.retryable = retryable;
|
||||
this.kind = kind;
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
@@ -190,8 +191,9 @@ async function upload(command) {
|
||||
}
|
||||
if (!response.ok) {
|
||||
const problem = await response.json().catch(() => ({}));
|
||||
const kind = typeof problem.kind === "string" ? problem.kind : "unclassified-rejection";
|
||||
const reason = typeof problem.error === "string" ? problem.error : "unclassified rejection";
|
||||
throw new UploadError(response.status, response.status >= 500, reason);
|
||||
throw new UploadError(response.status, response.status >= 500, kind, reason);
|
||||
}
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
@@ -282,6 +284,7 @@ async function synchronize(command) {
|
||||
root.setAttribute("data-sync-uploaded-total", String(uploadedTotal));
|
||||
root.setAttribute("data-sync-pending-count", String((await pendingCommands(database)).length));
|
||||
root.setAttribute("data-sync-ack-sequence", String(canonical.serverSequence));
|
||||
root.setAttribute("data-sync-ack-command-id", canonical.commandId);
|
||||
root.setAttribute("data-sync-canonical-column", canonical.canonicalColumn);
|
||||
setPhase("acknowledged", `Command ${command.id} acknowledged in ${canonical.canonicalColumn}.`);
|
||||
root.dispatchEvent(new CustomEvent("kanban:sync-acknowledged", { detail: canonical }));
|
||||
@@ -324,14 +327,23 @@ async function synchronize(command) {
|
||||
setOnline(true);
|
||||
clearTimeout(leaseTimer);
|
||||
root.setAttribute("data-sync-error", error.message);
|
||||
root.setAttribute("data-sync-error-kind", "permanent-rejection");
|
||||
root.setAttribute("data-sync-error-status", String(error.status));
|
||||
root.setAttribute("data-sync-error-reason", error.reason);
|
||||
root.setAttribute("data-sync-rejected-command-id", command.id);
|
||||
const remaining = await pendingCommands(database);
|
||||
root.setAttribute("data-sync-pending-count", String(remaining.length));
|
||||
setManualRetryAvailable(false);
|
||||
setPhase("rejected", `Command ${command.id} was permanently rejected (${error.status}: ${error.reason}); ${remaining.length} durable command${remaining.length === 1 ? " remains" : "s remain"} queued for review.`);
|
||||
if (error.kind === "authorization-denial") {
|
||||
root.setAttribute("data-sync-error-kind", "authorization-denial");
|
||||
root.setAttribute("data-sync-pending-count", "redacted");
|
||||
root.setAttribute("data-sync-redacted-pending", "true");
|
||||
root.removeAttribute("data-sync-error-reason");
|
||||
root.removeAttribute("data-sync-rejected-command-id");
|
||||
setPhase("authorization-denied", "Current session cannot access local queued work. Sign back into the owning account to continue.");
|
||||
} else {
|
||||
root.setAttribute("data-sync-error-kind", "permanent-rejection");
|
||||
root.setAttribute("data-sync-error-reason", error.reason);
|
||||
root.setAttribute("data-sync-rejected-command-id", command.id);
|
||||
root.setAttribute("data-sync-pending-count", String(remaining.length));
|
||||
setPhase("rejected", `Command ${command.id} was permanently rejected (${error.status}: ${error.reason}); ${remaining.length} durable command${remaining.length === 1 ? " remains" : "s remain"} queued for review.`);
|
||||
}
|
||||
await releaseUploaderLease(database);
|
||||
root.setAttribute("data-sync-leader", "false");
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user