emulate 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.
Files changed (50) hide show
  1. package/README.md +59 -10
  2. package/dist/api.d.ts +2 -1
  3. package/dist/api.js +232 -96
  4. package/dist/api.js.map +1 -1
  5. package/dist/{chunk-TEPNEZ63.js → chunk-AQ2CLRU3.js} +26 -23
  6. package/dist/chunk-AQ2CLRU3.js.map +1 -0
  7. package/dist/chunk-WVQMFHQM.js +83 -0
  8. package/dist/chunk-WVQMFHQM.js.map +1 -0
  9. package/dist/{dist-RDFBZ5O6.js → dist-4X2KPMAJ.js} +212 -47
  10. package/dist/dist-4X2KPMAJ.js.map +1 -0
  11. package/dist/{dist-OTJZRQ3Q.js → dist-5JVGPOL3.js} +217 -75
  12. package/dist/dist-5JVGPOL3.js.map +1 -0
  13. package/dist/{dist-G7WQPZ3Y.js → dist-CE6BUCWQ.js} +211 -60
  14. package/dist/dist-CE6BUCWQ.js.map +1 -0
  15. package/dist/{dist-6JFNJPUU.js → dist-CFST4X4K.js} +172 -22
  16. package/dist/dist-CFST4X4K.js.map +1 -0
  17. package/dist/{dist-YOVM5HEY.js → dist-ENKE2S7V.js} +521 -60
  18. package/dist/dist-ENKE2S7V.js.map +1 -0
  19. package/dist/{dist-RMK3BS5M.js → dist-ETHHYBGF.js} +197 -33
  20. package/dist/dist-ETHHYBGF.js.map +1 -0
  21. package/dist/{dist-QMOJM6DV.js → dist-IBXD3O6A.js} +239 -54
  22. package/dist/dist-IBXD3O6A.js.map +1 -0
  23. package/dist/dist-J6LHUR52.js +1899 -0
  24. package/dist/dist-J6LHUR52.js.map +1 -0
  25. package/dist/{dist-6EW7SSOZ.js → dist-KKTYBE5S.js} +391 -222
  26. package/dist/dist-KKTYBE5S.js.map +1 -0
  27. package/dist/{dist-VVXVP5EZ.js → dist-LDUHEJAN.js} +553 -91
  28. package/dist/dist-LDUHEJAN.js.map +1 -0
  29. package/dist/{dist-B674PYKV.js → dist-PWGOAQC6.js} +22 -43
  30. package/dist/dist-PWGOAQC6.js.map +1 -0
  31. package/dist/{dist-H6JYGQM4.js → dist-REDHDZ3V.js} +272 -157
  32. package/dist/dist-REDHDZ3V.js.map +1 -0
  33. package/dist/fonts/favicon.ico +0 -0
  34. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  35. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  36. package/dist/index.js +365 -108
  37. package/dist/index.js.map +1 -1
  38. package/package.json +17 -14
  39. package/dist/chunk-TEPNEZ63.js.map +0 -1
  40. package/dist/dist-6EW7SSOZ.js.map +0 -1
  41. package/dist/dist-6JFNJPUU.js.map +0 -1
  42. package/dist/dist-B674PYKV.js.map +0 -1
  43. package/dist/dist-G7WQPZ3Y.js.map +0 -1
  44. package/dist/dist-H6JYGQM4.js.map +0 -1
  45. package/dist/dist-OTJZRQ3Q.js.map +0 -1
  46. package/dist/dist-QMOJM6DV.js.map +0 -1
  47. package/dist/dist-RDFBZ5O6.js.map +0 -1
  48. package/dist/dist-RMK3BS5M.js.map +0 -1
  49. package/dist/dist-VVXVP5EZ.js.map +0 -1
  50. package/dist/dist-YOVM5HEY.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SignJWT
3
3
  } from "./chunk-D6EKRYGP.js";
4
- import "./chunk-TEPNEZ63.js";
4
+ import "./chunk-AQ2CLRU3.js";
5
5
 
6
6
  // ../@emulators/google/dist/index.js
7
7
  import { randomBytes } from "crypto";
@@ -134,9 +134,7 @@ function parseBooleanParam(value) {
134
134
  return value === "true" || value === "1";
135
135
  }
