Say so when the server refuses a subscribe
Adding a shared address book did nothing in one browser and worked in another. The button was not broken; the refusal was invisible. Subscribing is the one call in the app that writes to somebody else's account, so it is the one a perfectly healthy server is entitled to say no to -- and JMAP says no to a `/set` by answering successfully with the object listed in `notUpdated`. Neither subscribe method looked. The promise resolved, the code carried on, the re-read came back unchanged, and the row stayed exactly where it was with nothing said. Every other `/set` in this codebase reads `notUpdated` and raises. These two were written without it, which is the whole defect: not a wrong answer, an unread one. Both now check it and say what the server said, which is the thing that was missing -- whatever the underlying refusal turns out to be, it can be read off the screen instead of guessed at from which browser was in front of you.
This commit is contained in:
@@ -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) => ({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user