Edits history of script submission #2403 for ' API client (jmap)'

  • deno
    // Import the assert function from the "assert" module
    import { assert } from "https://deno.land/[email protected]/testing/asserts.ts";
    
    interface EmailAddress {
      email: string;
    }
    
    interface EmailDetails {
      from: string;
      to: string;
      cc: string;
      subject: string;
      messageBody: string;
      mailboxId: string;
    }
    
    /**
     * TinyJMAPClient class provides methods to interact with a JMAP server.
     */
    export class TinyJMAPClient {
      private hostname: string;
      private username: string;
      private token: string;
      private session: any;
      private api_url: string | null = null;
      private account_id: string | null = null;
      private identity_id: string | null = null;
    
      /**
       * Constructor initializes the client with basic settings.
       *
       * @param hostname - JMAP server hostname.
       * @param username - Username for authentication.
       * @param token - Authentication token.
       */
      constructor(hostname: string, username: string, token: string) {
        assert(hostname.length > 0);
        assert(username.length > 0);
        assert(token.length > 0);
    
        this.hostname = hostname;
        this.username = username;
        this.token = token;
        this.session = null;
      }
    
      /**
       * Retrieve the JMAP session.
       *
       * @returns A promise that resolves with the session object.
       */
      async get_session() {
        if (this.session) {
          return this.session;
        }
    
        const r = await fetch(`https://${this.hostname}/.well-known/jmap`, {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${this.token}`,
          },
        });
    
        if (!r.ok) {
          throw new Error(`Failed to fetch JMAP session: ${r.status} ${r.statusText}`);
        }
    
        this.session = await r.json();
        this.api_url = this.session.apiUrl;
        return this.session;
      }
    
      /**
       * Fetch the account ID for JMAP operations.
       *
       * @returns A promise that resolves with the account ID.
       */
      async get_account_id() {
        if (this.account_id) {
          return this.account_id;
        }
    
        const session = await this.get_session();
        const account_id = session.primaryAccounts["urn:ietf:params:jmap:mail"];
        this.account_id = account_id;
        return account_id;
      }
    
      /**
       * Retrieve the identity ID.
       *
       * @returns A promise that resolves with the identity ID.
       */
      async get_identity_id() {
        if (this.identity_id) {
          return this.identity_id;
        }
    
        const identity_res = await this.make_jmap_call({
          using: [
            "urn:ietf:params:jmap:core",
            "urn:ietf:params:jmap:submission",
          ],
          methodCalls: [
            ["Identity/get", { accountId: await this.get_account_id() }, "i"],
          ],
        });
    
        const identity_id = identity_res.methodResponses[0][1]["list"].find(
          (i: any) => i.email === this.username
        ).id;
    
        this.identity_id = identity_id;
        return this.identity_id;
      }
      
      /**
       * Perform a JMAP API call.
       *
       * @param call - The API call details.
       * @returns A promise that resolves with the API call result.
       */
      async make_jmap_call(call: any) {
        const res = await fetch(this.api_url as string, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${this.token}`,
          },
          body: call,
        });
    
        if (!res.ok) {
          throw new Error(`Failed to make JMAP API call: ${res.status} ${res.statusText}`);
        }
    
        return await res.json();
      }
    
      
      /**
       * Create a new email draft and optionally send it.
       *
       * @param emailDetails - Object containing details of the email.
       * @param shouldSend - Flag indicating whether to send the email immediately. Default is `false`.
       * @returns A promise that resolves once the operation is complete.
       *
       * @example
       * ```typescript
       * const emailDetails: EmailDetails = {
       *   from: "sender@example.com",
       *   to: "recipient@example.com",
       *   subject: "Test Email",
       *   messageBody: "This is a test email body.",
       *   mailboxId: "your_mailbox_id_here",
       * };
       *
       * await client.create_draft_email(emailDetails, true);  // Sends the email
       * await client.create_draft_email(emailDetails, false); // Only saves as draft
       * ```
       */
      async create_draft_email(emailDetails: EmailDetails, shouldSend: boolean = false): Promise<void> {
        const { from, to, subject, messageBody, mailboxId } = emailDetails;
        const accountId = await this.get_account_id();
        const identityId = await this.get_identity_id();
    
        const draftObject = {
          from: [{ email: from}],
          to: [{ email: to}],
          subject,
          keywords: shouldSend ? {} : { $draft: true },
          mailboxIds: { [mailboxId]: true },
          bodyValues: { body: { value: messageBody, charset: "utf-8"}},
          textBody: [{ partId: "body", type: "text/plain"}]
        };
    
    
        const res = await this.make_jmap_call(JSON.stringify({
          using: [
            "urn:ietf:params:jmap:core",
            "urn:ietf:params:jmap:mail",
            "urn:ietf:params:jmap:submission",
          ],
          methodCalls: [
            ["Email/set", { accountId, create: { draft: draftObject } }, "a"],
            [
              "EmailSubmission/set",
              {
                accountId,
                onSuccessDestroyEmail: ["#sendIt"],
                create: { sendIt: { emailId: "#draft", identityId } },
              },
              "b",
            ],
          ],
        }));
    
        if (!res.ok) {
          throw new Error(`Failed to send the created Draft using JMAP': ${res.status} ${res.statusText}`)
        }
    
        return await res.json();
      }
    }
    

    Submitted by kryptonian283 958 days ago