1 | |
2 |
|
3 | import { getResumeUrls } from "windmill-client@1"; |
4 |
|
5 | type Gmail = { |
6 | token: string; |
7 | }; |
8 | export async function main( |
9 | gmail_auth: Gmail, |
10 | approver_emails: string[], |
11 | subject = "Resume Windmill flow", |
12 | ) { |
13 | throwOnInvalidApprovers(approver_emails); |
14 |
|
15 | const token = gmail_auth["token"]; |
16 | const email_promises = approver_emails.map((to_email) => |
17 | sendEmail(token, to_email, subject), |
18 | ); |
19 | const results = await Promise.all(email_promises); |
20 |
|
21 | return results; |
22 | } |
23 |
|
24 | async function sendEmail( |
25 | gmail_token: string, |
26 | to_email: string, |
27 | subject: string, |
28 | ): Promise<object> { |
29 | const email = await getEmailBody(to_email, subject); |
30 | const SEND_EMAIL_URL = `https://gmail.googleapis.com/gmail/v1/users/me/messages/send`; |
31 | const body = { |
32 | raw: email, |
33 | }; |
34 | const response = await fetch(SEND_EMAIL_URL, { |
35 | method: "POST", |
36 | body: JSON.stringify(body), |
37 | headers: { |
38 | Authorization: `Bearer ${gmail_token}`, |
39 | }, |
40 | }); |
41 | const response_json = await response.json(); |
42 |
|
43 | return handleSendEmailResult(response_json, to_email); |
44 | } |
45 |
|
46 | async function getEmailBody(to_email: string, subject: string) { |
47 | const { approvalPage } = await getResumeUrls(to_email); |
48 | const message = `There is a Windmill flow at ${Bun.env[ |
49 | "WM_FLOW_PATH" |
50 | ]} run by ${Bun.env["WM_USERNAME"]} waiting for your approval to resume. |
51 |
|
52 | In order to resume or cancel the flow go to ${approvalPage}`; |
53 | const email_body = `From: <me>\nTo: <${to_email}>\nSubject: ${subject}\n\r ${message}`; |
54 |
|
55 | return base64UrlEncode(email_body); |
56 | } |
57 |
|
58 | function throwOnInvalidApprovers(approvers: string[]) { |
59 | if (!Array.isArray(approvers) || approvers.length === 0) { |
60 | throw new Error("Expected at least one approver email"); |
61 | } |
62 | } |
63 |
|
64 | async function handleSendEmailResult(result: object, to_email: string) { |
65 | if (Object.keys(result).includes("error")) { |
66 | return Promise.reject({ wm_to_email: to_email, ...result }); |
67 | } |
68 |
|
69 | return result; |
70 | } |
71 |
|
72 | function base64UrlEncode(data: string | Uint8Array) { |
73 | const bytes = |
74 | typeof data === "string" ? new TextEncoder().encode(data) : data; |
75 | let binary = ""; |
76 | for (let i = 0; i < bytes.length; i++) { |
77 | binary += String.fromCharCode(bytes[i]); |
78 | } |
79 | return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); |
80 | } |
81 |
|