@emulators/google 0.4.0 → 0.5.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/README.md +130 -0
- package/dist/fonts/favicon.ico +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +389 -220
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -124,9 +124,7 @@ function parseBooleanParam(value) {
|
|
|
124
124
|
return value === "true" || value === "1";
|
|
125
125
|
}
|
|
126
126
|
function ensureSystemLabels(gs, userEmail) {
|
|
127
|
-
const existingIds = new Set(
|
|
128
|
-
gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id)
|
|
129
|
-
);
|
|
127
|
+
const existingIds = new Set(gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id));
|
|
130
128
|
for (const label of SYSTEM_LABELS) {
|
|
131
129
|
if (existingIds.has(label.gmail_id)) continue;
|
|
132
130
|
gs.labels.insert({
|
|
@@ -471,9 +469,7 @@ function getCurrentHistoryId(gs, userEmail) {
|
|
|
471
469
|
...gs.history.findBy("user_email", userEmail).map((event) => event.gmail_id)
|
|
472
470
|
].filter(Boolean);
|
|
473
471
|
if (historyIds.length === 0) return "0";
|
|
474
|
-
return historyIds.reduce(
|
|
475
|
-
(latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest
|
|
476
|
-
);
|
|
472
|
+
return historyIds.reduce((latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest);
|
|
477
473
|
}
|
|
478
474
|
function listHistoryForUser(gs, userEmail, options) {
|
|
479
475
|
const requestedTypes = options.historyTypes?.length ? new Set(options.historyTypes) : null;
|
|
@@ -1056,10 +1052,7 @@ function buildPayload(gs, message, headers, format) {
|
|
|
1056
1052
|
filename: "",
|
|
1057
1053
|
headers,
|
|
1058
1054
|
body: { size: 0 },
|
|
1059
|
-
parts: [
|
|
1060
|
-
createTextBodyPart("0", "text/plain", textBody),
|
|
1061
|
-
createTextBodyPart("1", "text/html", htmlBody)
|
|
1062
|
-
]
|
|
1055
|
+
parts: [createTextBodyPart("0", "text/plain", textBody), createTextBodyPart("1", "text/html", htmlBody)]
|
|
1063
1056
|
};
|
|
1064
1057
|
}
|
|
1065
1058
|
if (htmlBody) return createTextBodyPart("", "text/html", htmlBody, headers);
|
|
@@ -1080,10 +1073,7 @@ function buildPayload(gs, message, headers, format) {
|
|
|
1080
1073
|
filename: "",
|
|
1081
1074
|
headers: [],
|
|
1082
1075
|
body: { size: 0 },
|
|
1083
|
-
parts: [
|
|
1084
|
-
createTextBodyPart("0.0", "text/plain", textBody),
|
|
1085
|
-
createTextBodyPart("0.1", "text/html", htmlBody)
|
|
1086
|
-
]
|
|
1076
|
+
parts: [createTextBodyPart("0.0", "text/plain", textBody), createTextBodyPart("0.1", "text/html", htmlBody)]
|
|
1087
1077
|
});
|
|
1088
1078
|
} else if (htmlBody) {
|
|
1089
1079
|
parts.push(createTextBodyPart("0", "text/html", htmlBody));
|
|
@@ -1181,18 +1171,10 @@ function buildMimeBodyPart(input) {
|
|
|
1181
1171
|
].join("\r\n");
|
|
1182
1172
|
}
|
|
1183
1173
|
if (input.body_html) {
|
|
1184
|
-
return [
|
|
1185
|
-
"Content-Type: text/html; charset=utf-8",
|
|
1186
|
-
"",
|
|
1187
|
-
input.body_html
|
|
1188
|
-
].join("\r\n");
|
|
1174
|
+
return ["Content-Type: text/html; charset=utf-8", "", input.body_html].join("\r\n");
|
|
1189
1175
|
}
|
|
1190
1176
|
if (input.body_text) {
|
|
1191
|
-
return [
|
|
1192
|
-
"Content-Type: text/plain; charset=utf-8",
|
|
1193
|
-
"",
|
|
1194
|
-
input.body_text
|
|
1195
|
-
].join("\r\n");
|
|
1177
|
+
return ["Content-Type: text/plain; charset=utf-8", "", input.body_text].join("\r\n");
|
|
1196
1178
|
}
|
|
1197
1179
|
return null;
|
|
1198
1180
|
}
|
|
@@ -1794,7 +1776,7 @@ function requireGmailUser(c) {
|
|
|
1794
1776
|
if (authEmail instanceof Response) {
|
|
1795
1777
|
return authEmail;
|
|
1796
1778
|
}
|
|
1797
|
-
if (!matchesRequestedUser(c.req.param("userId"), authEmail)) {
|
|
1779
|
+
if (!matchesRequestedUser(c.req.param("userId") ?? "", authEmail)) {
|
|
1798
1780
|
return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
|
|
1799
1781
|
}
|
|
1800
1782
|
return authEmail;
|
|
@@ -1926,14 +1908,25 @@ function getGoogleStore(store) {
|
|
|
1926
1908
|
oauthClients: store.collection("google.oauth_clients", ["client_id"]),
|
|
1927
1909
|
messages: store.collection("google.messages", ["gmail_id", "thread_id", "user_email"]),
|
|
1928
1910
|
drafts: store.collection("google.drafts", ["gmail_id", "message_gmail_id", "user_email"]),
|
|
1929
|
-
attachments: store.collection("google.attachments", [
|
|
1911
|
+
attachments: store.collection("google.attachments", [
|
|
1912
|
+
"gmail_id",
|
|
1913
|
+
"message_gmail_id",
|
|
1914
|
+
"user_email"
|
|
1915
|
+
]),
|
|
1930
1916
|
history: store.collection("google.history", ["gmail_id", "message_gmail_id", "user_email"]),
|
|
1931
1917
|
labels: store.collection("google.labels", ["gmail_id", "user_email", "name"]),
|
|
1932
1918
|
filters: store.collection("google.filters", ["gmail_id", "user_email"]),
|
|
1933
|
-
forwardingAddresses: store.collection("google.forwarding_addresses", [
|
|
1919
|
+
forwardingAddresses: store.collection("google.forwarding_addresses", [
|
|
1920
|
+
"user_email",
|
|
1921
|
+
"forwarding_email"
|
|
1922
|
+
]),
|
|
1934
1923
|
sendAs: store.collection("google.send_as", ["user_email", "send_as_email"]),
|
|
1935
1924
|
calendars: store.collection("google.calendars", ["google_id", "user_email"]),
|
|
1936
|
-
calendarEvents: store.collection("google.calendar_events", [
|
|
1925
|
+
calendarEvents: store.collection("google.calendar_events", [
|
|
1926
|
+
"google_id",
|
|
1927
|
+
"calendar_google_id",
|
|
1928
|
+
"user_email"
|
|
1929
|
+
]),
|
|
1937
1930
|
driveItems: store.collection("google.drive_items", ["google_id", "user_email", "mime_type"])
|
|
1938
1931
|
};
|
|
1939
1932
|
}
|
|
@@ -2039,13 +2032,7 @@ function draftRoutes({ app, store }) {
|
|
|
2039
2032
|
});
|
|
2040
2033
|
return c.json(formatDraftResource(gs, draft, "full"));
|
|
2041
2034
|
} catch {
|
|
2042
|
-
return googleApiError(
|
|
2043
|
-
c,
|
|
2044
|
-
400,
|
|
2045
|
-
"Invalid raw MIME message payload.",
|
|
2046
|
-
"invalidArgument",
|
|
2047
|
-
"INVALID_ARGUMENT"
|
|
2048
|
-
);
|
|
2035
|
+
return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
|
|
2049
2036
|
}
|
|
2050
2037
|
};
|
|
2051
2038
|
const sendHandler = async (c) => {
|
|
@@ -2135,13 +2122,7 @@ function draftRoutes({ app, store }) {
|
|
|
2135
2122
|
}
|
|
2136
2123
|
return c.json(formatDraftResource(gs, updated.draft, "full"));
|
|
2137
2124
|
} catch {
|
|
2138
|
-
return googleApiError(
|
|
2139
|
-
c,
|
|
2140
|
-
400,
|
|
2141
|
-
"Invalid raw MIME message payload.",
|
|
2142
|
-
"invalidArgument",
|
|
2143
|
-
"INVALID_ARGUMENT"
|
|
2144
|
-
);
|
|
2125
|
+
return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
|
|
2145
2126
|
}
|
|
2146
2127
|
});
|
|
2147
2128
|
app.post("/gmail/v1/users/:userId/drafts/send", sendHandler);
|
|
@@ -2279,7 +2260,13 @@ function historyRoutes({ app, store }) {
|
|
|
2279
2260
|
const labelIds = getStringArray(body, "labelIds");
|
|
2280
2261
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, labelIds);
|
|
2281
2262
|
if (missingLabelIds.length > 0) {
|
|
2282
|
-
return googleApiError(
|
|
2263
|
+
return googleApiError(
|
|
2264
|
+
c,
|
|
2265
|
+
400,
|
|
2266
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2267
|
+
"invalidArgument",
|
|
2268
|
+
"INVALID_ARGUMENT"
|
|
2269
|
+
);
|
|
2283
2270
|
}
|
|
2284
2271
|
const expiration = String(Date.now() + 24 * 60 * 60 * 1e3);
|
|
2285
2272
|
const states = store.getData(WATCH_STATE_KEY) ?? /* @__PURE__ */ new Map();
|
|
@@ -2333,13 +2320,7 @@ function labelRoutes({ app, store }) {
|
|
|
2333
2320
|
return googleApiError(c, 400, "Invalid label name", "invalidArgument", "INVALID_ARGUMENT");
|
|
2334
2321
|
}
|
|
2335
2322
|
if (findLabelByName(gs, authEmail, name)) {
|
|
2336
|
-
return googleApiError(
|
|
2337
|
-
c,
|
|
2338
|
-
400,
|
|
2339
|
-
"Label name exists or conflicts",
|
|
2340
|
-
"failedPrecondition",
|
|
2341
|
-
"FAILED_PRECONDITION"
|
|
2342
|
-
);
|
|
2323
|
+
return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
|
|
2343
2324
|
}
|
|
2344
2325
|
const color = body.color && typeof body.color === "object" && !Array.isArray(body.color) ? body.color : void 0;
|
|
2345
2326
|
const label = createLabelRecord(gs, {
|
|
@@ -2397,13 +2378,7 @@ async function saveLabel(c, gs, replaceMissingFields) {
|
|
|
2397
2378
|
if (name) {
|
|
2398
2379
|
const conflicting = findLabelByName(gs, authEmail, name);
|
|
2399
2380
|
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
|
-
);
|
|
2381
|
+
return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
|
|
2407
2382
|
}
|
|
2408
2383
|
}
|
|
2409
2384
|
const updated = updateLabelRecord(gs, label, {
|
|
@@ -2427,7 +2402,13 @@ function messageRoutes({ app, store }) {
|
|
|
2427
2402
|
const defaultLabelIds = mode === "send" ? dedupeLabelIds([...labelIds, "SENT"]) : labelIds.length > 0 ? labelIds : mode === "import" ? ["INBOX", "UNREAD"] : [];
|
|
2428
2403
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, defaultLabelIds);
|
|
2429
2404
|
if (missingLabelIds.length > 0) {
|
|
2430
|
-
return googleApiError(
|
|
2405
|
+
return googleApiError(
|
|
2406
|
+
c,
|
|
2407
|
+
400,
|
|
2408
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2409
|
+
"invalidArgument",
|
|
2410
|
+
"INVALID_ARGUMENT"
|
|
2411
|
+
);
|
|
2431
2412
|
}
|
|
2432
2413
|
const messageInput = parseMessageInputFromBody(body, {
|
|
2433
2414
|
from: mode === "send" ? authEmail : void 0
|
|
@@ -2449,13 +2430,7 @@ function messageRoutes({ app, store }) {
|
|
|
2449
2430
|
});
|
|
2450
2431
|
return c.json(formatMessageResource(gs, message, "full"));
|
|
2451
2432
|
} catch {
|
|
2452
|
-
return googleApiError(
|
|
2453
|
-
c,
|
|
2454
|
-
400,
|
|
2455
|
-
"Invalid raw MIME message payload.",
|
|
2456
|
-
"invalidArgument",
|
|
2457
|
-
"INVALID_ARGUMENT"
|
|
2458
|
-
);
|
|
2433
|
+
return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
|
|
2459
2434
|
}
|
|
2460
2435
|
};
|
|
2461
2436
|
app.get("/gmail/v1/users/:userId/messages", (c) => {
|
|
@@ -2489,16 +2464,18 @@ function messageRoutes({ app, store }) {
|
|
|
2489
2464
|
const removeLabelIds = getStringArray(body, "removeLabelIds");
|
|
2490
2465
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
2491
2466
|
if (missingLabelIds.length > 0) {
|
|
2492
|
-
return googleApiError(
|
|
2467
|
+
return googleApiError(
|
|
2468
|
+
c,
|
|
2469
|
+
400,
|
|
2470
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2471
|
+
"invalidArgument",
|
|
2472
|
+
"INVALID_ARGUMENT"
|
|
2473
|
+
);
|
|
2493
2474
|
}
|
|
2494
2475
|
for (const messageId of ids) {
|
|
2495
2476
|
const message = getMessageById(gs, authEmail, messageId);
|
|
2496
2477
|
if (!message) continue;
|
|
2497
|
-
markMessageModified(
|
|
2498
|
-
gs,
|
|
2499
|
-
message,
|
|
2500
|
-
applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
|
|
2501
|
-
);
|
|
2478
|
+
markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds));
|
|
2502
2479
|
}
|
|
2503
2480
|
return c.body(null, 204);
|
|
2504
2481
|
});
|
|
@@ -2565,7 +2542,13 @@ function messageRoutes({ app, store }) {
|
|
|
2565
2542
|
const removeLabelIds = getStringArray(body, "removeLabelIds");
|
|
2566
2543
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
2567
2544
|
if (missingLabelIds.length > 0) {
|
|
2568
|
-
return googleApiError(
|
|
2545
|
+
return googleApiError(
|
|
2546
|
+
c,
|
|
2547
|
+
400,
|
|
2548
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
2549
|
+
"invalidArgument",
|
|
2550
|
+
"INVALID_ARGUMENT"
|
|
2551
|
+
);
|
|
2569
2552
|
}
|
|
2570
2553
|
const updated = markMessageModified(
|
|
2571
2554
|
gs,
|
|
@@ -2581,7 +2564,9 @@ function messageRoutes({ app, store }) {
|
|
|
2581
2564
|
if (!message) {
|
|
2582
2565
|
return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
|
|
2583
2566
|
}
|
|
2584
|
-
return c.json(
|
|
2567
|
+
return c.json(
|
|
2568
|
+
formatMessageResource(gs, markMessageModified(gs, message, trashLabelIds(message.label_ids)), "full")
|
|
2569
|
+
);
|
|
2585
2570
|
});
|
|
2586
2571
|
app.post("/gmail/v1/users/:userId/messages/:id/untrash", (c) => {
|
|
2587
2572
|
const authEmail = requireGmailUser(c);
|
|
@@ -2590,7 +2575,9 @@ function messageRoutes({ app, store }) {
|
|
|
2590
2575
|
if (!message) {
|
|
2591
2576
|
return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
|
|
2592
2577
|
}
|
|
2593
|
-
return c.json(
|
|
2578
|
+
return c.json(
|
|
2579
|
+
formatMessageResource(gs, markMessageModified(gs, message, untrashLabelIds(message.label_ids)), "full")
|
|
2580
|
+
);
|
|
2594
2581
|
});
|
|
2595
2582
|
app.delete("/gmail/v1/users/:userId/messages/:id", (c) => {
|
|
2596
2583
|
const authEmail = requireGmailUser(c);
|
|
@@ -2636,6 +2623,7 @@ var FONTS = {
|
|
|
2636
2623
|
"geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
|
|
2637
2624
|
"GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
|
|
2638
2625
|
};
|
|
2626
|
+
var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
|
|
2639
2627
|
function escapeHtml(s) {
|
|
2640
2628
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2641
2629
|
}
|
|
@@ -2787,6 +2775,132 @@ body{
|
|
|
2787
2775
|
.app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
|
|
2788
2776
|
.app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
|
|
2789
2777
|
.empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
|
|
2778
|
+
|
|
2779
|
+
.inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
|
|
2780
|
+
.inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
|
|
2781
|
+
.inspector-tabs a{
|
|
2782
|
+
padding:7px 16px;border-radius:6px;text-decoration:none;
|
|
2783
|
+
font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
|
|
2784
|
+
transition:color .15s,border-color .15s;
|
|
2785
|
+
}
|
|
2786
|
+
.inspector-tabs a:hover{color:#33ff00;}
|
|
2787
|
+
.inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
|
|
2788
|
+
.inspector-section{margin-bottom:24px;}
|
|
2789
|
+
.inspector-section h2{
|
|
2790
|
+
font-family:'Geist Pixel',monospace;
|
|
2791
|
+
font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
|
|
2792
|
+
}
|
|
2793
|
+
.inspector-section h3{
|
|
2794
|
+
font-family:'Geist Pixel',monospace;
|
|
2795
|
+
font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
|
|
2796
|
+
}
|
|
2797
|
+
.inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
|
|
2798
|
+
.inspector-table th,.inspector-table td{
|
|
2799
|
+
text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
|
|
2800
|
+
font-size:.8125rem;
|
|
2801
|
+
}
|
|
2802
|
+
.inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
|
|
2803
|
+
.inspector-table td{color:#33ff00;}
|
|
2804
|
+
.inspector-table tbody tr{transition:background .1s;}
|
|
2805
|
+
.inspector-table tbody tr:hover{background:#0a3300;}
|
|
2806
|
+
.inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
|
|
2807
|
+
|
|
2808
|
+
.checkout-layout{
|
|
2809
|
+
display:flex;min-height:calc(100vh - 42px);
|
|
2810
|
+
}
|
|
2811
|
+
.checkout-summary{
|
|
2812
|
+
flex:1;background:#020;padding:48px 40px 48px 10%;
|
|
2813
|
+
display:flex;flex-direction:column;justify-content:center;
|
|
2814
|
+
border-right:1px solid #0a3300;
|
|
2815
|
+
}
|
|
2816
|
+
.checkout-form-side{
|
|
2817
|
+
flex:1;background:#000;padding:48px 10% 48px 40px;
|
|
2818
|
+
display:flex;flex-direction:column;justify-content:center;
|
|
2819
|
+
}
|
|
2820
|
+
.checkout-merchant{
|
|
2821
|
+
display:flex;align-items:center;gap:10px;margin-bottom:6px;
|
|
2822
|
+
}
|
|
2823
|
+
.checkout-merchant-name{
|
|
2824
|
+
font-family:'Geist Pixel',monospace;
|
|
2825
|
+
font-size:.9375rem;font-weight:600;color:#33ff00;
|
|
2826
|
+
}
|
|
2827
|
+
.checkout-test-badge{
|
|
2828
|
+
font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
|
|
2829
|
+
background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
|
|
2830
|
+
}
|
|
2831
|
+
.checkout-total{
|
|
2832
|
+
font-family:'Geist Pixel',monospace;
|
|
2833
|
+
font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
|
|
2834
|
+
}
|
|
2835
|
+
.checkout-line-item{
|
|
2836
|
+
display:flex;align-items:center;gap:14px;padding:14px 0;
|
|
2837
|
+
border-bottom:1px solid #0a3300;
|
|
2838
|
+
}
|
|
2839
|
+
.checkout-line-item:first-child{border-top:1px solid #0a3300;}
|
|
2840
|
+
.checkout-item-icon{
|
|
2841
|
+
width:42px;height:42px;border-radius:6px;background:#0a3300;
|
|
2842
|
+
display:flex;align-items:center;justify-content:center;flex-shrink:0;
|
|
2843
|
+
font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
|
|
2844
|
+
}
|
|
2845
|
+
.checkout-item-details{flex:1;min-width:0;}
|
|
2846
|
+
.checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
|
|
2847
|
+
.checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
|
|
2848
|
+
.checkout-item-price{
|
|
2849
|
+
font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
|
|
2850
|
+
}
|
|
2851
|
+
.checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
|
|
2852
|
+
.checkout-totals{margin-top:20px;}
|
|
2853
|
+
.checkout-totals-row{
|
|
2854
|
+
display:flex;justify-content:space-between;padding:6px 0;
|
|
2855
|
+
font-size:.8125rem;color:#1a8c00;
|
|
2856
|
+
}
|
|
2857
|
+
.checkout-totals-row.total{
|
|
2858
|
+
border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
|
|
2859
|
+
font-size:.9375rem;font-weight:600;color:#33ff00;
|
|
2860
|
+
}
|
|
2861
|
+
.checkout-form-section{margin-bottom:24px;}
|
|
2862
|
+
.checkout-form-label{
|
|
2863
|
+
font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
|
|
2864
|
+
}
|
|
2865
|
+
.checkout-input{
|
|
2866
|
+
width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
|
|
2867
|
+
background:#020;color:#33ff00;font:inherit;font-size:.875rem;
|
|
2868
|
+
transition:border-color .15s;outline:none;
|
|
2869
|
+
}
|
|
2870
|
+
.checkout-input:focus{border-color:#33ff00;}
|
|
2871
|
+
.checkout-input::placeholder{color:#116600;}
|
|
2872
|
+
.checkout-card-box{
|
|
2873
|
+
border:1px solid #0a3300;border-radius:6px;padding:14px;
|
|
2874
|
+
background:#020;
|
|
2875
|
+
}
|
|
2876
|
+
.checkout-card-row{
|
|
2877
|
+
display:flex;gap:12px;margin-top:10px;
|
|
2878
|
+
}
|
|
2879
|
+
.checkout-card-row .checkout-input{flex:1;}
|
|
2880
|
+
.checkout-sim-note{
|
|
2881
|
+
font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
|
|
2882
|
+
font-style:italic;
|
|
2883
|
+
}
|
|
2884
|
+
.checkout-pay-btn{
|
|
2885
|
+
width:100%;padding:14px;border:none;border-radius:8px;
|
|
2886
|
+
background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
|
|
2887
|
+
cursor:pointer;transition:background .15s;
|
|
2888
|
+
font-family:'Geist Pixel',monospace;
|
|
2889
|
+
}
|
|
2890
|
+
.checkout-pay-btn:hover{background:#44ff22;}
|
|
2891
|
+
.checkout-cancel{
|
|
2892
|
+
text-align:center;margin-top:14px;
|
|
2893
|
+
}
|
|
2894
|
+
.checkout-cancel a{
|
|
2895
|
+
color:#1a8c00;text-decoration:none;font-size:.8125rem;
|
|
2896
|
+
transition:color .15s;
|
|
2897
|
+
}
|
|
2898
|
+
.checkout-cancel a:hover{color:#33ff00;}
|
|
2899
|
+
@media(max-width:768px){
|
|
2900
|
+
.checkout-layout{flex-direction:column;}
|
|
2901
|
+
.checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
|
|
2902
|
+
.checkout-form-side{padding:32px 20px;}
|
|
2903
|
+
}
|
|
2790
2904
|
`;
|
|
2791
2905
|
var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
|
|
2792
2906
|
function emuBar(service) {
|
|
@@ -2806,6 +2920,7 @@ function head(title) {
|
|
|
2806
2920
|
<head>
|
|
2807
2921
|
<meta charset="utf-8"/>
|
|
2808
2922
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
2923
|
+
<link rel="icon" href="/_emulate/favicon.ico"/>
|
|
2809
2924
|
<title>${escapeHtml(title)} | emulate</title>
|
|
2810
2925
|
<style>${CSS}</style>
|
|
2811
2926
|
</head>`;
|
|
@@ -2909,6 +3024,7 @@ async function createIdToken(user, clientId, nonce, baseUrl) {
|
|
|
2909
3024
|
family_name: user.family_name,
|
|
2910
3025
|
picture: user.picture,
|
|
2911
3026
|
locale: user.locale,
|
|
3027
|
+
...user.hd ? { hd: user.hd } : {},
|
|
2912
3028
|
...nonce ? { nonce } : {}
|
|
2913
3029
|
}).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuer(baseUrl).setAudience(clientId).setIssuedAt().setExpirationTime("1h");
|
|
2914
3030
|
return builder.sign(JWT_SECRET);
|
|
@@ -2936,7 +3052,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
2936
3052
|
"given_name",
|
|
2937
3053
|
"family_name",
|
|
2938
3054
|
"picture",
|
|
2939
|
-
"locale"
|
|
3055
|
+
"locale",
|
|
3056
|
+
"hd"
|
|
2940
3057
|
],
|
|
2941
3058
|
code_challenge_methods_supported: ["plain", "S256"]
|
|
2942
3059
|
});
|
|
@@ -2964,7 +3081,11 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
2964
3081
|
}
|
|
2965
3082
|
if (redirect_uri && !matchesRedirectUri(redirect_uri, client.redirect_uris)) {
|
|
2966
3083
|
return c.html(
|
|
2967
|
-
renderErrorPage(
|
|
3084
|
+
renderErrorPage(
|
|
3085
|
+
"Redirect URI mismatch",
|
|
3086
|
+
"The redirect_uri is not registered for this application.",
|
|
3087
|
+
SERVICE_LABEL
|
|
3088
|
+
),
|
|
2968
3089
|
400
|
|
2969
3090
|
);
|
|
2970
3091
|
}
|
|
@@ -3076,7 +3197,13 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
3076
3197
|
});
|
|
3077
3198
|
}
|
|
3078
3199
|
if (grant_type !== "authorization_code") {
|
|
3079
|
-
return c.json(
|
|
3200
|
+
return c.json(
|
|
3201
|
+
{
|
|
3202
|
+
error: "unsupported_grant_type",
|
|
3203
|
+
error_description: "Only authorization_code and refresh_token are supported."
|
|
3204
|
+
},
|
|
3205
|
+
400
|
|
3206
|
+
);
|
|
3080
3207
|
}
|
|
3081
3208
|
const pendingMap = getPendingCodes(store);
|
|
3082
3209
|
const pending = pendingMap.get(code);
|
|
@@ -3149,7 +3276,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
|
|
|
3149
3276
|
given_name: user.given_name,
|
|
3150
3277
|
family_name: user.family_name,
|
|
3151
3278
|
picture: user.picture,
|
|
3152
|
-
locale: user.locale
|
|
3279
|
+
locale: user.locale,
|
|
3280
|
+
...user.hd ? { hd: user.hd } : {}
|
|
3153
3281
|
});
|
|
3154
3282
|
});
|
|
3155
3283
|
app.post("/oauth2/revoke", async (c) => {
|
|
@@ -3201,7 +3329,13 @@ function settingsRoutes({ app, store }) {
|
|
|
3201
3329
|
}
|
|
3202
3330
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
3203
3331
|
if (missingLabelIds.length > 0) {
|
|
3204
|
-
return googleApiError(
|
|
3332
|
+
return googleApiError(
|
|
3333
|
+
c,
|
|
3334
|
+
400,
|
|
3335
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
3336
|
+
"invalidArgument",
|
|
3337
|
+
"INVALID_ARGUMENT"
|
|
3338
|
+
);
|
|
3205
3339
|
}
|
|
3206
3340
|
if (findMatchingFilter(gs, {
|
|
3207
3341
|
user_email: authEmail,
|
|
@@ -3306,14 +3440,16 @@ function threadRoutes({ app, store }) {
|
|
|
3306
3440
|
const removeLabelIds = getStringArray(body, "removeLabelIds");
|
|
3307
3441
|
const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
|
|
3308
3442
|
if (missingLabelIds.length > 0) {
|
|
3309
|
-
return googleApiError(
|
|
3443
|
+
return googleApiError(
|
|
3444
|
+
c,
|
|
3445
|
+
400,
|
|
3446
|
+
`Invalid label IDs: ${missingLabelIds.join(", ")}`,
|
|
3447
|
+
"invalidArgument",
|
|
3448
|
+
"INVALID_ARGUMENT"
|
|
3449
|
+
);
|
|
3310
3450
|
}
|
|
3311
3451
|
const updated = messages.map(
|
|
3312
|
-
(message) => markMessageModified(
|
|
3313
|
-
gs,
|
|
3314
|
-
message,
|
|
3315
|
-
applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
|
|
3316
|
-
)
|
|
3452
|
+
(message) => markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds))
|
|
3317
3453
|
);
|
|
3318
3454
|
return c.json(formatThreadResource(gs, updated, "full"));
|
|
3319
3455
|
});
|
|
@@ -3364,120 +3500,148 @@ function seedDefaults(store, _baseUrl) {
|
|
|
3364
3500
|
family_name: "User",
|
|
3365
3501
|
picture: null,
|
|
3366
3502
|
email_verified: true,
|
|
3367
|
-
locale: "en"
|
|
3503
|
+
locale: "en",
|
|
3504
|
+
hd: null
|
|
3368
3505
|
});
|
|
3369
3506
|
}
|
|
3370
3507
|
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
|
-
|
|
3508
|
+
seedCalendars(
|
|
3509
|
+
store,
|
|
3510
|
+
[
|
|
3511
|
+
{
|
|
3512
|
+
id: "primary",
|
|
3513
|
+
user_email: defaultEmail,
|
|
3514
|
+
summary: defaultEmail,
|
|
3515
|
+
primary: true,
|
|
3516
|
+
selected: true,
|
|
3517
|
+
time_zone: "UTC"
|
|
3518
|
+
},
|
|
3519
|
+
{
|
|
3520
|
+
id: "cal_team",
|
|
3521
|
+
user_email: defaultEmail,
|
|
3522
|
+
summary: "Team Calendar",
|
|
3523
|
+
description: "Shared team events",
|
|
3524
|
+
selected: true,
|
|
3525
|
+
time_zone: "UTC"
|
|
3526
|
+
}
|
|
3527
|
+
],
|
|
3528
|
+
defaultEmail
|
|
3529
|
+
);
|
|
3530
|
+
seedCalendarEvents(
|
|
3531
|
+
store,
|
|
3532
|
+
[
|
|
3533
|
+
{
|
|
3534
|
+
id: "evt_standup",
|
|
3535
|
+
user_email: defaultEmail,
|
|
3536
|
+
calendar_id: "primary",
|
|
3537
|
+
summary: "Daily Standup",
|
|
3538
|
+
description: "Team sync",
|
|
3539
|
+
start_date_time: new Date(Date.now() + 60 * 60 * 1e3).toISOString(),
|
|
3540
|
+
end_date_time: new Date(Date.now() + 90 * 60 * 1e3).toISOString(),
|
|
3541
|
+
attendees: [
|
|
3542
|
+
{ email: defaultEmail, display_name: "Test User" },
|
|
3543
|
+
{ email: "teammate@example.com", display_name: "Teammate" }
|
|
3544
|
+
],
|
|
3545
|
+
conference_entry_points: [
|
|
3546
|
+
{
|
|
3547
|
+
entry_point_type: "video",
|
|
3548
|
+
uri: "https://meet.google.com/emulate-standup",
|
|
3549
|
+
label: "Google Meet"
|
|
3550
|
+
}
|
|
3551
|
+
],
|
|
3552
|
+
hangout_link: "https://meet.google.com/emulate-standup"
|
|
3553
|
+
}
|
|
3554
|
+
],
|
|
3555
|
+
defaultEmail
|
|
3556
|
+
);
|
|
3557
|
+
seedDriveItems(
|
|
3558
|
+
store,
|
|
3559
|
+
[
|
|
3560
|
+
{
|
|
3561
|
+
id: "drv_root_receipts",
|
|
3562
|
+
user_email: defaultEmail,
|
|
3563
|
+
name: "Receipts",
|
|
3564
|
+
mime_type: "application/vnd.google-apps.folder",
|
|
3565
|
+
parent_ids: ["root"]
|
|
3566
|
+
},
|
|
3567
|
+
{
|
|
3568
|
+
id: "drv_receipt_pdf",
|
|
3569
|
+
user_email: defaultEmail,
|
|
3570
|
+
name: "March Receipt.pdf",
|
|
3571
|
+
mime_type: "application/pdf",
|
|
3572
|
+
parent_ids: ["drv_root_receipts"],
|
|
3573
|
+
data: "receipt-pdf-data"
|
|
3574
|
+
}
|
|
3575
|
+
],
|
|
3576
|
+
defaultEmail
|
|
3577
|
+
);
|
|
3578
|
+
seedMessages(
|
|
3579
|
+
store,
|
|
3580
|
+
[
|
|
3581
|
+
{
|
|
3582
|
+
id: "msg_welcome",
|
|
3583
|
+
thread_id: "thr_welcome",
|
|
3584
|
+
user_email: defaultEmail,
|
|
3585
|
+
from: "Welcome Team <welcome@example.com>",
|
|
3586
|
+
to: defaultEmail,
|
|
3587
|
+
subject: "Welcome to your local Gmail emulator",
|
|
3588
|
+
snippet: "Your OAuth flow is set up and Gmail message, thread, and label APIs are ready.",
|
|
3589
|
+
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.",
|
|
3590
|
+
label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
|
|
3591
|
+
date: new Date(Date.now() - 60 * 60 * 1e3).toISOString()
|
|
3592
|
+
},
|
|
3593
|
+
{
|
|
3594
|
+
id: "msg_build",
|
|
3595
|
+
thread_id: "thr_build",
|
|
3596
|
+
user_email: defaultEmail,
|
|
3597
|
+
from: "Build Bot <builds@example.com>",
|
|
3598
|
+
to: defaultEmail,
|
|
3599
|
+
subject: "Nightly build finished successfully",
|
|
3600
|
+
snippet: "The latest build completed successfully in 6 minutes.",
|
|
3601
|
+
body_text: "The latest build completed successfully in 6 minutes.\n\nArtifact upload finished and smoke checks passed.",
|
|
3602
|
+
label_ids: ["INBOX", "CATEGORY_UPDATES"],
|
|
3603
|
+
date: new Date(Date.now() - 2 * 60 * 60 * 1e3).toISOString()
|
|
3604
|
+
},
|
|
3605
|
+
{
|
|
3606
|
+
id: "msg_build_reply",
|
|
3607
|
+
thread_id: "thr_build",
|
|
3608
|
+
user_email: defaultEmail,
|
|
3609
|
+
from: defaultEmail,
|
|
3610
|
+
to: "Build Bot <builds@example.com>",
|
|
3611
|
+
subject: "Re: Nightly build finished successfully",
|
|
3612
|
+
snippet: "Thanks, I will review the artifact after lunch.",
|
|
3613
|
+
body_text: "Thanks, I will review the artifact after lunch.",
|
|
3614
|
+
label_ids: ["SENT"],
|
|
3615
|
+
date: new Date(Date.now() - 90 * 60 * 1e3).toISOString(),
|
|
3616
|
+
in_reply_to: "<msg_build@emulate.google.local>",
|
|
3617
|
+
references: "<msg_build@emulate.google.local>"
|
|
3618
|
+
},
|
|
3619
|
+
{
|
|
3620
|
+
id: "msg_draft",
|
|
3621
|
+
thread_id: "thr_draft",
|
|
3622
|
+
user_email: defaultEmail,
|
|
3623
|
+
from: defaultEmail,
|
|
3624
|
+
to: "someone@example.com",
|
|
3625
|
+
subject: "Draft follow-up",
|
|
3626
|
+
snippet: "Checking in on the open question from yesterday.",
|
|
3627
|
+
body_text: "Checking in on the open question from yesterday.",
|
|
3628
|
+
label_ids: ["DRAFT"],
|
|
3629
|
+
date: new Date(Date.now() - 30 * 60 * 1e3).toISOString()
|
|
3630
|
+
}
|
|
3631
|
+
],
|
|
3632
|
+
defaultEmail
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
var CONSUMER_EMAIL_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
|
|
3636
|
+
function deriveHd(email) {
|
|
3637
|
+
const domain = email.split("@")[1]?.toLowerCase();
|
|
3638
|
+
if (!domain) return null;
|
|
3639
|
+
if (CONSUMER_EMAIL_DOMAINS.has(domain)) return null;
|
|
3640
|
+
return domain;
|
|
3641
|
+
}
|
|
3642
|
+
function resolveHd(user) {
|
|
3643
|
+
if (user.hd !== void 0) return user.hd || null;
|
|
3644
|
+
return deriveHd(user.email);
|
|
3481
3645
|
}
|
|
3482
3646
|
function seedFromConfig(store, _baseUrl, config) {
|
|
3483
3647
|
const gs = getGoogleStore(store);
|
|
@@ -3494,7 +3658,8 @@ function seedFromConfig(store, _baseUrl, config) {
|
|
|
3494
3658
|
family_name: user.family_name ?? nameParts.slice(1).join(" "),
|
|
3495
3659
|
picture: user.picture ?? null,
|
|
3496
3660
|
email_verified: user.email_verified ?? true,
|
|
3497
|
-
locale: user.locale ?? "en"
|
|
3661
|
+
locale: user.locale ?? "en",
|
|
3662
|
+
hd: resolveHd(user)
|
|
3498
3663
|
});
|
|
3499
3664
|
}
|
|
3500
3665
|
ensureSystemLabels(gs, user.email);
|
|
@@ -3555,29 +3720,33 @@ function seedMessages(store, messages, fallbackEmail) {
|
|
|
3555
3720
|
const userEmail = message.user_email ?? fallbackEmail;
|
|
3556
3721
|
ensureSystemLabels(gs, userEmail);
|
|
3557
3722
|
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
|
-
|
|
3723
|
+
createStoredMessage(
|
|
3724
|
+
gs,
|
|
3725
|
+
{
|
|
3726
|
+
gmail_id: message.id,
|
|
3727
|
+
thread_id: message.thread_id,
|
|
3728
|
+
user_email: userEmail,
|
|
3729
|
+
raw: message.raw ?? null,
|
|
3730
|
+
from: message.from,
|
|
3731
|
+
to: message.to,
|
|
3732
|
+
cc: message.cc ?? null,
|
|
3733
|
+
bcc: message.bcc ?? null,
|
|
3734
|
+
reply_to: message.reply_to ?? null,
|
|
3735
|
+
subject: message.subject,
|
|
3736
|
+
snippet: message.snippet,
|
|
3737
|
+
body_text: message.body_text ?? null,
|
|
3738
|
+
body_html: message.body_html ?? null,
|
|
3739
|
+
label_ids: message.label_ids ?? ["INBOX", "UNREAD"],
|
|
3740
|
+
date: message.date,
|
|
3741
|
+
internal_date: message.internal_date,
|
|
3742
|
+
message_id: message.message_id,
|
|
3743
|
+
references: message.references ?? null,
|
|
3744
|
+
in_reply_to: message.in_reply_to ?? null
|
|
3745
|
+
},
|
|
3746
|
+
{
|
|
3747
|
+
createMissingCustomLabels: true
|
|
3748
|
+
}
|
|
3749
|
+
);
|
|
3581
3750
|
}
|
|
3582
3751
|
}
|
|
3583
3752
|
function seedCalendars(store, calendars, fallbackEmail) {
|