136
136
  function ensureSystemLabels(gs, userEmail) {
137
- const existingIds = new Set(
138
- gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id)
139
- );
137
+ const existingIds = new Set(gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id));
140
138
  for (const label of SYSTEM_LABELS) {
141
139
  if (existingIds.has(label.gmail_id)) continue;
142
140
  gs.labels.insert({
@@ -481,9 +479,7 @@ function getCurrentHistoryId(gs, userEmail) {
481
479
  ...gs.history.findBy("user_email", userEmail).map((event) => event.gmail_id)
482
480
  ].filter(Boolean);
483
481
  if (historyIds.length === 0) return "0";
484
- return historyIds.reduce(
485
- (latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest
486
- );
482
+ return historyIds.reduce((latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest);
487
483
  }
488
484
  function listHistoryForUser(gs, userEmail, options) {
489
485
  const requestedTypes = options.historyTypes?.length ? new Set(options.historyTypes) : null;
@@ -1066,10 +1062,7 @@ function buildPayload(gs, message, headers, format) {
1066
1062
  filename: "",
1067
1063
  headers,
1068
1064
  body: { size: 0 },
1069
- parts: [
1070
- createTextBodyPart("0", "text/plain", textBody),
1071
- createTextBodyPart("1", "text/html", htmlBody)
1072
- ]
1065
+ parts: [createTextBodyPart("0", "text/plain", textBody), createTextBodyPart("1", "text/html", htmlBody)]
1073
1066
  };
1074
1067
  }
1075
1068
  if (htmlBody) return createTextBodyPart("", "text/html", htmlBody, headers);
@@ -1090,10 +1083,7 @@ function buildPayload(gs, message, headers, format) {
1090
1083
  filename: "",
1091
1084
  headers: [],
1092
1085
  body: { size: 0 },
1093
- parts: [
1094
- createTextBodyPart("0.0", "text/plain", textBody),
1095
- createTextBodyPart("0.1", "text/html", htmlBody)
1096
- ]
1086
+ parts: [createTextBodyPart("0.0", "text/plain", textBody), createTextBodyPart("0.1", "text/html", htmlBody)]
1097
1087
  });
1098
1088
  } else if (htmlBody) {
1099
1089
  parts.push(createTextBodyPart("0", "text/html", htmlBody));
@@ -1191,18 +1181,10 @@ function buildMimeBodyPart(input) {
1191
1181
  ].join("\r\n");
1192
1182
  }
1193
1183
  if (input.body_html) {
1194
- return [
1195
- "Content-Type: text/html; charset=utf-8",
1196
- "",
1197
- input.body_html
1198
- ].join("\r\n");
1184
+ return ["Content-Type: text/html; charset=utf-8", "", input.body_html].join("\r\n");
1199
1185
  }
1200
1186
  if (input.body_text) {
1201
- return [
1202
- "Content-Type: text/plain; charset=utf-8",
1203
- "",
1204
- input.body_text
1205
- ].join("\r\n");
1187
+ return ["Content-Type: text/plain; charset=utf-8", "", input.body_text].join("\r\n");
1206
1188
  }
1207
1189
  return null;
1208
1190
  }
@@ -1798,7 +1780,7 @@ function requireGmailUser(c) {
1798
1780
  if (authEmail instanceof Response) {
1799
1781
  return authEmail;
1800
1782
  }
1801
- if (!matchesRequestedUser(c.req.param("userId"), authEmail)) {
1783
+ if (!matchesRequestedUser(c.req.param("userId") ?? "", authEmail)) {
1802
1784
  return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
1803
1785
  }
1804
1786
  return authEmail;
@@ -1928,14 +1910,25 @@ function getGoogleStore(store) {
1928
1910
  oauthClients: store.collection("google.oauth_clients", ["client_id"]),
1929
1911
  messages: store.collection("google.messages", ["gmail_id", "thread_id", "user_email"]),
1930
1912
  drafts: store.collection("google.drafts", ["gmail_id", "message_gmail_id", "user_email"]),
1931
- attachments: store.collection("google.attachments", ["gmail_id", "message_gmail_id", "user_email"]),
1913
+ attachments: store.collection("google.attachments", [
1914
+ "gmail_id",
1915
+ "message_gmail_id",
1916
+ "user_email"
1917
+ ]),
1932
1918
  history: store.collection("google.history", ["gmail_id", "message_gmail_id", "user_email"]),
1933
1919
  labels: store.collection("google.labels", ["gmail_id", "user_email", "name"]),
1934
1920
  filters: store.collection("google.filters", ["gmail_id", "user_email"]),
1935
- forwardingAddresses: store.collection("google.forwarding_addresses", ["user_email", "forwarding_email"]),
1921
+ forwardingAddresses: store.collection("google.forwarding_addresses", [
1922
+ "user_email",
1923
+ "forwarding_email"
1924
+ ]),
1936
1925
  sendAs: store.collection("google.send_as", ["user_email", "send_as_email"]),
1937
1926
  calendars: store.collection("google.calendars", ["google_id", "user_email"]),
1938
- calendarEvents: store.collection("google.calendar_events", ["google_id", "calendar_google_id", "user_email"]),
1927
+ calendarEvents: store.collection("google.calendar_events", [
1928
+ "google_id",
1929
+ "calendar_google_id",
1930
+ "user_email"
1931
+ ]),
1939
1932
  driveItems: store.collection("google.drive_items", ["google_id", "user_email", "mime_type"])
1940
1933
  };
1941
1934
  }
@@ -2037,13 +2030,7 @@ function draftRoutes({ app, store }) {
2037
2030
  });
2038
2031
  return c.json(formatDraftResource(gs, draft, "full"));
2039
2032
  } catch {
2040
- return googleApiError(
2041
- c,
2042
- 400,
2043
- "Invalid raw MIME message payload.",
2044
- "invalidArgument",
2045
- "INVALID_ARGUMENT"
2046
- );
2033
+ return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
2047
2034
  }
2048
2035
  };
