emulate 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.
Files changed (50) hide show
  1. package/README.md +198 -27
  2. package/dist/api.d.ts +2 -1
  3. package/dist/api.js +675 -103
  4. package/dist/api.js.map +1 -1
  5. package/dist/chunk-WVQMFHQM.js +83 -0
  6. package/dist/chunk-WVQMFHQM.js.map +1 -0
  7. package/dist/{dist-B674PYKV.js → dist-2ZZGNPJI.js} +22 -43
  8. package/dist/dist-2ZZGNPJI.js.map +1 -0
  9. package/dist/{dist-RDFBZ5O6.js → dist-CXRPM6BK.js} +211 -48
  10. package/dist/dist-CXRPM6BK.js.map +1 -0
  11. package/dist/{dist-VVXVP5EZ.js → dist-DSJSF3GY.js} +551 -91
  12. package/dist/dist-DSJSF3GY.js.map +1 -0
  13. package/dist/{dist-RMK3BS5M.js → dist-IFULY5LE.js} +196 -33
  14. package/dist/dist-IFULY5LE.js.map +1 -0
  15. package/dist/dist-IRUBHCZU.js +1898 -0
  16. package/dist/dist-IRUBHCZU.js.map +1 -0
  17. package/dist/{dist-YOVM5HEY.js → dist-NJJLJT2N.js} +520 -61
  18. package/dist/dist-NJJLJT2N.js.map +1 -0
  19. package/dist/dist-OGSAVJ25.js +4874 -0
  20. package/dist/dist-OGSAVJ25.js.map +1 -0
  21. package/dist/{dist-H6JYGQM4.js → dist-PO4CL5SJ.js} +271 -158
  22. package/dist/dist-PO4CL5SJ.js.map +1 -0
  23. package/dist/{dist-QMOJM6DV.js → dist-R3TNKUIE.js} +238 -55
  24. package/dist/dist-R3TNKUIE.js.map +1 -0
  25. package/dist/{dist-6JFNJPUU.js → dist-WACHAAVU.js} +171 -22
  26. package/dist/dist-WACHAAVU.js.map +1 -0
  27. package/dist/{dist-OTJZRQ3Q.js → dist-XWWZVLQQ.js} +216 -75
  28. package/dist/dist-XWWZVLQQ.js.map +1 -0
  29. package/dist/{dist-6EW7SSOZ.js → dist-ZY5SZSJ2.js} +397 -223
  30. package/dist/dist-ZY5SZSJ2.js.map +1 -0
  31. package/dist/fonts/favicon.ico +0 -0
  32. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  33. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  34. package/dist/index.js +812 -117
  35. package/dist/index.js.map +1 -1
  36. package/package.json +17 -15
  37. package/dist/chunk-TEPNEZ63.js +0 -2143
  38. package/dist/chunk-TEPNEZ63.js.map +0 -1
  39. package/dist/dist-6EW7SSOZ.js.map +0 -1
  40. package/dist/dist-6JFNJPUU.js.map +0 -1
  41. package/dist/dist-B674PYKV.js.map +0 -1
  42. package/dist/dist-G7WQPZ3Y.js +0 -1287
  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,6 @@
1
1
  import {
2
2
  SignJWT
3
3
  } from "./chunk-D6EKRYGP.js";
4
- import "./chunk-TEPNEZ63.js";
5
4
 
6
5
  // ../@emulators/google/dist/index.js
7
6
  import { randomBytes } from "crypto";
@@ -76,6 +75,7 @@ var LABEL_ALIASES = {
76
75
  updates: "CATEGORY_UPDATES",
77
76
  forums: "CATEGORY_FORUMS"
78
77
  };
