From cad60019b8bc0b92d087f8df0873d7cb0026cd75 Mon Sep 17 00:00:00 2001 From: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com> Date: Sun, 25 Oct 2026 21:50:44 +0800 Subject: [PATCH] fix(gmail): tolerate dates outside the JavaScript date range --- packages/integrations/src/google.ts | 3 +- tests/google-date.test.ts | 56 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/google-date.test.ts diff --git a/packages/integrations/src/google.ts b/packages/integrations/src/google.ts index b656f6570..800268156 100644 --- a/packages/integrations/src/google.ts +++ b/packages/integrations/src/google.ts @@ -341,6 +341,7 @@ function mapMessage(message: z.infer): Mail { const time = message.internalDate ? Number(message.internalDate) : Date.parse(metadata.get("date") ?? ""); + const date = new Date(time); const body = plain.length ? plain.join("\n\n") : html.length @@ -355,7 +356,7 @@ function mapMessage(message: z.infer): Mail { to: addresses(metadata.get("to") ?? ""), subject: decodeHeader(metadata.get("subject") ?? "(No subject)"), body, - date: Number.isFinite(time) ? new Date(time).toISOString() : "", + date: Number.isFinite(date.getTime()) ? date.toISOString() : "", unread: message.labelIds.includes("UNREAD"), label: message.labelIds.includes("INBOX") ? "Inbox" diff --git a/tests/google-date.test.ts b/tests/google-date.test.ts new file mode 100644 index 000000000..d05b32070 --- /dev/null +++ b/tests/google-date.test.ts @@ -0,0 +1,56 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { GoogleClient } from "../packages/integrations/src/google.ts"; + +test("invalid Gmail dates do not prevent inbox and thread reads", async () => { + for (const internalDate of ["8640000000000001", "-8640000000000001", "1e100", "invalid"]) { + const messages = [ + { id: "invalid-date", threadId: "thread1", internalDate, snippet: "Still readable" }, + { id: "valid-date", threadId: "thread1", internalDate: "1791658800000" }, + ]; + const client = new GoogleClient({ + getAccessToken: async () => "synthetic-access-token", + fetch: async (input) => { + const path = new URL(String(input)).pathname; + if (path.endsWith("/messages")) + return Response.json({ messages: messages.map(({ id }) => ({ id })) }); + if (path.includes("/threads/")) return Response.json({ id: "thread1", messages }); + const message = messages.find(({ id }) => path.endsWith(`/${id}`)); + assert.ok(message); + return Response.json(message); + }, + }); + for (const mails of [await client.listMail(), await client.getThread("thread1")]) { + assert.equal(mails.length, 2); + assert.equal(mails[0].body, "Still readable"); + assert.equal(mails[0].date, ""); + assert.equal(mails[1].date, "2026-10-10T19:00:00.000Z"); + } + } +}); + +test("Gmail dates retain valid epoch, date limits and Date-header fallback", async () => { + for (const [internalDate, expected] of [ + ["0", "1970-01-01T00:00:00.000Z"], + ["8640000000000000", "+275760-09-13T00:00:00.000Z"], + ["-8640000000000000", "-271821-04-20T00:00:00.000Z"], + [undefined, "2026-10-10T19:00:00.000Z"], + ]) { + const client = new GoogleClient({ + getAccessToken: async () => "synthetic-access-token", + fetch: async () => + Response.json({ + id: "thread1", + messages: [ + { + id: "message1", + threadId: "thread1", + internalDate, + payload: { headers: [{ name: "Date", value: "Sat, 10 Oct 2026 19:00:00 GMT" }] }, + }, + ], + }), + }); + assert.equal((await client.getThread("thread1"))[0].date, expected); + } +});