2049
2036
  const sendHandler = async (c) => {
@@ -2133,13 +2120,7 @@ function draftRoutes({ app, store }) {
2133
2120
  }
2134
2121
  return c.json(formatDraftResource(gs, updated.draft, "full"));
2135
2122
  } catch {
2136
- return googleApiError(
2137
- c,
2138
- 400,
2139
- "Invalid raw MIME message payload.",
2140
- "invalidArgument",
2141
- "INVALID_ARGUMENT"
2142
- );
2123
+ return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
2143
2124
  }
2144
2125
  });
2145
2126
  app.post("/gmail/v1/users/:userId/drafts/send", sendHandler);
@@ -2273,7 +2254,13 @@ function historyRoutes({ app, store }) {
2273
2254
  const labelIds = getStringArray(body, "labelIds");
2274
2255
  const missingLabelIds = findMissingLabelIds(gs, authEmail, labelIds);
2275
2256
  if (missingLabelIds.length > 0) {
2276
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2257
+ return googleApiError(
2258
+ c,
2259
+ 400,
2260
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2261
+ "invalidArgument",
2262
+ "INVALID_ARGUMENT"
2263
+ );
2277
2264
  }
2278
2265
  const expiration = String(Date.now() + 24 * 60 * 60 * 1e3);
2279
2266
  const states = store.getData(WATCH_STATE_KEY) ?? /* @__PURE__ */ new Map();
@@ -2325,13 +2312,7 @@ function labelRoutes({ app, store }) {
2325
2312
  return googleApiError(c, 400, "Invalid label name", "invalidArgument", "INVALID_ARGUMENT");
2326
2313
  }
2327
2314
  if (findLabelByName(gs, authEmail, name)) {
2328
- return googleApiError(
2329
- c,
2330
- 400,
2331
- "Label name exists or conflicts",
2332
- "failedPrecondition",
2333
- "FAILED_PRECONDITION"
2334
- );
2315
+ return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
2335
2316
  }
2336
2317
  const color = body.color && typeof body.color === "object" && !Array.isArray(body.color) ? body.color : void 0;
2337
2318
  const label = createLabelRecord(gs, {
@@ -2389,13 +2370,7 @@ async function saveLabel(c, gs, replaceMissingFields) {
2389
2370
  if (name) {
2390
2371
  const conflicting = findLabelByName(gs, authEmail, name);
2391
2372
  if (conflicting && conflicting.gmail_id !== label.gmail_id) {
2392
- return googleApiError(
2393
- c,
2394
- 400,
2395
- "Label name exists or conflicts",
2396
- "failedPrecondition",
2397
- "FAILED_PRECONDITION"
2398
- );
2373
+ return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
2399
2374
  }
2400
2375
  }
2401
2376
  const updated = updateLabelRecord(gs, label, {
@@ -2417,7 +2392,13 @@ function messageRoutes({ app, store }) {
2417
2392
  const defaultLabelIds = mode === "send" ? dedupeLabelIds([...labelIds, "SENT"]) : labelIds.length > 0 ? labelIds : mode === "import" ? ["INBOX", "UNREAD"] : [];
2418
2393
  const missingLabelIds = findMissingLabelIds(gs, authEmail, defaultLabelIds);
2419
2394
  if (missingLabelIds.length > 0) {
2420
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2395
+ return googleApiError(
2396
+ c,
2397
+ 400,
2398
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2399
+ "invalidArgument",
2400
+ "INVALID_ARGUMENT"
2401
+ );
2421
2402
  }
2422
2403
  const messageInput = parseMessageInputFromBody(body, {
2423
2404
  from: mode === "send" ? authEmail : void 0
@@ -2439,13 +2420,7 @@ function messageRoutes({ app, store }) {
2439
2420
  });
2440
2421
  return c.json(formatMessageResource(gs, message, "full"));
2441
2422
  } catch {
2442
- return googleApiError(
2443
- c,
2444
- 400,
2445
- "Invalid raw MIME message payload.",
2446
- "invalidArgument",
2447
- "INVALID_ARGUMENT"
2448
- );
2423
+ return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
2449
2424
  }
2450
2425
  };
2451
2426
  app.get("/gmail/v1/users/:userId/messages", (c) => {
@@ -2479,16 +2454,18 @@ function messageRoutes({ app, store }) {
2479
2454
  const removeLabelIds = getStringArray(body, "removeLabelIds");
2480
2455
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
2481
2456
  if (missingLabelIds.length > 0) {
2482
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2457
+ return googleApiError(
2458
+ c,
2459
+ 400,
2460
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2461
+ "invalidArgument",
2462
+ "INVALID_ARGUMENT"
2463
+ );
2483
2464
  }
2484
2465
  for (const messageId of ids) {
2485
2466
  const message = getMessageById(gs, authEmail, messageId);
2486
2467
  if (!message) continue;
2487
- markMessageModified(
2488
- gs,
2489
- message,
2490
- applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
2491
- );
2468
+ markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds));
2492
2469
  }
2493
2470
  return c.body(null, 204);
2494
2471
  });
@@ -2555,7 +2532,13 @@ function messageRoutes({ app, store }) {
2555
2532
  const removeLabelIds = getStringArray(body, "removeLabelIds");
2556
2533
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
2557
2534
  if (missingLabelIds.length > 0) {
2558
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2535
+ return googleApiError(
2536
+ c,
2537
+ 400,
2538
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2539
+ "invalidArgument",
2540
+ "INVALID_ARGUMENT"
2541
+ );
2559
2542
  }
2560
2543
  const updated = markMessageModified(
2561
2544
  gs,
@@ -2571,7 +2554,9 @@ function messageRoutes({ app, store }) {
2571
2554
  if (!message) {
2572
2555
  return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
2573
2556
  }
2574
- return c.json(formatMessageResource(gs, markMessageModified(gs, message, trashLabelIds(message.label_ids)), "full"));
2557
+ return c.json(
2558
+ formatMessageResource(gs, markMessageModified(gs, message, trashLabelIds(message.label_ids)), "full")
2559
+ );
2575
2560
  });
2576
2561
  app.post("/gmail/v1/users/:userId/messages/:id/untrash", (c) => {
2577
2562
  const authEmail = requireGmailUser(c);
@@ -2580,7 +2565,9 @@ function messageRoutes({ app, store }) {
2580
2565
  if (!message) {
2581
2566
  return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
2582
2567
  }
2583
- return c.json(formatMessageResource(gs, markMessageModified(gs, message, untrashLabelIds(message.label_ids)), "full"));
2568
+ return c.json(
2569
+ formatMessageResource(gs, markMessageModified(gs, message, untrashLabelIds(message.label_ids)), "full")
2570
+ );
2584
2571
  });
2585
2572
  app.delete("/gmail/v1/users/:userId/messages/:id", (c) => {
2586
2573
  const authEmail = requireGmailUser(c);
@@ -2613,6 +2600,7 @@ var FONTS = {
2613
2600
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
2614
2601
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
2615
2602
  };
2603
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
2616
2604
  function escapeHtml(s) {
2617
2605
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2618
2606
  }
@@ -2764,6 +2752,132 @@ body{
2764
2752
  .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
2765
2753
  .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
2766
2754
  .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
2755
+
2756
+ .inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
2757
+ .inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
2758
+ .inspector-tabs a{
2759
+ padding:7px 16px;border-radius:6px;text-decoration:none;
2760
+ font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
2761
+ transition:color .15s,border-color .15s;
2762
+ }
2763
+ .inspector-tabs a:hover{color:#33ff00;}
2764
+ .inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
2765
+ .inspector-section{margin-bottom:24px;}
2766
+ .inspector-section h2{
2767
+ font-family:'Geist Pixel',monospace;
2768
+ font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
2769
+ }
2770
+ .inspector-section h3{
2771
+ font-family:'Geist Pixel',monospace;
2772
+ font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
2773
+ }
2774
+ .inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
2775
+ .inspector-table th,.inspector-table td{
2776
+ text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
2777
+ font-size:.8125rem;
2778
+ }
2779
+ .inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
2780
+ .inspector-table td{color:#33ff00;}
2781
+ .inspector-table tbody tr{transition:background .1s;}
2782
+ .inspector-table tbody tr:hover{background:#0a3300;}
2783
+ .inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
2784
+
2785
+ .checkout-layout{
2786
+ display:flex;min-height:calc(100vh - 42px);
2787
+ }
2788
+ .checkout-summary{
2789
+ flex:1;background:#020;padding:48px 40px 48px 10%;
2790
+ display:flex;flex-direction:column;justify-content:center;
2791
+ border-right:1px solid #0a3300;
2792
+ }
2793
+ .checkout-form-side{
2794
+ flex:1;background:#000;padding:48px 10% 48px 40px;
2795
+ display:flex;flex-direction:column;justify-content:center;
2796
+ }
2797
+ .checkout-merchant{
2798
+ display:flex;align-items:center;gap:10px;margin-bottom:6px;
2799
+ }
2800
+ .checkout-merchant-name{
2801
+ font-family:'Geist Pixel',monospace;
2802
+ font-size:.9375rem;font-weight:600;color:#33ff00;
2803
+ }
2804
+ .checkout-test-badge{
2805
+ font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
2806
+ background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
2807
+ }
2808
+ .checkout-total{
2809
+ font-family:'Geist Pixel',monospace;
2810
+ font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
2811
+ }
2812
+ .checkout-line-item{
2813
+ display:flex;align-items:center;gap:14px;padding:14px 0;
2814
+ border-bottom:1px solid #0a3300;
2815
+ }
2816
+ .checkout-line-item:first-child{border-top:1px solid #0a3300;}
2817
+ .checkout-item-icon{
2818
+ width:42px;height:42px;border-radius:6px;background:#0a3300;
2819
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
2820
+ font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
2821
+ }
2822
+ .checkout-item-details{flex:1;min-width:0;}
2823
+ .checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
2824
+ .checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
2825
+ .checkout-item-price{
2826
+ font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
2827
+ }
2828
+ .checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
2829
+ .checkout-totals{margin-top:20px;}
2830
+ .checkout-totals-row{
2831
+ display:flex;justify-content:space-between;padding:6px 0;
2832
+ font-size:.8125rem;color:#1a8c00;
2833
+ }
2834
+ .checkout-totals-row.total{
2835
+ border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
2836
+ font-size:.9375rem;font-weight:600;color:#33ff00;
2837
+ }
2838
+ .checkout-form-section{margin-bottom:24px;}
2839
+ .checkout-form-label{
2840
+ font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
2841
+ }
2842
+ .checkout-input{
2843
+ width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
2844
+ background:#020;color:#33ff00;font:inherit;font-size:.875rem;
2845
+ transition:border-color .15s;outline:none;
2846
+ }
2847
+ .checkout-input:focus{border-color:#33ff00;}
2848
+ .checkout-input::placeholder{color:#116600;}
2849
+ .checkout-card-box{
2850
+ border:1px solid #0a3300;border-radius:6px;padding:14px;
2851
+ background:#020;
2852
+ }
2853
+ .checkout-card-row{
2854
+ display:flex;gap:12px;margin-top:10px;
2855
+ }
2856
+ .checkout-card-row .checkout-input{flex:1;}
2857
+ .checkout-sim-note{
2858
+ font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
2859
+ font-style:italic;
2860
+ }
2861
+ .checkout-pay-btn{
2862
+ width:100%;padding:14px;border:none;border-radius:8px;
2863
+ background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
2864
+ cursor:pointer;transition:background .15s;
2865
+ font-family:'Geist Pixel',monospace;
2866
+ }
2867
+ .checkout-pay-btn:hover{background:#44ff22;}
2868
+ .checkout-cancel{
2869
+ text-align:center;margin-top:14px;
2870
+ }
2871
+ .checkout-cancel a{
2872
+ color:#1a8c00;text-decoration:none;font-size:.8125rem;
2873
+ transition:color .15s;
2874
+ }
2875
+ .checkout-cancel a:hover{color:#33ff00;}
2876
+ @media(max-width:768px){
2877
+ .checkout-layout{flex-direction:column;}
2878
+ .checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
2879
+ .checkout-form-side{padding:32px 20px;}
2880
+ }
2767
2881
  `;
2768
2882
  var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
2769
2883
  function emuBar(service) {
@@ -2783,6 +2897,7 @@ function head(title) {
2783
2897
  <head>
2784
2898
  <meta charset="utf-8"/>
2785
2899
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
2900
+ <link rel="icon" href="/_emulate/favicon.ico"/>
2786
2901
  <title>${escapeHtml(title)} | emulate</title>
2787
2902
  <style>${CSS}</style>
2788
2903
  </head>`;
@@ -2884,6 +2999,7 @@ async function createIdToken(user, clientId, nonce, baseUrl) {
2884
2999
  family_name: user.family_name,
2885
3000
  picture: user.picture,
2886
3001
  locale: user.locale,
3002
+ ...user.hd ? { hd: user.hd } : {},
2887
3003
  ...nonce ? { nonce } : {}
2888
3004
  }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuer(baseUrl).setAudience(clientId).setIssuedAt().setExpirationTime("1h");
2889
3005
  return builder.sign(JWT_SECRET);
@@ -2911,7 +3027,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
2911
3027
  "given_name",
2912
3028
  "family_name",
2913
3029
  "picture",
2914
- "locale"
3030
+ "locale",
3031
+ "hd"
2915
3032
  ],
2916
3033
  code_challenge_methods_supported: ["plain", "S256"]
2917
3034
  });
@@ -2939,7 +3056,11 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
2939
3056
  }
2940
3057
  if (redirect_uri && !matchesRedirectUri(redirect_uri, client.redirect_uris)) {
2941
3058
  return c.html(
2942
- renderErrorPage("Redirect URI mismatch", "The redirect_uri is not registered for this application.", SERVICE_LABEL),
3059
+ renderErrorPage(
3060
+ "Redirect URI mismatch",
3061
+ "The redirect_uri is not registered for this application.",
3062
+ SERVICE_LABEL
3063
+ ),
2943
3064
  400
2944
3065
  );
2945
3066
  }
@@ -3051,7 +3172,13 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
3051
3172
  });
3052
3173
  }
3053
3174
  if (grant_type !== "authorization_code") {
3054
- return c.json({ error: "unsupported_grant_type", error_description: "Only authorization_code and refresh_token are supported." }, 400);
3175
+ return c.json(
3176
+ {
3177
+ error: "unsupported_grant_type",
3178
+ error_description: "Only authorization_code and refresh_token are supported."
3179
+ },
3180
+ 400
3181
+ );
3055
3182
  }
3056
3183
  const pendingMap = getPendingCodes(store);
3057
3184
  const pending = pendingMap.get(code);
@@ -3124,7 +3251,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
3124
3251
  given_name: user.given_name,
3125
3252
  family_name: user.family_name,
3126
3253
  picture: user.picture,
3127
- locale: user.locale
3254
+ locale: user.locale,
3255
+ ...user.hd ? { hd: user.hd } : {}
3128
3256
  });
3129
3257
  });
3130
3258
  app.post("/oauth2/revoke", async (c) => {
@@ -3174,7 +3302,13 @@ function settingsRoutes({ app, store }) {
3174
3302
  }
3175
3303
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
3176
3304
  if (missingLabelIds.length > 0) {
3177
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
3305
+ return googleApiError(
3306
+ c,
3307
+ 400,
3308
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
3309
+ "invalidArgument",
3310
+ "INVALID_ARGUMENT"
3311
+ );
3178
3312
  }
3179
3313
  if (findMatchingFilter(gs, {
3180
3314
  user_email: authEmail,
@@ -3277,14 +3411,16 @@ function threadRoutes({ app, store }) {
3277
3411
  const removeLabelIds = getStringArray(body, "removeLabelIds");
3278
3412
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
3279
3413
  if (missingLabelIds.length > 0) {
3280
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
3414
+ return googleApiError(
3415
+ c,
3416
+ 400,
3417
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
3418
+ "invalidArgument",
3419
+ "INVALID_ARGUMENT"
3420
+ );
3281
3421
  }
3282
3422
  const updated = messages.map(
3283
- (message) => markMessageModified(
3284
- gs,
3285
- message,
3286
- applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
3287
- )
3423
+ (message) => markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds))
3288
3424
  );
3289
3425
  return c.json(formatThreadResource(gs, updated, "full"));
3290
3426
  });
@@ -3333,120 +3469,148 @@ function seedDefaults(store, _baseUrl) {
3333
3469
  family_name: "User",
3334
3470
  picture: null,
3335
3471
  email_verified: true,
3336
- locale: "en"
3472
+ locale: "en",
3473
+ hd: null
3337
3474
  });
3338
3475
  }
3339
3476
  ensureSystemLabels(gs, defaultEmail);
3340
- seedCalendars(store, [
3341
- {
3342
- id: "primary",
3343
- user_email: defaultEmail,
3344
- summary: defaultEmail,
3345
- primary: true,
3346
- selected: true,
3347
- time_zone: "UTC"
3348
- },
3349
- {
3350
- id: "cal_team",
3351
- user_email: defaultEmail,
3352
- summary: "Team Calendar",
3353
- description: "Shared team events",
3354
- selected: true,
3355
- time_zone: "UTC"
3356
- }
3357
- ], defaultEmail);
3358
- seedCalendarEvents(store, [
3359
- {
3360
- id: "evt_standup",
3361
- user_email: defaultEmail,
3362
- calendar_id: "primary",
3363
- summary: "Daily Standup",
3364
- description: "Team sync",
3365
- start_date_time: new Date(Date.now() + 60 * 60 * 1e3).toISOString(),
3366
- end_date_time: new Date(Date.now() + 90 * 60 * 1e3).toISOString(),
3367
- attendees: [
3368
- { email: defaultEmail, display_name: "Test User" },
3369
- { email: "teammate@example.com", display_name: "Teammate" }
3370
- ],
3371
- conference_entry_points: [
3372
- {
3373
- entry_point_type: "video",
3374
- uri: "https://meet.google.com/emulate-standup",
3375
- label: "Google Meet"
3376
- }
3377
- ],
3378
- hangout_link: "https://meet.google.com/emulate-standup"
3379
- }
3380
- ], defaultEmail);
3381
- seedDriveItems(store, [
3382
- {
3383
- id: "drv_root_receipts",
3384
- user_email: defaultEmail,
3385
- name: "Receipts",
3386
- mime_type: "application/vnd.google-apps.folder",
3387
- parent_ids: ["root"]
3388
- },
3389
- {
3390
- id: "drv_receipt_pdf",
3391
- user_email: defaultEmail,
3392
- name: "March Receipt.pdf",
3393
- mime_type: "application/pdf",
3394
- parent_ids: ["drv_root_receipts"],
3395
- data: "receipt-pdf-data"
3396
- }
3397
- ], defaultEmail);
3398
- seedMessages(store, [
3399
- {
3400
- id: "msg_welcome",
3401
- thread_id: "thr_welcome",
3402
- user_email: defaultEmail,
3403
- from: "Welcome Team <welcome@example.com>",
3404
- to: defaultEmail,
3405
- subject: "Welcome to your local Gmail emulator",
3406
- snippet: "Your OAuth flow is set up and Gmail message, thread, and label APIs are ready.",
3407
- 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.",
3408
- label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
3409
- date: new Date(Date.now() - 60 * 60 * 1e3).toISOString()
3410
- },
3411
- {
3412
- id: "msg_build",
3413
- thread_id: "thr_build",
3414
- user_email: defaultEmail,
3415
- from: "Build Bot <builds@example.com>",
3416
- to: defaultEmail,
3417
- subject: "Nightly build finished successfully",
3418
- snippet: "The latest build completed successfully in 6 minutes.",
3419
- body_text: "The latest build completed successfully in 6 minutes.\n\nArtifact upload finished and smoke checks passed.",
3420
- label_ids: ["INBOX", "CATEGORY_UPDATES"],
3421
- date: new Date(Date.now() - 2 * 60 * 60 * 1e3).toISOString()
3422
- },
3423
- {
3424
- id: "msg_build_reply",
3425
- thread_id: "thr_build",
3426
- user_email: defaultEmail,
3427
- from: defaultEmail,
3428
- to: "Build Bot <builds@example.com>",
3429
- subject: "Re: Nightly build finished successfully",
3430
- snippet: "Thanks, I will review the artifact after lunch.",
3431
- body_text: "Thanks, I will review the artifact after lunch.",
3432
- label_ids: ["SENT"],
3433
- date: new Date(Date.now() - 90 * 60 * 1e3).toISOString(),
3434
- in_reply_to: "<msg_build@emulate.google.local>",
3435
- references: "<msg_build@emulate.google.local>"
3436
- },
3437
- {
3438
- id: "msg_draft",
3439
- thread_id: "thr_draft",
3440
- user_email: defaultEmail,
3441
- from: defaultEmail,
3442
- to: "someone@example.com",
3443
- subject: "Draft follow-up",
3444
- snippet: "Checking in on the open question from yesterday.",
3445
- body_text: "Checking in on the open question from yesterday.",
3446
- label_ids: ["DRAFT"],
3447
- date: new Date(Date.now() - 30 * 60 * 1e3).toISOString()
3448
- }
3449
- ], defaultEmail);
3477
+ seedCalendars(
3478
+ store,
3479
+ [
3480
+ {
3481
+ id: "primary",
3482
+ user_email: defaultEmail,
3483
+ summary: defaultEmail,
3484
+ primary: true,
3485
+ selected: true,
3486
+ time_zone: "UTC"
3487
+ },
3488
+ {
3489
+ id: "cal_team",
3490
+ user_email: defaultEmail,
3491
+ summary: "Team Calendar",
3492
+ description: "Shared team events",
3493
+ selected: true,
3494
+ time_zone: "UTC"
3495
+ }
3496
+ ],
3497
+ defaultEmail
3498
+ );
3499
+ seedCalendarEvents(
3500
+ store,
3501
+ [
3502
+ {
3503
+ id: "evt_standup",
3504
+ user_email: defaultEmail,
3505
+ calendar_id: "primary",
3506
+ summary: "Daily Standup",
3507
+ description: "Team sync",
3508
+ start_date_time: new Date(Date.now() + 60 * 60 * 1e3).toISOString(),
3509
+ end_date_time: new Date(Date.now() + 90 * 60 * 1e3).toISOString(),
3510
+ attendees: [
3511
+ { email: defaultEmail, display_name: "Test User" },
3512
+ { email: "teammate@example.com", display_name: "Teammate" }
3513
+ ],
3514
+ conference_entry_points: [
3515
+ {
3516
+ entry_point_type: "video",
3517
+ uri: "https://meet.google.com/emulate-standup",
3518
+ label: "Google Meet"
3519
+ }
3520
+ ],
3521
+ hangout_link: "https://meet.google.com/emulate-standup"
3522
+ }
3523
+ ],
3524
+ defaultEmail
3525
+ );
3526
+ seedDriveItems(
3527
+ store,
3528
+ [
3529
+ {
3530
+ id: "drv_root_receipts",
3531
+ user_email: defaultEmail,
3532
+ name: "Receipts",
3533
+ mime_type: "application/vnd.google-apps.folder",
3534
+ parent_ids: ["root"]
3535
+ },
3536
+ {
3537
+ id: "drv_receipt_pdf",
3538
+ user_email: defaultEmail,
3539
+ name: "March Receipt.pdf",
3540
+ mime_type: "application/pdf",
3541
+ parent_ids: ["drv_root_receipts"],
3542
+ data: "receipt-pdf-data"
3543
+ }
3544
+ ],
3545
+ defaultEmail
3546
+ );
3547
+ seedMessages(
3548
+ store,
3549
+ [
3550
+ {
3551
+ id: "msg_welcome",
3552
+ thread_id: "thr_welcome",
3553
+ user_email: defaultEmail,
3554
+ from: "Welcome Team <welcome@example.com>",
3555
+ to: defaultEmail,
3556
+ subject: "Welcome to your local Gmail emulator",
3557
+ snippet: "Your OAuth flow is set up and Gmail message, thread, and label APIs are ready.",
3558
+ 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.",
3559
+ label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
3560
+ date: new Date(Date.now() - 60 * 60 * 1e3).toISOString()
3561
+ },
3562
+ {
3563
+ id: "msg_build",
3564
+ thread_id: "thr_build",
3565
+ user_email: defaultEmail,
3566
+ from: "Build Bot <builds@example.com>",
3567
+ to: defaultEmail,
3568
+ subject: "Nightly build finished successfully",
3569
+ snippet: "The latest build completed successfully in 6 minutes.",
3570
+ body_text: "The latest build completed successfully in 6 minutes.\n\nArtifact upload finished and smoke checks passed.",
3571
+ label_ids: ["INBOX", "CATEGORY_UPDATES"],
3572
+ date: new Date(Date.now() - 2 * 60 * 60 * 1e3).toISOString()
3573
+ },
3574
+ {
3575
+ id: "msg_build_reply",
3576
+ thread_id: "thr_build",
3577
+ user_email: defaultEmail,
3578
+ from: defaultEmail,
3579
+ to: "Build Bot <builds@example.com>",
3580
+ subject: "Re: Nightly build finished successfully",
3581
+ snippet: "Thanks, I will review the artifact after lunch.",
3582
+ body_text: "Thanks, I will review the artifact after lunch.",
3583
+ label_ids: ["SENT"],
3584
+ date: new Date(Date.now() - 90 * 60 * 1e3).toISOString(),
3585
+ in_reply_to: "<msg_build@emulate.google.local>",
3586
+ references: "<msg_build@emulate.google.local>"
3587
+ },
3588
+ {
3589
+ id: "msg_draft",
3590
+ thread_id: "thr_draft",
3591
+ user_email: defaultEmail,
3592
+ from: defaultEmail,
3593
+ to: "someone@example.com",
3594
+ subject: "Draft follow-up",
3595
+ snippet: "Checking in on the open question from yesterday.",
3596
+ body_text: "Checking in on the open question from yesterday.",
3597
+ label_ids: ["DRAFT"],
3598
+ date: new Date(Date.now() - 30 * 60 * 1e3).toISOString()
3599
+ }
3600
+ ],
3601
+ defaultEmail
3602
+ );
3603
+ }
3604
+ var CONSUMER_EMAIL_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
3605
+ function deriveHd(email) {
3606
+ const domain = email.split("@")[1]?.toLowerCase();
3607
+ if (!domain) return null;
3608
+ if (CONSUMER_EMAIL_DOMAINS.has(domain)) return null;
3609
+ return domain;
3610
+ }
3611
+ function resolveHd(user) {
3612
+ if (user.hd !== void 0) return user.hd || null;
3613
+ return deriveHd(user.email);
3450
3614
  }
3451
3615
  function seedFromConfig(store, _baseUrl, config) {
3452
3616
  const gs = getGoogleStore(store);
@@ -3463,7 +3627,8 @@ function seedFromConfig(store, _baseUrl, config) {
3463
3627
  family_name: user.family_name ?? nameParts.slice(1).join(" "),
3464
3628
  picture: user.picture ?? null,
3465
3629
  email_verified: user.email_verified ?? true,
3466
- locale: user.locale ?? "en"
3630
+ locale: user.locale ?? "en",
3631
+ hd: resolveHd(user)
3467
3632
  });
3468
3633
  }
3469
3634
  ensureSystemLabels(gs, user.email);
@@ -3524,29 +3689,33 @@ function seedMessages(store, messages, fallbackEmail) {
3524
3689
  const userEmail = message.user_email ?? fallbackEmail;
3525
3690
  ensureSystemLabels(gs, userEmail);
3526
3691
  if (message.id && gs.messages.findOneBy("gmail_id", message.id)) continue;
3527
- createStoredMessage(gs, {
3528
- gmail_id: message.id,
3529
- thread_id: message.thread_id,
3530
- user_email: userEmail,
3531
- raw: message.raw ?? null,
3532
- from: message.from,
3533
- to: message.to,
3534
- cc: message.cc ?? null,
3535
- bcc: message.bcc ?? null,
3536
- reply_to: message.reply_to ?? null,
3537
- subject: message.subject,
3538
- snippet: message.snippet,
3539
- body_text: message.body_text ?? null,
3540
- body_html: message.body_html ?? null,
3541
- label_ids: message.label_ids ?? ["INBOX", "UNREAD"],
3542
- date: message.date,
3543
- internal_date: message.internal_date,
3544
- message_id: message.message_id,
3545
- references: message.references ?? null,
3546
- in_reply_to: message.in_reply_to ?? null
3547
- }, {
3548
- createMissingCustomLabels: true
3549
- });
3692
+ createStoredMessage(
3693
+ gs,
3694
+ {
3695
+ gmail_id: message.id,
3696
+ thread_id: message.thread_id,
3697
+ user_email: userEmail,
3698
+ raw: message.raw ?? null,
3699
+ from: message.from,
3700
+ to: message.to,
3701
+ cc: message.cc ?? null,
3702
+ bcc: message.bcc ?? null,
3703
+ reply_to: message.reply_to ?? null,
3704
+ subject: message.subject,
3705
+ snippet: message.snippet,
3706
+ body_text: message.body_text ?? null,
3707
+ body_html: message.body_html ?? null,
3708
+ label_ids: message.label_ids ?? ["INBOX", "UNREAD"],
3709
+ date: message.date,
3710
+ internal_date: message.internal_date,
3711
+ message_id: message.message_id,
3712
+ references: message.references ?? null,
3713
+ in_reply_to: message.in_reply_to ?? null
3714
+ },
3715
+ {
3716
+ createMissingCustomLabels: true
3717
+ }
3718
+ );
3550
3719
  }
3551
3720
  }
3552
3721
  function seedCalendars(store, calendars, fallbackEmail) {
@@ -3638,4 +3807,4 @@ export {
3638
3807
  googlePlugin,
3639
3808
  seedFromConfig
3640
3809
  };
3641
- //# sourceMappingURL=dist-6EW7SSOZ.js.map
3810
+ //# sourceMappingURL=dist-KKTYBE5S.js.map