78
+ var lastGeneratedHistoryId = 0n;
79
79
  function generateUid(prefix = "") {
80
80
  const id = randomBytes(12).toString("base64url").slice(0, 20);
81
81
  return prefix ? `${prefix}_${id}` : id;
@@ -86,7 +86,12 @@ function generateDraftId() {
86
86
  }
87
87
  function generateHistoryId() {
88
88
  const entropy = randomBytes(3).readUIntBE(0, 3).toString().padStart(8, "0");
89
- return `${Date.now()}${entropy}`;
89
+ let next = BigInt(`${Date.now()}${entropy}`);
90
+ if (next <= lastGeneratedHistoryId) {
91
+ next = lastGeneratedHistoryId + 1n;
92
+ }
93
+ lastGeneratedHistoryId = next;
94
+ return next.toString();
90
95
  }
91
96
  function getAuthenticatedEmail(c) {
92
97
  const authUser = c.get("authUser");
@@ -134,9 +139,7 @@ function parseBooleanParam(value) {
134
139
  return value === "true" || value === "1";
135
140
  }
136
141
  function ensureSystemLabels(gs, userEmail) {
137
- const existingIds = new Set(
138
- gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id)
139
- );
142
+ const existingIds = new Set(gs.labels.findBy("user_email", userEmail).map((row) => row.gmail_id));
140
143
  for (const label of SYSTEM_LABELS) {
141
144
  if (existingIds.has(label.gmail_id)) continue;
142
145
  gs.labels.insert({
@@ -481,9 +484,7 @@ function getCurrentHistoryId(gs, userEmail) {
481
484
  ...gs.history.findBy("user_email", userEmail).map((event) => event.gmail_id)
482
485
  ].filter(Boolean);
483
486
  if (historyIds.length === 0) return "0";
484
- return historyIds.reduce(
485
- (latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest
486
- );
487
+ return historyIds.reduce((latest, current) => compareHistoryIds(current, latest) > 0 ? current : latest);
487
488
  }
488
489
  function listHistoryForUser(gs, userEmail, options) {
489
490
  const requestedTypes = options.historyTypes?.length ? new Set(options.historyTypes) : null;
@@ -1066,10 +1067,7 @@ function buildPayload(gs, message, headers, format) {
1066
1067
  filename: "",
1067
1068
  headers,
1068
1069
  body: { size: 0 },
1069
- parts: [
1070
- createTextBodyPart("0", "text/plain", textBody),
1071
- createTextBodyPart("1", "text/html", htmlBody)
1072
- ]
1070
+ parts: [createTextBodyPart("0", "text/plain", textBody), createTextBodyPart("1", "text/html", htmlBody)]
1073
1071
  };
1074
1072
  }
1075
1073
  if (htmlBody) return createTextBodyPart("", "text/html", htmlBody, headers);
@@ -1090,10 +1088,7 @@ function buildPayload(gs, message, headers, format) {
1090
1088
  filename: "",
1091
1089
  headers: [],
1092
1090
  body: { size: 0 },
1093
- parts: [
1094
- createTextBodyPart("0.0", "text/plain", textBody),
1095
- createTextBodyPart("0.1", "text/html", htmlBody)
1096
- ]
1091
+ parts: [createTextBodyPart("0.0", "text/plain", textBody), createTextBodyPart("0.1", "text/html", htmlBody)]
1097
1092
  });
1098
1093
  } else if (htmlBody) {
1099
1094
  parts.push(createTextBodyPart("0", "text/html", htmlBody));
@@ -1191,18 +1186,10 @@ function buildMimeBodyPart(input) {
1191
1186
  ].join("\r\n");
1192
1187
  }
1193
1188
  if (input.body_html) {
1194
- return [
1195
- "Content-Type: text/html; charset=utf-8",
1196
- "",
1197
- input.body_html
1198
- ].join("\r\n");
1189
+ return ["Content-Type: text/html; charset=utf-8", "", input.body_html].join("\r\n");
1199
1190
  }
1200
1191
  if (input.body_text) {
1201
- return [
1202
- "Content-Type: text/plain; charset=utf-8",
1203
- "",
1204
- input.body_text
1205
- ].join("\r\n");
1192
+ return ["Content-Type: text/plain; charset=utf-8", "", input.body_text].join("\r\n");
1206
1193
  }
1207
1194
  return null;
1208
1195
  }
@@ -1798,7 +1785,7 @@ function requireGmailUser(c) {
1798
1785
  if (authEmail instanceof Response) {
1799
1786
  return authEmail;
1800
1787
  }
1801
- if (!matchesRequestedUser(c.req.param("userId"), authEmail)) {
1788
+ if (!matchesRequestedUser(c.req.param("userId") ?? "", authEmail)) {
1802
1789
  return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
1803
1790
  }
1804
1791
  return authEmail;
@@ -1928,14 +1915,25 @@ function getGoogleStore(store) {
1928
1915
  oauthClients: store.collection("google.oauth_clients", ["client_id"]),
1929
1916
  messages: store.collection("google.messages", ["gmail_id", "thread_id", "user_email"]),
1930
1917
  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"]),
1918
+ attachments: store.collection("google.attachments", [
1919
+ "gmail_id",
1920
+ "message_gmail_id",
1921
+ "user_email"
1922
+ ]),
1932
1923
  history: store.collection("google.history", ["gmail_id", "message_gmail_id", "user_email"]),
1933
1924
  labels: store.collection("google.labels", ["gmail_id", "user_email", "name"]),
1934
1925
  filters: store.collection("google.filters", ["gmail_id", "user_email"]),
1935
- forwardingAddresses: store.collection("google.forwarding_addresses", ["user_email", "forwarding_email"]),
1926
+ forwardingAddresses: store.collection("google.forwarding_addresses", [
1927
+ "user_email",
1928
+ "forwarding_email"
1929
+ ]),
1936
1930
  sendAs: store.collection("google.send_as", ["user_email", "send_as_email"]),
1937
1931
  calendars: store.collection("google.calendars", ["google_id", "user_email"]),
1938
- calendarEvents: store.collection("google.calendar_events", ["google_id", "calendar_google_id", "user_email"]),
1932
+ calendarEvents: store.collection("google.calendar_events", [
1933
+ "google_id",
1934
+ "calendar_google_id",
1935
+ "user_email"
1936
+ ]),
1939
1937
  driveItems: store.collection("google.drive_items", ["google_id", "user_email", "mime_type"])
1940
1938
  };
1941
1939
  }
@@ -2037,13 +2035,7 @@ function draftRoutes({ app, store }) {
2037
2035
  });
2038
2036
  return c.json(formatDraftResource(gs, draft, "full"));
2039
2037
  } catch {
2040
- return googleApiError(
2041
- c,
2042
- 400,
2043
- "Invalid raw MIME message payload.",
2044
- "invalidArgument",
2045
- "INVALID_ARGUMENT"
2046
- );
2038
+ return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
2047
2039
  }
2048
2040
  };
