@samva/vite 0.5.0 → 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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Samva editor</title>
7
- <script type="module" crossorigin src="./assets/index-BZ-5-1V0.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-Cg4x6ewh.css">
7
+ <script type="module" crossorigin src="./assets/index-DWbrYu6R.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-CxXHDnD3.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { createEmailRenderer } from "./email.js";
4
4
  import { isTemplateDefinition, readEmailProject, renderEmailFixtures } from "./project.js";
5
5
  import { existsSync } from "node:fs";
6
6
  import { open, readFile, realpath, rename, rm, stat } from "node:fs/promises";
7
+ import { isIP } from "node:net";
7
8
  import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import { checkEmailCompatibility, emailSelections } from "@samva/markup/compiler";
@@ -109,13 +110,12 @@ var EditorFileStore = class {
109
110
  const rel = full.slice(this.dir.length + 1).split("\\").join("/");
110
111
  return kindForId(rel) === void 0 ? void 0 : rel;
111
112
  }
112
- async load(id, previous) {
113
+ async load(id, previous, fixture = this.session.get(id)?.fixture) {
113
114
  const path = await this.resolvePath(id);
114
115
  if (path === void 0) return { diagnostics: [diagnostic(id, "error", "template-unavailable", "Template file is missing or resolves outside the configured templates directory.")] };
115
116
  const authoredSource = await readFile(path, "utf8").catch(() => void 0);
116
117
  if (authoredSource === void 0) return { diagnostics: [diagnostic(id, "error", "template-read-error", "Template file could not be read.")] };
117
- const session = this.session.get(id);
118
- const name = session?.name ?? deriveName(id);
118
+ const name = this.session.get(id)?.name ?? deriveName(id);
119
119
  if (this.evaluator === void 0) return {
120
120
  authoredSource,
121
121
  diagnostics: [diagnostic(id, "error", "tsx-evaluator-unavailable", "TSX templates require the Vite evaluator.")]
@@ -124,7 +124,7 @@ var EditorFileStore = class {
124
124
  path,
125
125
  id,
126
126
  name,
127
- fixture: session?.fixture
127
+ fixture
128
128
  }).catch((cause) => ({
129
129
  ok: false,
130
130
  diagnostics: [diagnostic(id, "error", "module-load-error", cause instanceof Error ? cause.message : String(cause))]
@@ -408,12 +408,7 @@ var EditorFileStore = class {
408
408
  error: `Template declares no fixture named ${JSON.stringify(fixture)}.`
409
409
  };
410
410
  const meta = this.session.get(id);
411
- this.session.set(id, {
412
- ...meta,
413
- metadata: meta?.metadata ?? {},
414
- fixture
415
- });
416
- const loaded = await this.load(id, entry);
411
+ const loaded = await this.load(id, entry, fixture);
417
412
  if (loaded.entry === void 0) {
418
413
  if (loaded.diagnostics.length > 0) this.diagnostics.set(id, loaded.diagnostics);
419
414
  return {
@@ -422,6 +417,11 @@ var EditorFileStore = class {
422
417
  error: `Template ${id} could not be re-rendered.`
423
418
  };
424
419
  }
420
+ this.session.set(id, {
421
+ ...meta,
422
+ metadata: meta?.metadata ?? {},
423
+ fixture
424
+ });
425
425
  this.entries.set(id, loaded.entry);
426
426
  this.publishEntry(id, loaded.entry, client);
427
427
  return { ok: true };
@@ -513,6 +513,28 @@ const isViteInternalPath = (pathname) => VITE_INTERNAL_PREFIXES.some((prefix) =>
513
513
  const hasFileExtension = (pathname) => extname(pathname) !== "";
514
514
  const underRoute = (route, pathname) => route === "/" || pathname === route || pathname.startsWith(`${route}/`);
515
515
  const childRoute = (route, child) => route === "/" ? `/${child}` : `${route}/${child}`;
516
+ const isLoopbackHostname = (hostname) => {
517
+ const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
518
+ if (normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "::1") return true;
519
+ if (isIP(normalized) !== 4) return false;
520
+ return normalized.split(".")[0] === "127";
521
+ };
522
+ const isLoopbackAddress = (address) => {
523
+ if (address === void 0) return false;
524
+ const normalized = address.startsWith("::ffff:") ? address.slice(7) : address;
525
+ return isLoopbackHostname(normalized);
526
+ };
527
+ const isTrustedSourceSave = (req) => {
528
+ if (!isLoopbackAddress(req.socket.remoteAddress)) return false;
529
+ const origin = req.headers.origin;
530
+ if (origin === void 0) return false;
531
+ try {
532
+ const parsed = new URL(origin);
533
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && isLoopbackHostname(parsed.hostname);
534
+ } catch {
535
+ return false;
536
+ }
537
+ };
516
538
  const MAX_BODY_BYTES = 6295552;
517
539
  var EditorBodyTooLarge = class extends Error {};
518
540
  const readBody = (req) => new Promise((done, fail) => {
@@ -716,7 +738,11 @@ const samvaEditor = (options = {}) => {
716
738
  runtime: "automatic",
717
739
  importSource: "@samva/markup"
718
740
  } },
719
- optimizeDeps: { exclude: ["@samva/markup"] }
741
+ optimizeDeps: { exclude: ["@samva/markup"] },
742
+ server: {
743
+ host: "127.0.0.1",
744
+ allowedHosts: ["localhost", ".localhost"]
745
+ }
720
746
  };
721
747
  },
722
748
  configResolved(resolved) {
@@ -895,8 +921,7 @@ const samvaEditor = (options = {}) => {
895
921
  const id = query.get("id");
896
922
  if (!id) return sendJson(res, 400, { error: "missing id" });
897
923
  if (req.method === "POST") {
898
- const origin = req.headers.origin;
899
- if (origin !== void 0 && origin !== `http://${req.headers.host}` && origin !== `https://${req.headers.host}`) return sendJson(res, 403, { error: "Source saves require the editor origin." });
924
+ if (!isTrustedSourceSave(req)) return sendJson(res, 403, { error: "Source saves require the editor origin." });
900
925
  let body;
901
926
  try {
902
927
  body = JSON.parse(await readBody(req));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/vite",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Unified Vite editor for Samva email, SMS, and WhatsApp templates",
5
5
  "keywords": [
6
6
  "email",
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "@babel/parser": "^8.0.4",
73
- "@samva/markup": "^0.5.0",
73
+ "@samva/markup": "^0.6.0",
74
74
  "better-result": "3.0.1",
75
75
  "caniemail": "2.0.2",
76
76
  "css-tree": "3.2.1",