@emulators/google 0.4.1 → 0.6.0
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.
- package/dist/fonts/favicon.ico +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +396 -223
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -66,6 +66,7 @@ var LABEL_ALIASES = {
|
|
|
66
66
|
updates: "CATEGORY_UPDATES",
|
|
67
67
|
forums: "CATEGORY_FORUMS"
|
|
68
68
|
};
|
|
69
|
+
var lastGeneratedHistoryId = 0n;
|
|
69
70
|
function generateUid(prefix = "") {
|
|
70
71
|
const id = randomBytes(12).toString("base64url").slice(0, 20);
|
|
71
72
|
return prefix ? `${prefix}_${id}` : id;
|
|
@@ -76,7 +77,12 @@ function generateDraftId() {
|
|
|
76
77
|
}
|
|
77
78
|
function generateHistoryId() {
|
|
78
79
|
const entropy = randomBytes(3).readUIntBE(0, 3).toString().padStart(8, "0");
|
|
79
|
-
|
|
80
|
+
let next = BigInt(`${Date.now()}${entropy}`);
|
|
81
|
+
if (next <= lastGeneratedHistoryId) {
|
|
82
|
+
next = lastGeneratedHistoryId + 1n;
|
|
83
|
+
}
|
|
84
|
+
lastGeneratedHistoryId = next;
|
|
85
|
+
return next.toString();
|
|
80
86
|
}
|
|
81
87
|
function getAuthenticatedEmail(c) {
|
|
82
88
|
const authUser = c.get("authUser");
|
|
@@ -124,9 +130,7 @@ function parseBooleanParam(value) {
|
|
|
124
130
|
return value === "true" || value === "1";
|
|
125
131
|
}
|
|
126
132
|
function ensureSystemLabels(gs, userEmail) {
|
|
127
|
-
const existingIds = new Set(
|
|
128
|
-
gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id)
|
|
129
|
-
);
|
|
133
|
+
const existingIds = new Set(gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id));
|
|
130
134
|
for (const label of SYSTEM_LABELS) {
|
|
131
135
|
if (existingIds.has(label.gmail_id)) continue;
|
|
132
136
|
gs.labels.insert({
|
|
@@ -471,9 +475,7 @@ function getCurrentHistoryId(gs, userEmail) {
|
|
|
471
475
|
...gs.history.findBy("user_email", userEmail).map((event) => event.gmail_id)
|
|
472
476
|
].filter(Boolean);
|
|
473
477
|
if (historyIds.length === 0) return "0";
|
|
474
|
-
return historyIds.reduce(
|
|
475
|
-
(latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest
|
|
476
|
-
);
|
|
478
|
+
return historyIds.reduce((latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest);
|
|
477
479
|
}
|
|
478
480
|
function listHistoryForUser(gs, userEmail, options) {
|
|
479
481
|
const requestedTypes = options.historyTypes?.length ? new Set(options.historyTypes) : null;
|
|
@@ -1056,10 +1058,7 @@ function buildPayload(gs, message, headers, format) {
|
|
|
1056
1058
|
filename: "",
|
|
1057
1059
|
headers,
|
|
1058
1060
|
body: { size: 0 },
|
|
1059
|
-
parts: [
|
|
1060
|
-
createTextBodyPart("0", "text/plain", textBody),
|
|
1061
|
-
createTextBodyPart("1", "text/html", htmlBody)
|
|
1062
|
-
]
|
|
1061
|
+
parts: [createTextBodyPart("0", "text/plain", textBody), createTextBodyPart("1", "text/html", htmlBody)]
|
|
1063
1062
|
};
|
|
1064
1063
|
}
|
|
1065
1064
|
if (htmlBody) return createTextBodyPart("", "text/html", htmlBody, headers);
|
|
@@ -1080,10 +1079,7 @@ function buildPayload(gs, message, headers, format) {
|
|
|
1080
1079
|
filename: "",
|
|
1081
1080
|
headers: [],
|
|
1082
1081
|
body: { size: 0 },
|
|
1083
|
-
parts: [
|
|
1084
|
-
createTextBodyPart("0.0", "text/plain", textBody),
|
|
1085
|
-
createTextBodyPart("0.1", "text/html", htmlBody)
|
|
1086
|
-
]
|
|
1082
|
+
parts: [createTextBodyPart("0.0", "text/plain", textBody), createTextBodyPart("0.1", "text/html", htmlBody)]
|
|
1087
1083
|
});
|
|
1088
1084
|
} else if (htmlBody) {
|
|
1089
1085
|
parts.push(createTextBodyPart("0", "text/html", htmlBody));
|
|
@@ -1181,18 +1177,10 @@ function buildMimeBodyPart(input) {
|
|
|
1181
1177
|
].join("\r\n");
|
|
1182
1178
|
}
|
|
1183
1179
|
if (input.body_html) {
|
|
1184
|
-
return [
|
|
1185
|
-
"Content-Type: text/html; charset=utf-8",
|
|
1186
|
-
"",
|
|
1187
|
-
input.body_html
|
|
1188
|
-
].join("\r\n");
|
|
1180
|
+
return ["Content-Type: text/html; charset=utf-8", "", input.body_html].join("\r\n");
|
|
1189
1181
|
}
|
|
1190
1182
|
if (input.body_text) {
|
|
1191
|
-
return [
|
|
1192
|
-
"Content-Type: text/plain; charset=utf-8",
|
|
1193
|
-
"",
|
|
1194
|
-
input.body_text
|
|
1195
|
-
].join("\r\n");
|
|
1183
|
+
return ["Content-Type: text/plain; charset=utf-8", "", input.body_text].join("\r\n");
|
|
1196
1184
|
}
|
|
1197
1185
|
return null;
|
|
1198
1186
|
}
|
|
@@ -1794,7 +1782,7 @@ function requireGmailUser(c) {
|
|
|
1794
1782
|
if (authEmail instanceof Response) {
|
|
1795
1783
|
return authEmail;
|
|
1796
1784
|
}
|
|
1797
|
-
if (!matchesRequestedUser(c.req.param("userId"), authEmail)) {
|
|
1785
|
+
if (!matchesRequestedUser(c.req.param("userId") ?? "", authEmail)) {
|
|
1798
1786
|
return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
|
|
1799
1787
|
}
|
|
1800
1788
|
return authEmail;
|
|
@@ -1926,14 +1914,25 @@ function getGoogleStore(store) {
|
|
|
1926
1914
|
oauthClients: store.collection("google.oauth_clients", ["client_id"]),
|
|
1927
1915
|
messages: store.collection("google.messages", ["gmail_id", "thread_id", "user_email"]),
|
|
1928
1916
|
drafts: store.collection("google.drafts", ["gmail_id", "message_gmail_id", "user_email"]),
|
|
1929
|
-
attachments: store.collection("google.attachments", [
|
|
1917
|
+
attachments: store.collection("google.attachments", [
|
|
1918
|
+
"gmail_id",
|
|
1919
|
+
"message_gmail_id",
|
|
1920
|
+
"user_email"
|
|
1921
|
+
]),
|
|
1930
1922
|
history: store.collection("google.history", ["gmail_id", "message_gmail_id", "user_email"]),
|
|
1931
1923
|
labels: store.collection("google.labels", ["gmail_id", "user_email", "name"]),
|
|
1932
1924
|
filters: store.collection("google.filters", ["gmail_id", "user_email"]),
|
|
1933
|
-
forwardingAddresses: store.collection("google.forwarding_addresses", [
|
|
1925
|
+
forwardingAddresses: store.collection("google.forwarding_addresses", [
|
|
1926
|
+
"user_email",
|
|
1927
|
+
"forwarding_email"
|
|
1928
|
+
]),
|
|
1934
1929
|
sendAs: store.collection("google.send_as", ["user_email", "send_as_email"]),
|
|
1935
1930
|
calendars: store.collection("google.calendars", ["google_id", "user_email"]),
|
|
1936
|
-
calendarEvents: store.collection("google.calendar_events", [
|
|
1931
|
+
calendarEvents: store.collection("google.calendar_events", [
|
|
1932
|
+
"google_id",
|
|
1933
|
+
"calendar_google_id",
|
|
1934
|
+
"user_email"
|
|
1935
|
+
]),
|
|
1937
1936
|
driveItems: store.collection("google.drive_items", ["google_id", "user_email", "mime_type"])
|
|
1938
1937
|
};
|
|
1939
1938
|
}
|
|
@@ -2039,13 +2038,7 @@ function draftRoutes({ app, store }) {
|
|
|
2039
2038
|
});
|
|
2040
2039
|
return c.json(formatDraftResource(gs, draft, "full"));
|
|
2041
2040
|
} catch {
|
|
2042
|
-
return googleApiError(
|
|
2043
|
-
c,
|
|
2044
|
-
400,
|
|
2045
|
-
"Invalid raw MIME message payload.",
|
|
2046
|
-
"invalidArgument",
|
|
2047
|
-
"INVALID_ARGUMENT"
|
|
2048
|
-
);
|
|
2041
|
+
return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
|
|
2049
2042
|
}
|
|
2050
2043
|
};
|
|
2051
2044
|
const sendHandler = async (c) => {
|
|
@@ -2135,13 +2128,7 @@ function draftRoutes({ app, store }) {
|
|
|
2135
2128
|
}
|
|
2136
2129
|
return c.json(formatDraftResource(gs, updated.draft, "full"));
|
|
2137
2130
|
} catch {
|
|
2138
|
-
return googleApiError(
|
|
2139
|
-
c,
|
|
2140
|
-
400,
|
|
2141
|
-
"Invalid raw MIME message payload.",
|
|
2142
|
-
"invalidArgument",
|
|
2143
|
-
"INVALID_ARGUMENT"
|
|
2144
|
-
);
|
|
2131
|
+
return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
|
|
2145
2132
|
}
|
|
2146
2133
|
});
|
|
2147
2134
|
app.post("/gmail/v1/users/:userId/drafts/send", sendHandler);
|
|
@@ -2279,7 +2266,13 @@ function historyRoutes({ app, store }) {
|
|
|
2279
2266
|
const labelIds = getStringArray(body, "labelIds");
|
|
2280
2267
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, labelIds);
|
|
2281
2268
|
if (missingLabelIds.length > 0) {
|
|
2282
|
-
return googleApiError(
|
|
2269
|
+
return googleApiError(
|
|
2270
|
+
c,
|
|
2271
|
+
400,
|
|
2272
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2273
|
+
"invalidArgument",
|
|
2274
|
+
"INVALID_ARGUMENT"
|
|
2275
|
+
);
|
|
2283
2276
|
}
|
|
2284
2277
|
const expiration = String(Date.now() + 24 * 60 * 60 * 1e3);
|
|
2285
2278
|
const states = store.getData(WATCH_STATE_KEY) ?? /* @__PURE__ */ new Map();
|
|
@@ -2333,13 +2326,7 @@ function labelRoutes({ app, store }) {
|
|
|
2333
2326
|
return googleApiError(c, 400, "Invalid label name", "invalidArgument", "INVALID_ARGUMENT");
|
|
2334
2327
|
}
|
|
2335
2328
|
if (findLabelByName(gs, authEmail, name)) {
|
|
2336
|
-
return googleApiError(
|
|
2337
|
-
c,
|
|
2338
|
-
400,
|
|
2339
|
-
"Label name exists or conflicts",
|
|
2340
|
-
"failedPrecondition",
|
|
2341
|
-
"FAILED_PRECONDITION"
|
|
2342
|
-
);
|
|
2329
|
+
return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
|
|
2343
2330
|
}
|
|
2344
2331
|
const color = body.color && typeof body.color === "object" && !Array.isArray(body.color) ? body.color : void 0;
|
|
2345
2332
|
const label = createLabelRecord(gs, {
|
|
@@ -2397,13 +2384,7 @@ async function saveLabel(c, gs, replaceMissingFields) {
|
|
|
2397
2384
|
if (name) {
|
|
2398
2385
|
const conflicting = findLabelByName(gs, authEmail, name);
|
|
2399
2386
|
if (conflicting && conflicting.gmail_id !== label.gmail_id) {
|
|
2400
|
-
return googleApiError(
|
|
2401
|
-
c,
|
|
2402
|
-
400,
|
|
2403
|
-
"Label name exists or conflicts",
|
|
2404
|
-
"failedPrecondition",
|
|
2405
|
-
"FAILED_PRECONDITION"
|
|
2406
|
-
);
|
|
2387
|
+
return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
|
|
2407
2388
|
}
|
|
2408
2389
|
}
|
|
2409
2390
|
const updated = updateLabelRecord(gs, label, {
|
|
@@ -2427,7 +2408,13 @@ function messageRoutes({ app, store }) {
|
|
|
2427
2408
|
const defaultLabelIds = mode === "send" ? dedupeLabelIds([...labelIds, "SENT"]) : labelIds.length > 0 ? labelIds : mode === "import" ? ["INBOX", "UNREAD"] : [];
|
|
2428
2409
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, defaultLabelIds);
|
|
2429
2410
|
if (missingLabelIds.length > 0) {
|
|
2430
|
-
return googleApiError(
|
|
2411
|
+
return googleApiError(
|
|
2412
|
+
c,
|
|
2413
|
+
400,
|
|
2414
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2415
|
+
"invalidArgument",
|
|
2416
|
+
"INVALID_ARGUMENT"
|
|
2417
|
+
);
|
|
2431
2418
|
}
|
|
2432
2419
|
const messageInput = parseMessageInputFromBody(body, {
|
|
2433
2420
|
from: mode === "send" ? authEmail : void 0
|
|
@@ -2449,13 +2436,7 @@ function messageRoutes({ app, store }) {
|
|
|
2449
2436
|
});
|
|
2450
2437
|
return c.json(formatMessageResource(gs, message, "full"));
|
|
2451
2438
|
} catch {
|
|
2452
|
-
return googleApiError(
|
|
2453
|
-
c,
|
|
2454
|
-
400,
|
|
2455
|
-
"Invalid raw MIME message payload.",
|
|
2456
|
-
"invalidArgument",
|
|
2457
|
-
"INVALID_ARGUMENT"
|
|
2458
|
-
);
|
|
2439
|
+
return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
|
|
2459
2440
|
}
|
|
2460
2441
|
};
|
|
2461
2442
|
app.get("/gmail/v1/users/:userId/messages", (c) => {
|
|
@@ -2489,16 +2470,18 @@ function messageRoutes({ app, store }) {
|
|
|
2489
2470
|
const removeLabelIds = getStringArray(body, "removeLabelIds");
|
|
2490
2471
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
2491
2472
|
if (missingLabelIds.length > 0) {
|
|
2492
|
-
return googleApiError(
|
|
2473
|
+
return googleApiError(
|
|
2474
|
+
c,
|
|
2475
|
+
400,
|
|
2476
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2477
|
+
"invalidArgument",
|
|
2478
|
+
"INVALID_ARGUMENT"
|
|
2479
|
+
);
|
|
2493
2480
|
}
|
|
2494
2481
|
for (const messageId of ids) {
|
|
2495
2482
|
const message = getMessageById(gs, authEmail, messageId);
|
|
2496
2483
|
if (!message) continue;
|
|
2497
|
-
markMessageModified(
|
|
2498
|
-
gs,
|
|
2499
|
-
message,
|
|
2500
|
-
applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
|
|
2501
|
-
);
|
|
2484
|
+
markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds));
|
|
2502
2485
|
}
|
|
2503
2486
|
return c.body(null, 204);
|
|
2504
2487
|
});
|
|
@@ -2565,7 +2548,13 @@ function messageRoutes({ app, store }) {
|
|
|
2565
2548
|
const removeLabelIds = getStringArray(body, "removeLabelIds");
|
|
2566
2549
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
2567
2550
|
if (missingLabelIds.length > 0) {
|
|
2568
|
-
return googleApiError(
|
|
2551
|
+
return googleApiError(
|
|
2552
|
+
c,
|
|
2553
|
+
400,
|
|
2554
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2555
|
+
"invalidArgument",
|
|
2556
|
+
"INVALID_ARGUMENT"
|
|
2557
|
+
);
|
|
2569
2558
|
}
|
|
2570
2559
|
const updated = markMessageModified(
|
|
2571
2560
|
gs,
|
|
@@ -2581,7 +2570,9 @@ function messageRoutes({ app, store }) {
|
|
|
2581
2570
|
if (!message) {
|
|
2582
2571
|
return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
|
|
2583
2572
|
}
|
|
2584
|
-
return c.json(
|
|
2573
|
+
return c.json(
|
|
2574
|
+
formatMessageResource(gs, markMessageModified(gs, message, trashLabelIds(message.label_ids)), "full")
|
|
2575
|
+
);
|
|
2585
2576
|
});
|
|
2586
2577
|
app.post("/gmail/v1/users/:userId/messages/:id/untrash", (c) => {
|
|
2587
2578
|
const authEmail = requireGmailUser(c);
|
|
@@ -2590,7 +2581,9 @@ function messageRoutes({ app, store }) {
|
|
|
2590
2581
|
if (!message) {
|
|
2591
2582
|
return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
|
|
2592
2583
|
}
|
|
2593
|
-
return c.json(
|
|
2584
|
+
return c.json(
|
|
2585
|
+
formatMessageResource(gs, markMessageModified(gs, message, untrashLabelIds(message.label_ids)), "full")
|
|
2586
|
+
);
|
|
2594
2587
|
});
|
|
2595
2588
|
app.delete("/gmail/v1/users/:userId/messages/:id", (c) => {
|
|
2596
2589
|
const authEmail = requireGmailUser(c);
|
|
@@ -2609,8 +2602,6 @@ import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
|
2609
2602
|
import { SignJWT } from "jose";
|
|
2610
2603
|
|
|
2611
2604
|
// ../core/dist/index.js
|
|
2612
|
-
import { Hono } from "hono";
|
|
2613
|
-
import { cors } from "hono/cors";
|
|
2614
2605
|
import { jwtVerify, importPKCS8 } from "jose";
|
|
2615
2606
|
import { readFileSync } from "fs";
|
|
2616
2607
|
import { fileURLToPath } from "url";
|
|
@@ -2636,6 +2627,7 @@ var FONTS = {
|
|
|
2636
2627
|
"geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
|
|
2637
2628
|
"GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
|
|
2638
2629
|
};
|
|
2630
|
+
var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
|
|
2639
2631
|
function escapeHtml(s) {
|
|
2640
2632
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2641
2633
|
}
|
|
@@ -2787,6 +2779,132 @@ body{
|
|
|
2787
2779
|
.app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
|
|
2788
2780
|
.app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
|
|
2789
2781
|
.empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
|
|
2782
|
+
|
|
2783
|
+
.inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
|
|
2784
|
+
.inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
|
|
2785
|
+
.inspector-tabs a{
|
|
2786
|
+
padding:7px 16px;border-radius:6px;text-decoration:none;
|
|
2787
|
+
font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
|
|
2788
|
+
transition:color .15s,border-color .15s;
|
|
2789
|
+
}
|
|
2790
|
+
.inspector-tabs a:hover{color:#33ff00;}
|
|
2791
|
+
.inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
|
|
2792
|
+
.inspector-section{margin-bottom:24px;}
|
|
2793
|
+
.inspector-section h2{
|
|
2794
|
+
font-family:'Geist Pixel',monospace;
|
|
2795
|
+
font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
|
|
2796
|
+
}
|
|
2797
|
+
.inspector-section h3{
|
|
2798
|
+
font-family:'Geist Pixel',monospace;
|
|
2799
|
+
font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
|
|
2800
|
+
}
|
|
2801
|
+
.inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
|
|
2802
|
+
.inspector-table th,.inspector-table td{
|
|
2803
|
+
text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
|
|
2804
|
+
font-size:.8125rem;
|
|
2805
|
+
}
|
|
2806
|
+
.inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
|
|
2807
|
+
.inspector-table td{color:#33ff00;}
|
|
2808
|
+
.inspector-table tbody tr{transition:background .1s;}
|
|
2809
|
+
.inspector-table tbody tr:hover{background:#0a3300;}
|
|
2810
|
+
.inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
|
|
2811
|
+
|
|
2812
|
+
.checkout-layout{
|
|
2813
|
+
display:flex;min-height:calc(100vh - 42px);
|
|
2814
|
+
}
|
|
2815
|
+
.checkout-summary{
|
|
2816
|
+
flex:1;background:#020;padding:48px 40px 48px 10%;
|
|
2817
|
+
display:flex;flex-direction:column;justify-content:center;
|
|
2818
|
+
border-right:1px solid #0a3300;
|
|
2819
|
+
}
|
|
2820
|
+
.checkout-form-side{
|
|
2821
|
+
flex:1;background:#000;padding:48px 10% 48px 40px;
|
|
2822
|
+
display:flex;flex-direction:column;justify-content:center;
|
|
2823
|
+
}
|
|
2824
|
+
.checkout-merchant{
|
|
2825
|
+
display:flex;align-items:center;gap:10px;margin-bottom:6px;
|
|
2826
|
+
}
|
|
2827
|
+
.checkout-merchant-name{
|
|
2828
|
+
font-family:'Geist Pixel',monospace;
|
|
2829
|
+
font-size:.9375rem;font-weight:600;color:#33ff00;
|
|
2830
|
+
}
|
|
2831
|
+
.checkout-test-badge{
|
|
2832
|
+
font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
|
|
2833
|
+
background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
|
|
2834
|
+
}
|
|
2835
|
+
.checkout-total{
|
|
2836
|
+
font-family:'Geist Pixel',monospace;
|
|
2837
|
+
font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
|
|
2838
|
+
}
|
|
2839
|
+
.checkout-line-item{
|
|
2840
|
+
display:flex;align-items:center;gap:14px;padding:14px 0;
|
|
2841
|
+
border-bottom:1px solid #0a3300;
|
|
2842
|
+
}
|
|
2843
|
+
.checkout-line-item:first-child{border-top:1px solid #0a3300;}
|
|
2844
|
+
.checkout-item-icon{
|
|
2845
|
+
width:42px;height:42px;border-radius:6px;background:#0a3300;
|
|
2846
|
+
display:flex;align-items:center;justify-content:center;flex-shrink:0;
|
|
2847
|
+
font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
|
|
2848
|
+
}
|
|
2849
|
+
.checkout-item-details{flex:1;min-width:0;}
|
|
2850
|
+
.checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
|
|
2851
|
+
.checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
|
|
2852
|
+
.checkout-item-price{
|
|
2853
|
+
font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
|
|
2854
|
+
}
|
|
2855
|
+
.checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
|
|
2856
|
+
.checkout-totals{margin-top:20px;}
|
|
2857
|
+
.checkout-totals-row{
|
|
2858
|
+
display:flex;justify-content:space-between;padding:6px 0;
|
|
2859
|
+
font-size:.8125rem;color:#1a8c00;
|
|
2860
|
+
}
|
|
2861
|
+
.checkout-totals-row.total{
|
|
2862
|
+
border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
|
|
2863
|
+
font-size:.9375rem;font-weight:600;color:#33ff00;
|
|
2864
|
+
}
|
|
2865
|
+
.checkout-form-section{margin-bottom:24px;}
|
|
2866
|
+
.checkout-form-label{
|
|
2867
|
+
font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
|
|
2868
|
+
}
|
|
2869
|
+
.checkout-input{
|
|
2870
|
+
width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
|
|
2871
|
+
background:#020;color:#33ff00;font:inherit;font-size:.875rem;
|
|
2872
|
+
transition:border-color .15s;outline:none;
|
|
2873
|
+
}
|
|
2874
|
+
.checkout-input:focus{border-color:#33ff00;}
|
|
2875
|
+
.checkout-input::placeholder{color:#116600;}
|
|
2876
|
+
.checkout-card-box{
|
|
2877
|
+
border:1px solid #0a3300;border-radius:6px;padding:14px;
|
|
2878
|
+
background:#020;
|
|
2879
|
+
}
|
|
2880
|
+
.checkout-card-row{
|
|
2881
|
+
display:flex;gap:12px;margin-top:10px;
|
|
2882
|
+
}
|
|
2883
|
+
.checkout-card-row .checkout-input{flex:1;}
|
|
2884
|
+
.checkout-sim-note{
|
|
2885
|
+
font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
|
|
2886
|
+
font-style:italic;
|
|
2887
|
+
}
|
|
2888
|
+
.checkout-pay-btn{
|
|
2889
|
+
width:100%;padding:14px;border:none;border-radius:8px;
|
|
2890
|
+
background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
|
|
2891
|
+
cursor:pointer;transition:background .15s;
|
|
2892
|
+
font-family:'Geist Pixel',monospace;
|
|
2893
|
+
}
|
|
2894
|
+
.checkout-pay-btn:hover{background:#44ff22;}
|
|
2895
|
+
.checkout-cancel{
|
|
2896
|
+
text-align:center;margin-top:14px;
|
|
2897
|
+
}
|
|
2898
|
+
.checkout-cancel a{
|
|
2899
|
+
color:#1a8c00;text-decoration:none;font-size:.8125rem;
|
|
2900
|
+
transition:color .15s;
|
|
2901
|
+
}
|
|
2902
|
+
.checkout-cancel a:hover{color:#33ff00;}
|
|
2903
|
+
@media(max-width:768px){
|
|
2904
|
+
.checkout-layout{flex-direction:column;}
|
|
2905
|
+
.checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
|
|
2906
|
+
.checkout-form-side{padding:32px 20px;}
|
|
2907
|
+
}
|
|
2790
2908
|
`;
|
|
2791
2909
|
var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
|
|
2792
2910
|
function emuBar(service) {
|
|
@@ -2806,6 +2924,7 @@ function head(title) {
|
|
|
2806
2924
|
<head>
|
|
2807
2925
|
<meta charset="utf-8"/>
|
|
2808
2926
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
2927
|
+
<link rel="icon" href="/_emulate/favicon.ico"/>
|
|
2809
2928
|
<title>${escapeHtml(title)} | emulate</title>
|
|
2810
2929
|
<style>${CSS}</style>
|
|
2811
2930
|
</head>`;
|
|
@@ -2909,6 +3028,7 @@ async function createIdToken(user, clientId, nonce, baseUrl) {
|
|
|
2909
3028
|
family_name: user.family_name,
|
|
2910
3029
|
picture: user.picture,
|
|
2911
3030
|
locale: user.locale,
|
|
3031
|
+
...user.hd ? { hd: user.hd } : {},
|
|
2912
3032
|
...nonce ? { nonce } : {}
|
|
2913
3033
|
}).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuer(baseUrl).setAudience(clientId).setIssuedAt().setExpirationTime("1h");
|
|
2914
3034
|
return builder.sign(JWT_SECRET);
|
|
@@ -2936,7 +3056,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
2936
3056
|
"given_name",
|
|
2937
3057
|
"family_name",
|
|
2938
3058
|
"picture",
|
|
2939
|
-
"locale"
|
|
3059
|
+
"locale",
|
|
3060
|
+
"hd"
|
|
2940
3061
|
],
|
|
2941
3062
|
code_challenge_methods_supported: ["plain", "S256"]
|
|
2942
3063
|
});
|
|
@@ -2964,7 +3085,11 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
2964
3085
|
}
|
|
2965
3086
|
if (redirect_uri && !matchesRedirectUri(redirect_uri, client.redirect_uris)) {
|
|
2966
3087
|
return c.html(
|
|
2967
|
-
renderErrorPage(
|
|
3088
|
+
renderErrorPage(
|
|
3089
|
+
"Redirect URI mismatch",
|
|
3090
|
+
"The redirect_uri is not registered for this application.",
|
|
3091
|
+
SERVICE_LABEL
|
|
3092
|
+
),
|
|
2968
3093
|
400
|
|
2969
3094
|
);
|
|
2970
3095
|
}
|
|
@@ -3076,7 +3201,13 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
3076
3201
|
});
|
|
3077
3202
|
}
|
|
3078
3203
|
if (grant_type !== "authorization_code") {
|
|
3079
|
-
return c.json(
|
|
3204
|
+
return c.json(
|
|
3205
|
+
{
|
|
3206
|
+
error: "unsupported_grant_type",
|
|
3207
|
+
error_description: "Only authorization_code and refresh_token are supported."
|
|
3208
|
+
},
|
|
3209
|
+
400
|
|
3210
|
+
);
|
|
3080
3211
|
}
|
|
3081
3212
|
const pendingMap = getPendingCodes(store);
|
|
3082
3213
|
const pending = pendingMap.get(code);
|
|
@@ -3149,7 +3280,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
3149
3280
|
given_name: user.given_name,
|
|
3150
3281
|
family_name: user.family_name,
|
|
3151
3282
|
picture: user.picture,
|
|
3152
|
-
locale: user.locale
|
|
3283
|
+
locale: user.locale,
|
|
3284
|
+
...user.hd ? { hd: user.hd } : {}
|
|
3153
3285
|
});
|
|
3154
3286
|
});
|
|
3155
3287
|
app.post("/oauth2/revoke", async (c) => {
|
|
@@ -3201,7 +3333,13 @@ function settingsRoutes({ app, store }) {
|
|
|
3201
3333
|
}
|
|
3202
3334
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
3203
3335
|
if (missingLabelIds.length > 0) {
|
|
3204
|
-
return googleApiError(
|
|
3336
|
+
return googleApiError(
|
|
3337
|
+
c,
|
|
3338
|
+
400,
|
|
3339
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
3340
|
+
"invalidArgument",
|
|
3341
|
+
"INVALID_ARGUMENT"
|
|
3342
|
+
);
|
|
3205
3343
|
}
|
|
3206
3344
|
if (findMatchingFilter(gs, {
|
|
3207
3345
|
user_email: authEmail,
|
|
@@ -3306,14 +3444,16 @@ function threadRoutes({ app, store }) {
|
|
|
3306
3444
|
const removeLabelIds = getStringArray(body, "removeLabelIds");
|
|
3307
3445
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
3308
3446
|
if (missingLabelIds.length > 0) {
|
|
3309
|
-
return googleApiError(
|
|
3447
|
+
return googleApiError(
|
|
3448
|
+
c,
|
|
3449
|
+
400,
|
|
3450
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
3451
|
+
"invalidArgument",
|
|
3452
|
+
"INVALID_ARGUMENT"
|
|
3453
|
+
);
|
|
3310
3454
|
}
|
|
3311
3455
|
const updated = messages.map(
|
|
3312
|
-
(message) => markMessageModified(
|
|
3313
|
-
gs,
|
|
3314
|
-
message,
|
|
3315
|
-
applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
|
|
3316
|
-
)
|
|
3456
|
+
(message) => markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds))
|
|
3317
3457
|
);
|
|
3318
3458
|
return c.json(formatThreadResource(gs, updated, "full"));
|
|
3319
3459
|
});
|
|
@@ -3364,120 +3504,148 @@ function seedDefaults(store, _baseUrl) {
|
|
|
3364
3504
|
family_name: "User",
|
|
3365
3505
|
picture: null,
|
|
3366
3506
|
email_verified: true,
|
|
3367
|
-
locale: "en"
|
|
3507
|
+
locale: "en",
|
|
3508
|
+
hd: null
|
|
3368
3509
|
});
|
|
3369
3510
|
}
|
|
3370
3511
|
ensureSystemLabels(gs, defaultEmail);
|
|
3371
|
-
seedCalendars(
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3512
|
+
seedCalendars(
|
|
3513
|
+
store,
|
|
3514
|
+
[
|
|
3515
|
+
{
|
|
3516
|
+
id: "primary",
|
|
3517
|
+
user_email: defaultEmail,
|
|
3518
|
+
summary: defaultEmail,
|
|
3519
|
+
primary: true,
|
|
3520
|
+
selected: true,
|
|
3521
|
+
time_zone: "UTC"
|
|
3522
|
+
},
|
|
3523
|
+
{
|
|
3524
|
+
id: "cal_team",
|
|
3525
|
+
user_email: defaultEmail,
|
|
3526
|
+
summary: "Team Calendar",
|
|
3527
|
+
description: "Shared team events",
|
|
3528
|
+
selected: true,
|
|
3529
|
+
time_zone: "UTC"
|
|
3530
|
+
}
|
|
3531
|
+
],
|
|
3532
|
+
defaultEmail
|
|
3533
|
+
);
|
|
3534
|
+
seedCalendarEvents(
|
|
3535
|
+
store,
|
|
3536
|
+
[
|
|
3537
|
+
{
|
|
3538
|
+
id: "evt_standup",
|
|
3539
|
+
user_email: defaultEmail,
|
|
3540
|
+
calendar_id: "primary",
|
|
3541
|
+
summary: "Daily Standup",
|
|
3542
|
+
description: "Team sync",
|
|
3543
|
+
start_date_time: new Date(Date.now() + 60 * 60 * 1e3).toISOString(),
|
|
3544
|
+
end_date_time: new Date(Date.now() + 90 * 60 * 1e3).toISOString(),
|
|
3545
|
+
attendees: [
|
|
3546
|
+
{ email: defaultEmail, display_name: "Test User" },
|
|
3547
|
+
{ email: "teammate@example.com", display_name: "Teammate" }
|
|
3548
|
+
],
|
|
3549
|
+
conference_entry_points: [
|
|
3550
|
+
{
|
|
3551
|
+
entry_point_type: "video",
|
|
3552
|
+
uri: "https://meet.google.com/emulate-standup",
|
|
3553
|
+
label: "Google Meet"
|
|
3554
|
+
}
|
|
3555
|
+
],
|
|
3556
|
+
hangout_link: "https://meet.google.com/emulate-standup"
|
|
3557
|
+
}
|
|
3558
|
+
],
|
|
3559
|
+
defaultEmail
|
|
3560
|
+
);
|
|
3561
|
+
seedDriveItems(
|
|
3562
|
+
store,
|
|
3563
|
+
[
|
|
3564
|
+
{
|
|
3565
|
+
id: "drv_root_receipts",
|
|
3566
|
+
user_email: defaultEmail,
|
|
3567
|
+
name: "Receipts",
|
|
3568
|
+
mime_type: "application/vnd.google-apps.folder",
|
|
3569
|
+
parent_ids: ["root"]
|
|
3570
|
+
},
|
|
3571
|
+
{
|
|
3572
|
+
id: "drv_receipt_pdf",
|
|
3573
|
+
user_email: defaultEmail,
|
|
3574
|
+
name: "March Receipt.pdf",
|
|
3575
|
+
mime_type: "application/pdf",
|
|
3576
|
+
parent_ids: ["drv_root_receipts"],
|
|
3577
|
+
data: "receipt-pdf-data"
|
|
3578
|
+
}
|
|
3579
|
+
],
|
|
3580
|
+
defaultEmail
|
|
3581
|
+
);
|
|
3582
|
+
seedMessages(
|
|
3583
|
+
store,
|
|
3584
|
+
[
|
|
3585
|
+
{
|
|
3586
|
+
id: "msg_welcome",
|
|
3587
|
+
thread_id: "thr_welcome",
|
|
3588
|
+
user_email: defaultEmail,
|
|
3589
|
+
from: "Welcome Team <welcome@example.com>",
|
|
3590
|
+
to: defaultEmail,
|
|
3591
|
+
subject: "Welcome to your local Gmail emulator",
|
|
3592
|
+
snippet: "Your OAuth flow is set up and Gmail message, thread, and label APIs are ready.",
|
|
3593
|
+
body_text: "Your OAuth flow is set up and Gmail message, thread, and label APIs are ready.\n\nUse this inbox to test Gmail automations locally.",
|
|
3594
|
+
label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
|
|
3595
|
+
date: new Date(Date.now() - 60 * 60 * 1e3).toISOString()
|
|
3596
|
+
},
|
|
3597
|
+
{
|
|
3598
|
+
id: "msg_build",
|
|
3599
|
+
thread_id: "thr_build",
|
|
3600
|
+
user_email: defaultEmail,
|
|
3601
|
+
from: "Build Bot <builds@example.com>",
|
|
3602
|
+
to: defaultEmail,
|
|
3603
|
+
subject: "Nightly build finished successfully",
|
|
3604
|
+
snippet: "The latest build completed successfully in 6 minutes.",
|
|
3605
|
+
body_text: "The latest build completed successfully in 6 minutes.\n\nArtifact upload finished and smoke checks passed.",
|
|
3606
|
+
label_ids: ["INBOX", "CATEGORY_UPDATES"],
|
|
3607
|
+
date: new Date(Date.now() - 2 * 60 * 60 * 1e3).toISOString()
|
|
3608
|
+
},
|
|
3609
|
+
{
|
|
3610
|
+
id: "msg_build_reply",
|
|
3611
|
+
thread_id: "thr_build",
|
|
3612
|
+
user_email: defaultEmail,
|
|
3613
|
+
from: defaultEmail,
|
|
3614
|
+
to: "Build Bot <builds@example.com>",
|
|
3615
|
+
subject: "Re: Nightly build finished successfully",
|
|
3616
|
+
snippet: "Thanks, I will review the artifact after lunch.",
|
|
3617
|
+
body_text: "Thanks, I will review the artifact after lunch.",
|
|
3618
|
+
label_ids: ["SENT"],
|
|
3619
|
+
date: new Date(Date.now() - 90 * 60 * 1e3).toISOString(),
|
|
3620
|
+
in_reply_to: "<msg_build@emulate.google.local>",
|
|
3621
|
+
references: "<msg_build@emulate.google.local>"
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
id: "msg_draft",
|
|
3625
|
+
thread_id: "thr_draft",
|
|
3626
|
+
user_email: defaultEmail,
|
|
3627
|
+
from: defaultEmail,
|
|
3628
|
+
to: "someone@example.com",
|
|
3629
|
+
subject: "Draft follow-up",
|
|
3630
|
+
snippet: "Checking in on the open question from yesterday.",
|
|
3631
|
+
body_text: "Checking in on the open question from yesterday.",
|
|
3632
|
+
label_ids: ["DRAFT"],
|
|
3633
|
+
date: new Date(Date.now() - 30 * 60 * 1e3).toISOString()
|
|
3634
|
+
}
|
|
3635
|
+
],
|
|
3636
|
+
defaultEmail
|
|
3637
|
+
);
|
|
3638
|
+
}
|
|
3639
|
+
var CONSUMER_EMAIL_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
|
|
3640
|
+
function deriveHd(email) {
|
|
3641
|
+
const domain = email.split("@")[1]?.toLowerCase();
|
|
3642
|
+
if (!domain) return null;
|
|
3643
|
+
if (CONSUMER_EMAIL_DOMAINS.has(domain)) return null;
|
|
3644
|
+
return domain;
|
|
3645
|
+
}
|
|
3646
|
+
function resolveHd(user) {
|
|
3647
|
+
if (user.hd !== void 0) return user.hd || null;
|
|
3648
|
+
return deriveHd(user.email);
|
|
3481
3649
|
}
|
|
3482
3650
|
function seedFromConfig(store, _baseUrl, config) {
|
|
3483
3651
|
const gs = getGoogleStore(store);
|
|
@@ -3494,7 +3662,8 @@ function seedFromConfig(store, _baseUrl, config) {
|
|
|
3494
3662
|
family_name: user.family_name ?? nameParts.slice(1).join(" "),
|
|
3495
3663
|
picture: user.picture ?? null,
|
|
3496
3664
|
email_verified: user.email_verified ?? true,
|
|
3497
|
-
locale: user.locale ?? "en"
|
|
3665
|
+
locale: user.locale ?? "en",
|
|
3666
|
+
hd: resolveHd(user)
|
|
3498
3667
|
});
|
|
3499
3668
|
}
|
|
3500
3669
|
ensureSystemLabels(gs, user.email);
|
|
@@ -3555,29 +3724,33 @@ function seedMessages(store, messages, fallbackEmail) {
|
|
|
3555
3724
|
const userEmail = message.user_email ?? fallbackEmail;
|
|
3556
3725
|
ensureSystemLabels(gs, userEmail);
|
|
3557
3726
|
if (message.id && gs.messages.findOneBy("gmail_id", message.id)) continue;
|
|
3558
|
-
createStoredMessage(
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3727
|
+
createStoredMessage(
|
|
3728
|
+
gs,
|
|
3729
|
+
{
|
|
3730
|
+
gmail_id: message.id,
|
|
3731
|
+
thread_id: message.thread_id,
|
|
3732
|
+
user_email: userEmail,
|
|
3733
|
+
raw: message.raw ?? null,
|
|
3734
|
+
from: message.from,
|
|
3735
|
+
to: message.to,
|
|
3736
|
+
cc: message.cc ?? null,
|
|
3737
|
+
bcc: message.bcc ?? null,
|
|
3738
|
+
reply_to: message.reply_to ?? null,
|
|
3739
|
+
subject: message.subject,
|
|
3740
|
+
snippet: message.snippet,
|
|
3741
|
+
body_text: message.body_text ?? null,
|
|
3742
|
+
body_html: message.body_html ?? null,
|
|
3743
|
+
label_ids: message.label_ids ?? ["INBOX", "UNREAD"],
|
|
3744
|
+
date: message.date,
|
|
3745
|
+
internal_date: message.internal_date,
|
|
3746
|
+
message_id: message.message_id,
|
|
3747
|
+
references: message.references ?? null,
|
|
3748
|
+
in_reply_to: message.in_reply_to ?? null
|
|
3749
|
+
},
|
|
3750
|
+
{
|
|
3751
|
+
createMissingCustomLabels: true
|
|
3752
|
+
}
|
|
3753
|
+
);
|
|
3581
3754
|
}
|
|
3582
3755
|
}
|
|
3583
3756
|
function seedCalendars(store, calendars, fallbackEmail) {
|