@automate.ax/integration-contracts 0.68.0 → 0.68.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,8 @@ import { ATTACHMENT_INPUT_SCHEMA, DRAFT_IDENTIFIER_SCHEMA, LABEL_SCHEMA, MESSAGE
5
5
  export declare const AUTOMATE_AX_EMAIL_HEADER = "X-Sent-From-Automate-Ax";
6
6
  /** Internal options for constructing one RFC 5322 message. */
7
7
  export interface BuildMessageOptions {
8
+ /** Signed identity of the automation composing the message. */
9
+ automationOriginToken?: string;
8
10
  /** Files and inline MIME parts attached to the message. */
9
11
  attachments?: z.input<typeof ATTACHMENT_INPUT_SCHEMA>[];
10
12
  /** Blind-copy recipients. */
@@ -46,6 +48,14 @@ export declare function getGmailApi(secret: Record<string, unknown>): gmail_v1.G
46
48
  * @param options - Message recipients, content, headers, and attachments.
47
49
  */
48
50
  export declare function buildRawMessage(options: BuildMessageOptions): Promise<string>;
51
+ /**
52
+ * Replaces Automate.ax's origin header without changing the MIME body.
53
+ *
54
+ * @param raw - Base64url-encoded RFC 5322 message.
55
+ * @param token - Signed automation-origin token.
56
+ * @throws When the message has no RFC 5322 header boundary.
57
+ */
58
+ export declare function setRawMessageAutomationOrigin(raw: string, token: string): string;
49
59
  /**
50
60
  * Resolves a Gmail label name or ID to its canonical ID.
51
61
  *
@@ -117,8 +127,9 @@ export declare function toMessageMetadata(message: gmail_v1.Schema$Message, oper
117
127
  * @param gmailApi - Authenticated Gmail client.
118
128
  * @param original - Message receiving the reply.
119
129
  * @param input - Reply body, recipients, headers, and attachments.
130
+ * @param automationOriginToken - Signed identity of the sending automation.
120
131
  */
121
- export declare function sendReply(gmailApi: gmail_v1.Gmail, original: gmail_v1.Schema$Message, input: z.input<typeof REPLY_OPTIONS_SCHEMA>): Promise<{
132
+ export declare function sendReply(gmailApi: gmail_v1.Gmail, original: gmail_v1.Schema$Message, input: z.input<typeof REPLY_OPTIONS_SCHEMA>, automationOriginToken?: string): Promise<{
122
133
  messageId: string;
123
134
  threadId: string;
124
135
  labelIds: string[];
@@ -4,9 +4,9 @@ import MailComposer from "nodemailer/lib/mail-composer";
4
4
  import PostalMime, { addressParser, } from "postal-mime";
5
5
  import { z } from "zod";
6
6
  export const AUTOMATE_AX_EMAIL_HEADER = "X-Sent-From-Automate-Ax";
7
+ const AUTOMATE_AX_EMAIL_HEADER_LOWERCASE = AUTOMATE_AX_EMAIL_HEADER.toLowerCase();
7
8
  const AUTOMATE_EMAIL_HEADERS = {
8
9
  "X-Mailer": "Automate.ax",
9
- [AUTOMATE_AX_EMAIL_HEADER]: "true",
10
10
  };
11
11
  const HTML_TO_TEXT = compile({ wordwrap: false });
12
12
  const GMAIL_SECRET_SCHEMA = z.object({
@@ -56,8 +56,11 @@ export async function buildRawMessage(options) {
56
56
  cc: options.cc,
57
57
  from: options.from,
58
58
  headers: {
59
- ...options.headers,
59
+ ...Object.fromEntries(Object.entries(options.headers ?? {}).filter(([name]) => name.toLowerCase() !== AUTOMATE_AX_EMAIL_HEADER_LOWERCASE)),
60
60
  ...AUTOMATE_EMAIL_HEADERS,
61
+ ...(options.automationOriginToken && {
62
+ [AUTOMATE_AX_EMAIL_HEADER]: options.automationOriginToken,
63
+ }),
61
64
  },
62
65
  html: options.html,
63
66
  inReplyTo: options.inReplyTo,
@@ -73,6 +76,37 @@ export async function buildRawMessage(options) {
73
76
  message.keepBcc = true;
74
77
  return (await message.build()).toString("base64url");
75
78
  }
79
+ /**
80
+ * Replaces Automate.ax's origin header without changing the MIME body.
81
+ *
82
+ * @param raw - Base64url-encoded RFC 5322 message.
83
+ * @param token - Signed automation-origin token.
84
+ * @throws When the message has no RFC 5322 header boundary.
85
+ */
86
+ export function setRawMessageAutomationOrigin(raw, token) {
87
+ const message = Buffer.from(raw, "base64url").toString("latin1");
88
+ const headerSeparator = message.includes("\r\n\r\n") ? "\r\n" : "\n";
89
+ const headerEnd = message.indexOf(`${headerSeparator}${headerSeparator}`);
90
+ if (headerEnd === -1)
91
+ throw new Error("Email message has no header boundary.");
92
+ const headers = [];
93
+ let removesCurrentHeader = false;
94
+ for (const line of message.slice(0, headerEnd).split(headerSeparator)) {
95
+ if (/^[\t ]/.test(line)) {
96
+ if (!removesCurrentHeader)
97
+ headers.push(line);
98
+ continue;
99
+ }
100
+ const colon = line.indexOf(":");
101
+ removesCurrentHeader =
102
+ colon !== -1 &&
103
+ line.slice(0, colon).toLowerCase() === AUTOMATE_AX_EMAIL_HEADER_LOWERCASE;
104
+ if (!removesCurrentHeader)
105
+ headers.push(line);
106
+ }
107
+ headers.push(`${AUTOMATE_AX_EMAIL_HEADER}: ${token}`);
108
+ return Buffer.from(`${headers.join(headerSeparator)}${headerSeparator}${headerSeparator}${message.slice(headerEnd + headerSeparator.length * 2)}`, "latin1").toString("base64url");
109
+ }
76
110
  /**
77
111
  * Resolves a Gmail label name or ID to its canonical ID.
78
112
  *
@@ -235,13 +269,15 @@ export function toMessageMetadata(message, operation) {
235
269
  * @param gmailApi - Authenticated Gmail client.
236
270
  * @param original - Message receiving the reply.
237
271
  * @param input - Reply body, recipients, headers, and attachments.
272
+ * @param automationOriginToken - Signed identity of the sending automation.
238
273
  */
239
- export async function sendReply(gmailApi, original, input) {
274
+ export async function sendReply(gmailApi, original, input, automationOriginToken) {
240
275
  const replyOptions = await getReplyOptions(gmailApi, original, input.replyAll ?? false);
241
276
  return toMessageMetadata((await gmailApi.users.messages.send({
242
277
  requestBody: {
243
278
  raw: await buildRawMessage({
244
279
  ...getReplyMessageOptions(replyOptions, input),
280
+ automationOriginToken,
245
281
  bcc: input.bcc,
246
282
  from: input.from,
247
283
  headers: input.headers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/integration-contracts",
3
- "version": "0.68.0",
3
+ "version": "0.68.2",
4
4
  "description": "Shared integration payload contracts and provider primitives for Automate.ax.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,7 +41,7 @@
41
41
  }
42
42
  },
43
43
  "dependencies": {
44
- "@automate.ax/codec": "0.68.0",
44
+ "@automate.ax/codec": "0.68.2",
45
45
  "@googleapis/calendar": "^15.0.0",
46
46
  "@googleapis/forms": "^6.0.1",
47
47
  "@googleapis/gmail": "^12.0.0",
@@ -19,9 +19,10 @@ import {
19
19
  } from "./schemas"
20
20
 
21
21
  export const AUTOMATE_AX_EMAIL_HEADER = "X-Sent-From-Automate-Ax"
22
+ const AUTOMATE_AX_EMAIL_HEADER_LOWERCASE =
23
+ AUTOMATE_AX_EMAIL_HEADER.toLowerCase()
22
24
  const AUTOMATE_EMAIL_HEADERS = {
23
25
  "X-Mailer": "Automate.ax",
24
- [AUTOMATE_AX_EMAIL_HEADER]: "true",
25
26
  }
26
27
  const HTML_TO_TEXT = compile({ wordwrap: false })
27
28
  const GMAIL_SECRET_SCHEMA = z.object({
@@ -37,6 +38,9 @@ const LABEL_TYPE_SCHEMA = z.enum(["system", "user"])
37
38
 
38
39
  /** Internal options for constructing one RFC 5322 message. */
39
40
  export interface BuildMessageOptions {
41
+ /** Signed identity of the automation composing the message. */
42
+ automationOriginToken?: string
43
+
40
44
  /** Files and inline MIME parts attached to the message. */
41
45
  attachments?: z.input<typeof ATTACHMENT_INPUT_SCHEMA>[]
42
46
 
@@ -120,8 +124,15 @@ export async function buildRawMessage(options: BuildMessageOptions) {
120
124
  cc: options.cc,
121
125
  from: options.from,
122
126
  headers: {
123
- ...options.headers,
127
+ ...Object.fromEntries(
128
+ Object.entries(options.headers ?? {}).filter(
129
+ ([name]) => name.toLowerCase() !== AUTOMATE_AX_EMAIL_HEADER_LOWERCASE,
130
+ ),
131
+ ),
124
132
  ...AUTOMATE_EMAIL_HEADERS,
133
+ ...(options.automationOriginToken && {
134
+ [AUTOMATE_AX_EMAIL_HEADER]: options.automationOriginToken,
135
+ }),
125
136
  },
126
137
  html: options.html,
127
138
  inReplyTo: options.inReplyTo,
@@ -140,6 +151,41 @@ export async function buildRawMessage(options: BuildMessageOptions) {
140
151
  return (await message.build()).toString("base64url")
141
152
  }
142
153
 
154
+ /**
155
+ * Replaces Automate.ax's origin header without changing the MIME body.
156
+ *
157
+ * @param raw - Base64url-encoded RFC 5322 message.
158
+ * @param token - Signed automation-origin token.
159
+ * @throws When the message has no RFC 5322 header boundary.
160
+ */
161
+ export function setRawMessageAutomationOrigin(raw: string, token: string) {
162
+ const message = Buffer.from(raw, "base64url").toString("latin1")
163
+ const headerSeparator = message.includes("\r\n\r\n") ? "\r\n" : "\n"
164
+ const headerEnd = message.indexOf(`${headerSeparator}${headerSeparator}`)
165
+ if (headerEnd === -1) throw new Error("Email message has no header boundary.")
166
+
167
+ const headers: string[] = []
168
+ let removesCurrentHeader = false
169
+ for (const line of message.slice(0, headerEnd).split(headerSeparator)) {
170
+ if (/^[\t ]/.test(line)) {
171
+ if (!removesCurrentHeader) headers.push(line)
172
+ continue
173
+ }
174
+
175
+ const colon = line.indexOf(":")
176
+ removesCurrentHeader =
177
+ colon !== -1 &&
178
+ line.slice(0, colon).toLowerCase() === AUTOMATE_AX_EMAIL_HEADER_LOWERCASE
179
+ if (!removesCurrentHeader) headers.push(line)
180
+ }
181
+ headers.push(`${AUTOMATE_AX_EMAIL_HEADER}: ${token}`)
182
+
183
+ return Buffer.from(
184
+ `${headers.join(headerSeparator)}${headerSeparator}${headerSeparator}${message.slice(headerEnd + headerSeparator.length * 2)}`,
185
+ "latin1",
186
+ ).toString("base64url")
187
+ }
188
+
143
189
  /**
144
190
  * Resolves a Gmail label name or ID to its canonical ID.
145
191
  *
@@ -345,11 +391,13 @@ export function toMessageMetadata(
345
391
  * @param gmailApi - Authenticated Gmail client.
346
392
  * @param original - Message receiving the reply.
347
393
  * @param input - Reply body, recipients, headers, and attachments.
394
+ * @param automationOriginToken - Signed identity of the sending automation.
348
395
  */
349
396
  export async function sendReply(
350
397
  gmailApi: gmail_v1.Gmail,
351
398
  original: gmail_v1.Schema$Message,
352
399
  input: z.input<typeof REPLY_OPTIONS_SCHEMA>,
400
+ automationOriginToken?: string,
353
401
  ) {
354
402
  const replyOptions = await getReplyOptions(
355
403
  gmailApi,
@@ -362,6 +410,7 @@ export async function sendReply(
362
410
  requestBody: {
363
411
  raw: await buildRawMessage({
364
412
  ...getReplyMessageOptions(replyOptions, input),
413
+ automationOriginToken,
365
414
  bcc: input.bcc,
366
415
  from: input.from,
367
416
  headers: input.headers,