UNPKG

onesecmail

Version:

Create and receive email in only 1 second.

241 lines (240 loc) 7.87 kB
import { got } from "got"; import { z } from "zod"; import { TypedEmitter } from "tiny-typed-emitter"; import OneSecMailAPI, { BASE_API_URL, FORBIDDEN_LOGIN } from "./OneSecMailAPI.js"; import { shortMessageSchema } from "./schemas.js"; export default async function OneSecMail(a, b) { let emailAddress = null; let options = {}; if (typeof a === "string" && typeof b === "undefined") { emailAddress = a; } else if (typeof a === "object" && typeof b === "undefined") { options = a; } else if (typeof a === "string" && typeof b === "object") { emailAddress = a; options = b; } const api = new OneSecMailAPI(options); if (!emailAddress) { [emailAddress] = await api.genRandomMailbox(); return new OneSecMailbox(emailAddress, api); } const schema = z.string().email(); if (!schema.safeParse(emailAddress).success) { throw new Error("Email address must be a valid email address"); } const [local, domain] = emailAddress.toLowerCase().split("@"); if (FORBIDDEN_LOGIN.includes(local)) { throw new Error(`For security reason you cannot read messages from addresses: ${FORBIDDEN_LOGIN.map((local) => `${local}@${domain}`).join(", ")}`); } const domainList = await api.getDomainList(); if (!domainList.includes(domain)) { throw new Error(`Email address must contain a domain contained in the following list: ${domainList .map((domain) => `${local}@${domain}`) .join(", ")}`); } return new OneSecMailbox(`${local}@${domain}`, api); } class OneSecMailbox extends TypedEmitter { #api; #local; #domain; #controller; #state = "STOPPED"; #lastMessageid = 0; #intervalTimer; emailAddress; constructor(emailAddress, api) { super(); const [local, domain] = emailAddress.split("@"); this.#api = api; this.#local = local; this.#domain = domain; this.#controller = new AbortController(); this.emailAddress = emailAddress; } async getMessages(options) { const messages = await this.#api.getMessages(this.#local, this.#domain, options); return messages.map((message) => { return new OneSecMailShortMessage(this.emailAddress, message, this.#api); }); } async clearMessages(options) { try { await got.post("https://www.1secmail.com/mailbox", { form: { action: "deleteMailbox", login: this.#local, domain: this.#domain, }, retry: { limit: options?.retry ?? this.#api.retry, }, timeout: { request: options?.timeout ?? this.#api.timeout, }, }); } catch (e) { throw new Error("HTTP request failed"); } } async #polling(intervalTime) { const start = new Date().getTime(); let rerun = false; try { const { body } = await got.get(BASE_API_URL, { searchParams: { action: "getMessages", login: this.#local, domain: this.#domain }, retry: { limit: 0 }, timeout: { request: 5000 }, signal: this.#controller.signal, }); const schema = z.array(shortMessageSchema); let messages; try { messages = schema.parse(JSON.parse(body)); } catch (e) { throw new Error("Malformed response"); } messages.sort((a, b) => a.id - b.id); messages = messages.filter((message) => message.id > this.#lastMessageid); this.#lastMessageid = messages.at(-1)?.id ?? this.#lastMessageid; for (const message of messages) { this.emit("newMessage", new OneSecMailShortMessage(this.emailAddress, message, this.#api)); } rerun = true; } catch (error) { if (error instanceof Error && error.name !== "AbortError") { this.emit("error", error); rerun = true; } } if (rerun) { const end = new Date().getTime(); const diff = end - start; this.#intervalTimer = setTimeout(this.#polling.bind(this), intervalTime - diff, intervalTime); } } startPolling(intervalTime = 5000) { if (intervalTime < 1000) throw new RangeError("`intervalTime` must be at least 1000"); if (this.#state === "STARTED") return false; this.#polling(intervalTime); this.#state = "STARTED"; return true; } stopPolling() { if (this.#state === "STOPPED") return false; this.#controller.abort(); clearTimeout(this.#intervalTimer); this.#state = "STOPPED"; return true; } } class OneSecMailShortMessage { #api; #local; #domain; #emailAddress; id; from; subject; date; constructor(emailAddress, message, api) { const [local, domain] = emailAddress.split("@"); this.#api = api; this.#local = local; this.#domain = domain; this.#emailAddress = emailAddress; this.id = message.id; this.from = message.from; this.subject = message.subject; this.date = message.date; } async fetchFullMessage(options) { const message = await this.#api.readMessage(this.#local, this.#domain, this.id, options); if (!message) throw new Error("Message no longer exists"); return new OneSecMailMessage(this.#emailAddress, message, this.#api); } serialize() { return { id: this.id, from: this.from, subject: this.subject, date: this.date, }; } } class OneSecMailMessage { id; from; subject; date; attachments; body; textBody; htmlBody; constructor(emailAddress, message, api) { this.id = message.id; this.from = message.from; this.subject = message.subject; this.date = message.date; this.attachments = message.attachments.map((attachment) => { return new OneSecMailAttachment(emailAddress, message.id, attachment, api); }); this.body = message.body; this.textBody = message.textBody; this.htmlBody = message.htmlBody; } serialize() { return { id: this.id, from: this.from, subject: this.subject, date: this.date, attachments: this.attachments.map((attachment) => attachment.serialize()), body: this.body, textBody: this.textBody, htmlBody: this.htmlBody, }; } } class OneSecMailAttachment { #api; #local; #domain; #messageId; filename; contentType; size; constructor(emailAddress, messageId, attachment, api) { const [local, domain] = emailAddress.split("@"); this.#api = api; this.#local = local; this.#domain = domain; this.#messageId = messageId; this.filename = attachment.filename; this.contentType = attachment.contentType; this.size = attachment.size; } async download(options) { const file = await this.#api.download(this.#local, this.#domain, this.#messageId, this.filename, options); if (!file) throw new Error("File no longer exists"); return file; } serialize() { return { filename: this.filename, contentType: this.contentType, size: this.size, }; } }