2049
2041
  const sendHandler = async (c) => {
@@ -2133,13 +2125,7 @@ function draftRoutes({ app, store }) {
2133
2125
  }
2134
2126
  return c.json(formatDraftResource(gs, updated.draft, "full"));
2135
2127
  } catch {
2136
- return googleApiError(
2137
- c,
2138
- 400,
2139
- "Invalid raw MIME message payload.",
2140
- "invalidArgument",
2141
- "INVALID_ARGUMENT"
2142
- );
2128
+ return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
2143
2129
  }
2144
2130
  });
2145
2131
  app.post("/gmail/v1/users/:userId/drafts/send", sendHandler);
@@ -2273,7 +2259,13 @@ function historyRoutes({ app, store }) {
2273
2259
  const labelIds = getStringArray(body, "labelIds");
2274
2260
  const missingLabelIds = findMissingLabelIds(gs, authEmail, labelIds);
2275
2261
  if (missingLabelIds.length > 0) {
2276
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2262
+ return googleApiError(
2263
+ c,
2264
+ 400,
2265
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2266
+ "invalidArgument",
2267
+ "INVALID_ARGUMENT"
2268
+ );
2277
2269
  }
2278
2270
  const expiration = String(Date.now() + 24 * 60 * 60 * 1e3);
2279
2271
  const states = store.getData(WATCH_STATE_KEY) ?? /* @__PURE__ */ new Map();
@@ -2325,13 +2317,7 @@ function labelRoutes({ app, store }) {
2325
2317
  return googleApiError(c, 400, "Invalid label name", "invalidArgument", "INVALID_ARGUMENT");
2326
2318
  }
2327
2319
  if (findLabelByName(gs, authEmail, name)) {
2328
- return googleApiError(
2329
- c,
2330
- 400,
2331
- "Label name exists or conflicts",
2332
- "failedPrecondition",
2333
- "FAILED_PRECONDITION"
2334
- );
2320
+ return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
2335
2321
  }
2336
2322
  const color = body.color && typeof body.color === "object" && !Array.isArray(body.color) ? body.color : void 0;
2337
2323
  const label = createLabelRecord(gs, {
@@ -2389,13 +2375,7 @@ async function saveLabel(c, gs, replaceMissingFields) {
2389
2375
  if (name) {
2390
2376
  const conflicting = findLabelByName(gs, authEmail, name);
2391
2377
  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
- );
2378
+ return googleApiError(c, 400, "Label name exists or conflicts", "failedPrecondition", "FAILED_PRECONDITION");
2399
2379
  }
2400
2380
  }
2401
2381
  const updated = updateLabelRecord(gs, label, {
@@ -2417,7 +2397,13 @@ function messageRoutes({ app, store }) {
2417
2397
  const defaultLabelIds = mode === "send" ? dedupeLabelIds([...labelIds, "SENT"]) : labelIds.length > 0 ? labelIds : mode === "import" ? ["INBOX", "UNREAD"] : [];
2418
2398
  const missingLabelIds = findMissingLabelIds(gs, authEmail, defaultLabelIds);
2419
2399
  if (missingLabelIds.length > 0) {
2420
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2400
+ return googleApiError(
2401
+ c,
2402
+ 400,
2403
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2404
+ "invalidArgument",
2405
+ "INVALID_ARGUMENT"
2406
+ );
2421
2407
  }
2422
2408
  const messageInput = parseMessageInputFromBody(body, {
2423
2409
  from: mode === "send" ? authEmail : void 0
@@ -2439,13 +2425,7 @@ function messageRoutes({ app, store }) {
2439
2425
  });
2440
2426
  return c.json(formatMessageResource(gs, message, "full"));
2441
2427
  } catch {
2442
- return googleApiError(
2443
- c,
2444
- 400,
2445
- "Invalid raw MIME message payload.",
2446
- "invalidArgument",
2447
- "INVALID_ARGUMENT"
2448
- );
2428
+ return googleApiError(c, 400, "Invalid raw MIME message payload.", "invalidArgument", "INVALID_ARGUMENT");
2449
2429
  }
2450
2430
  };
2451
2431
  app.get("/gmail/v1/users/:userId/messages", (c) => {
@@ -2479,16 +2459,18 @@ function messageRoutes({ app, store }) {
2479
2459
  const removeLabelIds = getStringArray(body, "removeLabelIds");
2480
2460
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
2481
2461
  if (missingLabelIds.length > 0) {
2482
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2462
+ return googleApiError(
2463
+ c,
2464
+ 400,
2465
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2466
+ "invalidArgument",
2467
+ "INVALID_ARGUMENT"
2468
+ );
2483
2469
  }
2484
2470
  for (const messageId of ids) {
2485
2471
  const message = getMessageById(gs, authEmail, messageId);
2486
2472
  if (!message) continue;
2487
- markMessageModified(
2488
- gs,
2489
- message,
2490
- applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
2491
- );
2473
+ markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds));
2492
2474
  }
2493
2475
  return c.body(null, 204);
2494
2476
  });
@@ -2555,7 +2537,13 @@ function messageRoutes({ app, store }) {
2555
2537
  const removeLabelIds = getStringArray(body, "removeLabelIds");
2556
2538
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
2557
2539
  if (missingLabelIds.length > 0) {
2558
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
2540
+ return googleApiError(
2541
+ c,
2542
+ 400,
2543
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
2544
+ "invalidArgument",
2545
+ "INVALID_ARGUMENT"
2546
+ );
2559
2547
  }
2560
2548
  const updated = markMessageModified(
2561
2549
  gs,
@@ -2571,7 +2559,9 @@ function messageRoutes({ app, store }) {
2571
2559
  if (!message) {
2572
2560
  return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
2573
2561
  }
2574
- return c.json(formatMessageResource(gs, markMessageModified(gs, message, trashLabelIds(message.label_ids)), "full"));
2562
+ return c.json(
2563
+ formatMessageResource(gs, markMessageModified(gs, message, trashLabelIds(message.label_ids)), "full")
2564
+ );
2575
2565
  });
2576
2566
  app.post("/gmail/v1/users/:userId/messages/:id/untrash", (c) => {
2577
2567
  const authEmail = requireGmailUser(c);
@@ -2580,7 +2570,9 @@ function messageRoutes({ app, store }) {
2580
2570
  if (!message) {
2581
2571
  return googleApiError(c, 404, "Requested entity was not found.", "notFound", "NOT_FOUND");
2582
2572
  }
2583
- return c.json(formatMessageResource(gs, markMessageModified(gs, message, untrashLabelIds(message.label_ids)), "full"));
2573
+ return c.json(
2574
+ formatMessageResource(gs, markMessageModified(gs, message, untrashLabelIds(message.label_ids)), "full")
2575
+ );
2584
2576
  });
2585
2577
  app.delete("/gmail/v1/users/:userId/messages/:id", (c) => {
2586
2578
  const authEmail = requireGmailUser(c);
@@ -2613,6 +2605,7 @@ var FONTS = {
2613
2605
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
2614
2606
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
2615
2607
  };
2608
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
2616
2609
  function escapeHtml(s) {
2617
2610
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2618
2611
  }
@@ -2764,6 +2757,132 @@ body{
2764
2757
  .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
2765
2758
  .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
2766
2759
  .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
2760
+
2761
+ .inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
2762
+ .inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
2763
+ .inspector-tabs a{
2764
+ padding:7px 16px;border-radius:6px;text-decoration:none;
2765
+ font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
2766
+ transition:color .15s,border-color .15s;
2767
+ }
2768
+ .inspector-tabs a:hover{color:#33ff00;}
2769
+ .inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
2770
+ .inspector-section{margin-bottom:24px;}
2771
+ .inspector-section h2{
2772
+ font-family:'Geist Pixel',monospace;
2773
+ font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
2774
+ }
2775
+ .inspector-section h3{
2776
+ font-family:'Geist Pixel',monospace;
2777
+ font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
2778
+ }
2779
+ .inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
2780
+ .inspector-table th,.inspector-table td{
2781
+ text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
2782
+ font-size:.8125rem;
2783
+ }
2784
+ .inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
2785
+ .inspector-table td{color:#33ff00;}
2786
+ .inspector-table tbody tr{transition:background .1s;}
2787
+ .inspector-table tbody tr:hover{background:#0a3300;}
2788
+ .inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
2789
+
2790
+ .checkout-layout{
2791
+ display:flex;min-height:calc(100vh - 42px);
2792
+ }
2793
+ .checkout-summary{
2794
+ flex:1;background:#020;padding:48px 40px 48px 10%;
2795
+ display:flex;flex-direction:column;justify-content:center;
2796
+ border-right:1px solid #0a3300;
2797
+ }
2798
+ .checkout-form-side{
2799
+ flex:1;background:#000;padding:48px 10% 48px 40px;
2800
+ display:flex;flex-direction:column;justify-content:center;
2801
+ }
2802
+ .checkout-merchant{
2803
+ display:flex;align-items:center;gap:10px;margin-bottom:6px;
2804
+ }
2805
+ .checkout-merchant-name{
2806
+ font-family:'Geist Pixel',monospace;
2807
+ font-size:.9375rem;font-weight:600;color:#33ff00;
2808
+ }
2809
+ .checkout-test-badge{
2810
+ font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
2811
+ background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
2812
+ }
2813
+ .checkout-total{
2814
+ font-family:'Geist Pixel',monospace;
2815
+ font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
2816
+ }
2817
+ .checkout-line-item{
2818
+ display:flex;align-items:center;gap:14px;padding:14px 0;
2819
+ border-bottom:1px solid #0a3300;
2820
+ }
2821
+ .checkout-line-item:first-child{border-top:1px solid #0a3300;}
2822
+ .checkout-item-icon{
2823
+ width:42px;height:42px;border-radius:6px;background:#0a3300;
2824
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
2825
+ font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
2826
+ }
2827
+ .checkout-item-details{flex:1;min-width:0;}
2828
+ .checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
2829
+ .checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
2830
+ .checkout-item-price{
2831
+ font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
2832
+ }
2833
+ .checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
2834
+ .checkout-totals{margin-top:20px;}
2835
+ .checkout-totals-row{
2836
+ display:flex;justify-content:space-between;padding:6px 0;
2837
+ font-size:.8125rem;color:#1a8c00;
2838
+ }
2839
+ .checkout-totals-row.total{
2840
+ border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
2841
+ font-size:.9375rem;font-weight:600;color:#33ff00;
2842
+ }
2843
+ .checkout-form-section{margin-bottom:24px;}
2844
+ .checkout-form-label{
2845
+ font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
2846
+ }
2847
+ .checkout-input{
2848
+ width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
2849
+ background:#020;color:#33ff00;font:inherit;font-size:.875rem;
2850
+ transition:border-color .15s;outline:none;
2851
+ }
2852
+ .checkout-input:focus{border-color:#33ff00;}
2853
+ .checkout-input::placeholder{color:#116600;}
2854
+ .checkout-card-box{
2855
+ border:1px solid #0a3300;border-radius:6px;padding:14px;
2856
+ background:#020;
2857
+ }
2858
+ .checkout-card-row{
2859
+ display:flex;gap:12px;margin-top:10px;
2860
+ }
2861
+ .checkout-card-row .checkout-input{flex:1;}
2862
+ .checkout-sim-note{
2863
+ font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
2864
+ font-style:italic;
2865
+ }
2866
+ .checkout-pay-btn{
2867
+ width:100%;padding:14px;border:none;border-radius:8px;
2868
+ background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
2869
+ cursor:pointer;transition:background .15s;
2870
+ font-family:'Geist Pixel',monospace;
2871
+ }
2872
+ .checkout-pay-btn:hover{background:#44ff22;}
2873
+ .checkout-cancel{
2874
+ text-align:center;margin-top:14px;
2875
+ }
2876
+ .checkout-cancel a{
2877
+ color:#1a8c00;text-decoration:none;font-size:.8125rem;
2878
+ transition:color .15s;
2879
+ }
2880
+ .checkout-cancel a:hover{color:#33ff00;}
2881
+ @media(max-width:768px){
2882
+ .checkout-layout{flex-direction:column;}
2883
+ .checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
2884
+ .checkout-form-side{padding:32px 20px;}
2885
+ }
2767
2886
  `;
2768
2887
  var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
2769
2888
  function emuBar(service) {
@@ -2783,6 +2902,7 @@ function head(title) {
2783
2902
  <head>
2784
2903
  <meta charset="utf-8"/>
2785
2904
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
2905
+ <link rel="icon" href="/_emulate/favicon.ico"/>
2786
2906
  <title>${escapeHtml(title)} | emulate</title>
2787
2907
  <style>${CSS}</style>
2788
2908
  </head>`;
@@ -2884,6 +3004,7 @@ async function createIdToken(user, clientId, nonce, baseUrl) {
2884
3004
  family_name: user.family_name,
2885
3005
  picture: user.picture,
2886
3006
  locale: user.locale,
3007
+ ...user.hd ? { hd: user.hd } : {},
2887
3008
  ...nonce ? { nonce } : {}
2888
3009
  }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuer(baseUrl).setAudience(clientId).setIssuedAt().setExpirationTime("1h");
2889
3010
  return builder.sign(JWT_SECRET);
@@ -2911,7 +3032,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
2911
3032
  "given_name",
2912
3033
  "family_name",
2913
3034
  "picture",
2914
- "locale"
3035
+ "locale",
3036
+ "hd"
2915
3037
  ],
2916
3038
  code_challenge_methods_supported: ["plain", "S256"]
2917
3039
  });
@@ -2939,7 +3061,11 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
2939
3061
  }
2940
3062
  if (redirect_uri && !matchesRedirectUri(redirect_uri, client.redirect_uris)) {
2941
3063
  return c.html(
2942
- renderErrorPage("Redirect URI mismatch", "The redirect_uri is not registered for this application.", SERVICE_LABEL),
3064
+ renderErrorPage(
3065
+ "Redirect URI mismatch",
3066
+ "The redirect_uri is not registered for this application.",
3067
+ SERVICE_LABEL
3068
+ ),
2943
3069
  400
2944
3070
  );
2945
3071
  }
@@ -3051,7 +3177,13 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
3051
3177
  });
3052
3178
  }
3053
3179
  if (grant_type !== "authorization_code") {
3054
- return c.json({ error: "unsupported_grant_type", error_description: "Only authorization_code and refresh_token are supported." }, 400);
3180
+ return c.json(
3181
+ {
3182
+ error: "unsupported_grant_type",
3183
+ error_description: "Only authorization_code and refresh_token are supported."
3184
+ },
3185
+ 400
3186
+ );
3055
3187
  }
3056
3188
  const pendingMap = getPendingCodes(store);
3057
3189
  const pending = pendingMap.get(code);
@@ -3124,7 +3256,8 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
3124
3256
  given_name: user.given_name,
3125
3257
  family_name: user.family_name,
3126
3258
  picture: user.picture,
3127
- locale: user.locale
3259
+ locale: user.locale,
3260
+ ...user.hd ? { hd: user.hd } : {}
3128
3261
  });
3129
3262
  });
3130
3263
  app.post("/oauth2/revoke", async (c) => {
@@ -3174,7 +3307,13 @@ function settingsRoutes({ app, store }) {
3174
3307
  }
3175
3308
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
3176
3309
  if (missingLabelIds.length > 0) {
3177
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
3310
+ return googleApiError(
3311
+ c,
3312
+ 400,
3313
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
3314
+ "invalidArgument",
3315
+ "INVALID_ARGUMENT"
3316
+ );
3178
3317
  }
3179
3318
  if (findMatchingFilter(gs, {
3180
3319
  user_email: authEmail,
@@ -3277,14 +3416,16 @@ function threadRoutes({ app, store }) {
3277
3416
  const removeLabelIds = getStringArray(body, "removeLabelIds");
3278
3417
  const missingLabelIds = findMissingLabelIds(gs, authEmail, [...addLabelIds, ...removeLabelIds]);
3279
3418
  if (missingLabelIds.length > 0) {
3280
- return googleApiError(c, 400, `Invalid label IDs: ${missingLabelIds.join(", ")}`, "invalidArgument", "INVALID_ARGUMENT");
3419
+ return googleApiError(
3420
+ c,
3421
+ 400,
3422
+ `Invalid label IDs: ${missingLabelIds.join(", ")}`,
3423
+ "invalidArgument",
3424
+ "INVALID_ARGUMENT"
3425
+ );
3281
3426
  }
3282
3427
  const updated = messages.map(
3283
- (message) => markMessageModified(
3284
- gs,
3285
- message,
3286
- applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds)
3287
- )
3428
+ (message) => markMessageModified(gs, message, applyLabelMutation(message.label_ids, addLabelIds, removeLabelIds))
3288
3429
  );
3289
3430
  return c.json(formatThreadResource(gs, updated, "full"));
3290
3431
  });
@@ -3333,120 +3474,148 @@ function seedDefaults(store, _baseUrl) {
3333
3474
  family_name: "User",
3334
3475
  picture: null,
3335
3476
  email_verified: true,
3336
- locale: "en"
3477
+ locale: "en",
3478
+ hd: null
3337
3479
  });
3338
3480
  }
3339
3481
  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);
3482
+ seedCalendars(
3483
+ store,
3484
+ [
3485
+ {
3486
+ id: "primary",
3487
+ user_email: defaultEmail,
3488
+ summary: defaultEmail,
3489
+ primary: true,
3490
+ selected: true,
3491
+ time_zone: "UTC"
3492
+ },
3493
+ {
3494
+ id: "cal_team",
3495
+ user_email: defaultEmail,
3496
+ summary: "Team Calendar",
3497
+ description: "Shared team events",
3498
+ selected: true,
3499
+ time_zone: "UTC"
3500
+ }
3501
+ ],
3502
+ defaultEmail
3503
+ );
3504
+ seedCalendarEvents(
3505
+ store,
3506
+ [
3507
+ {
3508
+ id: "evt_standup",
3509
+ user_email: defaultEmail,
3510
+ calendar_id: "primary",
3511
+ summary: "Daily Standup",
3512
+ description: "Team sync",
3513
+ start_date_time: new Date(Date.now() + 60 * 60 * 1e3).toISOString(),
3514
+ end_date_time: new Date(Date.now() + 90 * 60 * 1e3).toISOString(),
3515
+ attendees: [
3516
+ { email: defaultEmail, display_name: "Test User" },
3517
+ { email: "teammate@example.com", display_name: "Teammate" }
3518
+ ],
3519
+ conference_entry_points: [
3520
+ {
3521
+ entry_point_type: "video",
3522
+ uri: "https://meet.google.com/emulate-standup",
3523
+ label: "Google Meet"
3524
+ }
3525
+ ],
3526
+ hangout_link: "https://meet.google.com/emulate-standup"
3527
+ }
3528
+ ],
3529
+ defaultEmail
3530
+ );
3531
+ seedDriveItems(
3532
+ store,
3533
+ [
3534
+ {
3535
+ id: "drv_root_receipts",
3536
+ user_email: defaultEmail,
3537
+ name: "Receipts",
3538
+ mime_type: "application/vnd.google-apps.folder",
3539
+ parent_ids: ["root"]
3540
+ },
3541
+ {
3542
+ id: "drv_receipt_pdf",
3543
+ user_email: defaultEmail,
3544
+ name: "March Receipt.pdf",
3545
+ mime_type: "application/pdf",
3546
+ parent_ids: ["drv_root_receipts"],
3547
+ data: "receipt-pdf-data"
3548
+ }
3549
+ ],
3550
+ defaultEmail
3551
+ );
3552
+ seedMessages(
3553
+ store,
3554
+ [
3555
+ {
3556
+ id: "msg_welcome",
3557
+ thread_id: "thr_welcome",
3558
+ user_email: defaultEmail,
3559
+ from: "Welcome Team <welcome@example.com>",
3560
+ to: defaultEmail,
3561
+ subject: "Welcome to your local Gmail emulator",
3562
+ snippet: "Your OAuth flow is set up and Gmail message, thread, and label APIs are ready.",
3563
+ 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.",
3564
+ label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
3565
+ date: new Date(Date.now() - 60 * 60 * 1e3).toISOString()
3566
+ },
3567
+ {
3568
+ id: "msg_build",
3569
+ thread_id: "thr_build",
3570
+ user_email: defaultEmail,
3571
+ from: "Build Bot <builds@example.com>",
3572
+ to: defaultEmail,
3573
+ subject: "Nightly build finished successfully",
3574
+ snippet: "The latest build completed successfully in 6 minutes.",
3575
+ body_text: "The latest build completed successfully in 6 minutes.\n\nArtifact upload finished and smoke checks passed.",
3576
+ label_ids: ["INBOX", "CATEGORY_UPDATES"],
3577
+ date: new Date(Date.now() - 2 * 60 * 60 * 1e3).toISOString()
3578
+ },
3579
+ {
3580
+ id: "msg_build_reply",
3581
+ thread_id: "thr_build",
3582
+ user_email: defaultEmail,
3583
+ from: defaultEmail,
3584
+ to: "Build Bot <builds@example.com>",
3585
+ subject: "Re: Nightly build finished successfully",
3586
+ snippet: "Thanks, I will review the artifact after lunch.",
3587
+ body_text: "Thanks, I will review the artifact after lunch.",
3588
+ label_ids: ["SENT"],
3589
+ date: new Date(Date.now() - 90 * 60 * 1e3).toISOString(),
3590
+ in_reply_to: "<msg_build@emulate.google.local>",
3591
+ references: "<msg_build@emulate.google.local>"
3592
+ },
3593
+ {
3594
+ id: "msg_draft",
3595
+ thread_id: "thr_draft",
3596
+ user_email: defaultEmail,
3597
+ from: defaultEmail,
3598
+ to: "someone@example.com",
3599
+ subject: "Draft follow-up",
3600
+ snippet: "Checking in on the open question from yesterday.",
3601
+ body_text: "Checking in on the open question from yesterday.",
3602
+ label_ids: ["DRAFT"],
3603
+ date: new Date(Date.now() - 30 * 60 * 1e3).toISOString()
3604
+ }
3605
+ ],
3606
+ defaultEmail
3607
+ );
3608
+ }
3609
+ var CONSUMER_EMAIL_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
3610
+ function deriveHd(email) {
3611
+ const domain = email.split("@")[1]?.toLowerCase();
3612
+ if (!domain) return null;
3613
+ if (CONSUMER_EMAIL_DOMAINS.has(domain)) return null;
3614
+ return domain;
3615
+ }
3616
+ function resolveHd(user) {
3617
+ if (user.hd !== void 0) return user.hd || null;
3618
+ return deriveHd(user.email);
3450
3619
  }
3451
3620
  function seedFromConfig(store, _baseUrl, config) {
3452
3621
  const gs = getGoogleStore(store);
@@ -3463,7 +3632,8 @@ function seedFromConfig(store, _baseUrl, config) {
3463
3632
  family_name: user.family_name ?? nameParts.slice(1).join(" "),
3464
3633
  picture: user.picture ?? null,
3465
3634
  email_verified: user.email_verified ?? true,
3466
- locale: user.locale ?? "en"
3635
+ locale: user.locale ?? "en",
3636
+ hd: resolveHd(user)
3467
3637
  });
3468
3638
  }
3469
3639
  ensureSystemLabels(gs, user.email);
@@ -3524,29 +3694,33 @@ function seedMessages(store, messages, fallbackEmail) {
3524
3694
  const userEmail = message.user_email ?? fallbackEmail;
3525
3695
  ensureSystemLabels(gs, userEmail);
3526
3696
  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
- });
3697
+ createStoredMessage(
3698
+ gs,
3699
+ {
3700
+ gmail_id: message.id,
3701
+ thread_id: message.thread_id,
3702
+ user_email: userEmail,
3703
+ raw: message.raw ?? null,
3704
+ from: message.from,
3705
+ to: message.to,
3706
+ cc: message.cc ?? null,
3707
+ bcc: message.bcc ?? null,
3708
+ reply_to: message.reply_to ?? null,
3709
+ subject: message.subject,
3710
+ snippet: message.snippet,
3711
+ body_text: message.body_text ?? null,
3712
+ body_html: message.body_html ?? null,
3713
+ label_ids: message.label_ids ?? ["INBOX", "UNREAD"],
3714
+ date: message.date,
3715
+ internal_date: message.internal_date,
3716
+ message_id: message.message_id,
3717
+ references: message.references ?? null,
3718
+ in_reply_to: message.in_reply_to ?? null
3719
+ },
3720
+ {
3721
+ createMissingCustomLabels: true
3722
+ }
3723
+ );
3550
3724
  }
3551
3725
  }
3552
3726
  function seedCalendars(store, calendars, fallbackEmail) {
@@ -3638,4 +3812,4 @@ export {
3638
3812
  googlePlugin,
3639
3813
  seedFromConfig
3640
3814
  };
3641
- //# sourceMappingURL=dist-6EW7SSOZ.js.map
3815
+ //# sourceMappingURL=dist-ZY5SZSJ2.js.map