import { authedGet, authedPost, type DeliberiContext } from "#lib/api/deliberi.ts";
import { buildUrl } from "#lib/api/_request.ts";
import { ClientResponseError } from "#lib/api/client.ts";
import { nsidOf } from "#lib/api/syntax.ts";
import type { Fetch } from "#lib/fetch.ts";
export interface ListEmailItem {
address: string;
verified: boolean;
primary: boolean;
createdAt: string;
}
const LIST_EMAILS = nsidOf("org.tangled.temp.account.listEmails");
const ADD_EMAIL = nsidOf("org.tangled.temp.account.addEmail");
const VERIFY_EMAIL = nsidOf("org.tangled.temp.account.verifyEmail");
const DELETE_EMAIL = nsidOf("org.tangled.temp.account.deleteEmail");
const SET_PRIMARY_EMAIL = nsidOf("org.tangled.temp.account.setPrimaryEmail");
export const listEmails = (ctx: DeliberiContext): Promise<{ emails: ListEmailItem[] }> =>
authedGet(ctx, LIST_EMAILS);
export const addEmail = (ctx: DeliberiContext, email: string): Promise<void> =>
authedPost(ctx, ADD_EMAIL, { email }).then(() => undefined);
export class VerificationError extends Error {
constructor(
message: string,
readonly kind: "invalid" | "unavailable"
) {
super(message);
}
}
export const verifyEmailByToken = async (
serviceUrl: string,
token: string,
fetchFn: Fetch = fetch
): Promise<{ email: string }> => {
const response = await fetchFn(buildUrl(serviceUrl, VERIFY_EMAIL), {
method: "POST",
headers: { "content-type": "application/json", accept: "application/json" },
body: JSON.stringify({ token })
});
if (response.status === 400) {
throw new VerificationError(
"The verification link is invalid or has already been used.",
"invalid"
);
}
if (!response.ok) {
throw new VerificationError(
`Could not reach the verification service (${response.status}).`,
"unavailable"
);
}
const text = await response.text();
return text ? JSON.parse(text) : { email: "" };
};
export const emailServiceError = (cause: unknown): string => {
if (cause instanceof ClientResponseError) {
if (cause.status === 401 || cause.status === 403) {
return "Unable to confirm your session with the email service. Sign out, sign back in, and try again.";
}
if (cause.status >= 500) return "Unable to reach the email service. Try again in a moment.";
if (cause.error === "InvalidEmail") {
return "Check the email address. It needs a domain that can receive mail.";
}
const description = cause.description?.trim();
if (!description) return "Unable to update your emails. Try again.";
const sentence = description.charAt(0).toUpperCase() + description.slice(1);
return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;
}
if (cause instanceof TypeError) {
return "Unable to reach the email service. Check your connection and try again.";
}
return cause instanceof Error ? cause.message : String(cause);
};
export const deleteEmail = (ctx: DeliberiContext, email: string): Promise<void> =>
authedPost(ctx, DELETE_EMAIL, { email }).then(() => undefined);
export const setPrimaryEmail = (ctx: DeliberiContext, email: string): Promise<void> =>
authedPost(ctx, SET_PRIMARY_EMAIL, { email }).then(() => undefined);
export const markPrimary = (rows: ListEmailItem[], address: string): ListEmailItem[] =>
rows.map((row) => ({ ...row, primary: row.address === address }));
export const removeEmailRow = (rows: ListEmailItem[], address: string): ListEmailItem[] =>
rows.filter((row) => row.address !== address);