Stop sending nodeType to servers that have no such property
Uploading a file or creating a folder failed on the live 0.15.5 server with `invalidProperties (nodeType)`. The property arrived in Stalwart 0.16; before that a FileNode has no nodeType at all, and the create is refused outright. Older servers tell a file from a directory a different way: the node carries file properties or it does not. Setting blobId, size or type — even to null — makes it a file, so a directory there is exactly parentId plus name. 0.16 is also the first release to advertise urn:stalwart:jmap and no earlier one knows that capability, so its presence stands in for "has the newer FileNode shape". Creates, and the property lists we ask for, are shaped from that. The read side needed it too: a server that never reports nodeType would have had every folder drawn with a file icon, sorted among the files and opening as a download. Nodes are normalised as they arrive, so everything downstream can still just read nodeType.
This commit is contained in:
@@ -6,30 +6,34 @@
|
||||
*/
|
||||
import { CAP, client, setErrorMessage } from "@/jmap/client";
|
||||
import type { FileNode, GetResponse, QueryResponse, SetResponse } from "@/jmap/types";
|
||||
import { directoryCreate, fileCreate, supportsNodeType, withNodeType } from "@/lib/filenode";
|
||||
import { useSession } from "@/store/session";
|
||||
import { toast } from "@/ui/toast";
|
||||
|
||||
const FOLDER = "ihasmail";
|
||||
|
||||
/** Just enough to find the folder, asking for nodeType only where it exists. */
|
||||
const folderProps = () => (supportsNodeType() ? ["id", "name", "nodeType", "parentId"] : ["id", "name", "parentId", "blobId", "size", "type"]);
|
||||
|
||||
async function ensureFolder(accountId: string): Promise<string> {
|
||||
let list: FileNode[] = [];
|
||||
try {
|
||||
const res = await client.chain([
|
||||
["FileNode/query", { accountId, filter: { isTopLevel: true, nodeType: "directory", name: FOLDER }, limit: 5 }, "q"],
|
||||
["FileNode/get", { accountId, "#ids": { resultOf: "q", name: "FileNode/query", path: "/ids" }, properties: ["id", "name", "nodeType", "parentId"] }, "g"],
|
||||
["FileNode/get", { accountId, "#ids": { resultOf: "q", name: "FileNode/query", path: "/ids" }, properties: folderProps() }, "g"],
|
||||
]);
|
||||
list = (res.get("g")?.[0] as unknown as GetResponse<FileNode>).list;
|
||||
list = withNodeType((res.get("g")?.[0] as unknown as GetResponse<FileNode>).list);
|
||||
} catch {
|
||||
// Older servers: no filter support — scan everything.
|
||||
const res = await client.chain([
|
||||
["FileNode/query", { accountId, limit: 1000 }, "q"],
|
||||
["FileNode/get", { accountId, "#ids": { resultOf: "q", name: "FileNode/query", path: "/ids" }, properties: ["id", "name", "nodeType", "parentId"] }, "g"],
|
||||
["FileNode/get", { accountId, "#ids": { resultOf: "q", name: "FileNode/query", path: "/ids" }, properties: folderProps() }, "g"],
|
||||
]);
|
||||
list = (res.get("g")?.[0] as unknown as GetResponse<FileNode>).list;
|
||||
list = withNodeType((res.get("g")?.[0] as unknown as GetResponse<FileNode>).list);
|
||||
}
|
||||
const existing = list.find((n) => n.name === FOLDER && n.nodeType === "directory" && !n.parentId);
|
||||
if (existing) return existing.id;
|
||||
const set = await client.call<SetResponse<FileNode>>("FileNode/set", { accountId, create: { d: { parentId: null, name: FOLDER, nodeType: "directory" } } });
|
||||
const set = await client.call<SetResponse<FileNode>>("FileNode/set", { accountId, create: { d: directoryCreate(null, FOLDER) } });
|
||||
const err = set.notCreated?.d;
|
||||
if (err) throw new Error(setErrorMessage(err));
|
||||
return set.created!.d!.id;
|
||||
@@ -51,7 +55,7 @@ export async function uploadSignatureImage(file: File): Promise<string> {
|
||||
const up = await client.upload(accountId, file, { type });
|
||||
const folderId = await ensureFolder(accountId);
|
||||
const name = `${Date.now()}-${file.name.replace(/[^\w.-]+/g, "_")}`;
|
||||
const res = await client.call<SetResponse<FileNode>>("FileNode/set", { accountId, create: { f: { parentId: folderId, name, nodeType: "file", blobId: up.blobId, type } } });
|
||||
const res = await client.call<SetResponse<FileNode>>("FileNode/set", { accountId, create: { f: fileCreate(folderId, name, up.blobId, type) } });
|
||||
const err = res.notCreated?.f;
|
||||
if (err) throw new Error(setErrorMessage(err));
|
||||
const created = res.created?.f as Partial<FileNode> | undefined;
|
||||
@@ -71,7 +75,7 @@ export async function storeSignatureHtml(html: string): Promise<string> {
|
||||
const up = await client.upload(accountId, new Blob([html], { type: "text/html" }), { type: "text/html" });
|
||||
const folderId = await ensureFolder(accountId);
|
||||
const name = `signature-${Date.now()}.html`;
|
||||
const res = await client.call<SetResponse<FileNode>>("FileNode/set", { accountId, create: { f: { parentId: folderId, name, nodeType: "file", blobId: up.blobId, type: "text/html" } } });
|
||||
const res = await client.call<SetResponse<FileNode>>("FileNode/set", { accountId, create: { f: fileCreate(folderId, name, up.blobId, "text/html") } });
|
||||
const err = res.notCreated?.f;
|
||||
if (err) throw new Error(setErrorMessage(err));
|
||||
const created = res.created?.f as Partial<FileNode> | undefined;
|
||||
|
||||
Reference in New Issue
Block a user