Merge pull request #102 from LINUXexpert-org/say-why-subscribe-failed

Say so when the server refuses a subscribe
This commit is contained in:
LINUXexpert.org
2026-08-27 11:34:45 -07:00
committed by GitHub
2 changed files with 21 additions and 4 deletions
+8 -2
View File
@@ -3,6 +3,7 @@ import { CAP, client, setErrorMessage } from "@/jmap/client";
import type { BusyPeriod, Calendar, CalendarEvent, GetResponse, Id, JSCalendarParticipant, JSCalendarRecurrenceRule, ParticipantIdentity, QueryResponse, SetResponse } from "@/jmap/types";
import { toUTCDate, toLocalDateTime, zonedToDate, parseDuration, DAY_MS, browserTimeZone } from "@/lib/dates";
import { settings } from "./settings";
import { toast } from "@/ui/toast";
import { useSession } from "./session";
export interface EventInstance {
@@ -146,10 +147,15 @@ export const useCalendar = create<CalendarState>((set, get) => ({
},
async setSharedSubscribed(accountId, calendarId, subscribed) {
// See the note in the contacts store: subscribing writes to another
// account, so a refusal is an ordinary answer and arrives in `notUpdated`
// rather than as a thrown error.
try {
await client.call("Calendar/set", { accountId, update: { [calendarId]: { isSubscribed: subscribed } } });
const res = await client.call<SetResponse>("Calendar/set", { accountId, update: { [calendarId]: { isSubscribed: subscribed } } });
const err = res.notUpdated?.[calendarId];
if (err) throw new Error(setErrorMessage(err));
} catch (err) {
set({ error: (err as Error).message });
toast.error(`Could not ${subscribed ? "add" : "remove"} that calendar: ${(err as Error).message}`);
return;
}
set((s) => ({
+13 -2
View File
@@ -2,6 +2,7 @@ import { create } from "zustand";
import { CAP, client, setErrorMessage } from "@/jmap/client";
import type { AddressBook, ContactCard, EmailAddress, GetResponse, Id, Principal, QueryResponse, SetResponse } from "@/jmap/types";
import { contactDisplayName, contactEmails, sortKey } from "@/lib/contacts";
import { toast } from "@/ui/toast";
import { useSession } from "./session";
import { useMail } from "./mail";
@@ -166,10 +167,20 @@ export const useContacts = create<ContactsState>((set, get) => ({
},
async setBookSubscribed(accountId, bookId, subscribed) {
/*
* `notUpdated` matters more here than anywhere else this pattern is used.
* Subscribing is a write to somebody *else's* account, so it is the one
* call in the app that a perfectly healthy server is entitled to refuse --
* and a refusal arrives as a successful response carrying a per-object
* failure, not as a thrown error. Ignoring it made a refused subscribe look
* exactly like a button that does nothing.
*/
try {
await client.call("AddressBook/set", { accountId, update: { [bookId]: { isSubscribed: subscribed } } });
const res = await client.call<SetResponse>("AddressBook/set", { accountId, update: { [bookId]: { isSubscribed: subscribed } } });
const err = res.notUpdated?.[bookId];
if (err) throw new Error(setErrorMessage(err));
} catch (err) {
set({ error: (err as Error).message });
toast.error(`Could not ${subscribed ? "add" : "remove"} that address book: ${(err as Error).message}`);
return;
}
if (!subscribed && get().selection.accountId === accountId && get().selection.bookId === bookId) {