@abi-software/simulationvuer 4.0.3 → 4.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/simulationvuer",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vite serve --host --force",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@abi-software/plotvuer": "1.0.7",
24
- "@opencor/opencor": "^0.20260803.0",
24
+ "@opencor/opencor": "^0.20260804.0",
25
25
  "element-plus": "2.8.4",
26
26
  "jsonschema": "^1.5.0",
27
27
  "mathjs": "^15.2.0",
package/vite.config.js CHANGED
@@ -31,72 +31,109 @@ export default defineConfig(({ command, mode }) => {
31
31
  dts: "src/components.d.ts",
32
32
  }),
33
33
 
34
- // Resolve the libopencor.js import URL in @opencor/opencor's opencor.es.js so that it works for all consumers
35
- // (not just Vite projects) and this without server configuration.
34
+ // Serve libOpenCOR for OpenCOR's runtime probe.
36
35
  //
37
- // In @opencor/opencor, the code uses:
38
- // new URL("./libopencor/wasm/<version>/libopencor.js", window.location.href).href
36
+ // In @opencor/opencor, opencor.es.js locates its glue code at runtime:
37
+ // new URL("libopencor/downloads/wasm/<version>/libopencor.js", document.baseURI).href
38
+ // and fetches that same-origin path (HEAD) before falling back to opencor.ws. The WASM is always loaded from a
39
+ // hardcoded opencor.ws base via locateFile. The probe is evaluated at runtime and the glue is imported
40
+ // dynamically (with @vite-ignore), so there is no static URL for Vite to rewrite.
39
41
  //
40
- // This resolves relative to the page URL, requiring the server to host libopencor.js at a page-relative path
41
- // (which doesn't work for library consumers or static builds).
42
- //
43
- // We fix this by:
44
- // - Dev (serve): rewriting to "/libopencor/libopencor.js", served by the middleware below.
45
- // - Build (lib): rewriting to use import.meta.url, so the import resolves relative to simulationvuer.js's URL
46
- // (same directory, libopencor.js is copied alongside it via closeBundle).
42
+ // The probe is satisfied by:
43
+ // - Dev (serve): serving "/libopencor/*" from the installed package via the middleware below, so the probe
44
+ // resolves same-origin (required for pthread Workers).
45
+ // - Build (lib): copying the package's libOpenCOR dir to dist/libopencor via closeBundle, so the
46
+ // document-relative probe hits when the page is served from the dist root (otherwise OpenCOR
47
+ // falls back to opencor.ws).
47
48
 
48
49
  {
49
50
  name: "opencor-libopencor",
50
- transform(code, id) {
51
- if (id.includes("@opencor/opencor") && id.endsWith("opencor.es.js")) {
52
- const importRegex =
53
- /(\/\*\s*@vite-ignore\s*\*\/[\s\n]*)(new URL\("(\.\/libopencor\/wasm\/[^/]+\/libopencor\.js)",\s*window\.location\.href\)\.href)/g;
54
- const res = code.replace(
55
- importRegex,
56
- (_match, prefix, _urlExpr, urlPath) => {
57
- if (command === "serve") {
58
- // Dev mode: absolute path served by the middleware below.
59
-
60
- return `${prefix}"/libopencor/libopencor.js"`;
61
- }
62
-
63
- // Build mode: resolve relative to the bundle's own URL.
64
- // Note: we use replace() instead of new URL(..., import.meta.url) to avoid Vite's asset handler, which
65
- // would inline the file as a data: URL (which breaks Emscripten's pthread Worker creation). At
66
- // runtime, import.meta.url points to simulationvuer.js and the derived path correctly reaches
67
- // libopencor in the same directory (copied alongside by closeBundle).
68
-
69
- return `${prefix}import.meta.url.replace(/[^/]*$/, "") + ${JSON.stringify(urlPath.substring(2))}`;
70
- },
71
- );
72
-
73
- if (res !== code) {
74
- return {
75
- code: res,
76
- map: null,
77
- };
78
- }
79
- }
80
- },
81
51
  configureServer(server) {
82
52
  // Serve libopencor files from @opencor/opencor during development so the dynamic import in opencor.es.js
83
53
  // resolves same-origin (required for pthread Workers).
84
54
 
55
+ // Resolve symlinks in libopencorDir once so the per-request guard compares real paths on both sides.
56
+
57
+ let realLibopencorDir;
58
+
59
+ try {
60
+ realLibopencorDir = fs.realpathSync(libopencorDir);
61
+ } catch {
62
+ realLibopencorDir = libopencorDir;
63
+ }
64
+
85
65
  server.middlewares.use((req, res, next) => {
86
- const urlPath = req.url;
66
+ // Parse the URL to strip any query string, and decode it safely (malformed sequences are rejected).
67
+
68
+ let pathname;
69
+
70
+ try {
71
+ pathname = decodeURIComponent(
72
+ new URL(req.url, "http://localhost").pathname,
73
+ );
74
+ } catch {
75
+ next();
76
+
77
+ return;
78
+ }
87
79
 
88
- if (!urlPath.startsWith("/libopencor/")) {
80
+ if (!pathname.startsWith("/libopencor/")) {
89
81
  next();
90
82
 
91
83
  return;
92
84
  }
93
85
 
94
- const filePath = path.join(
95
- libopencorDir,
96
- urlPath.replace("/libopencor/", ""),
97
- );
86
+ // Resolve the requested subpath and verify the result stays within libopencorDir, guarding against
87
+ // directory traversal (e.g., "/libopencor/../../..." or percent-encoded ".." segments) and symlink
88
+ // escapes (a symlink inside libopencorDir pointing outside would pass a purely lexical check).
89
+ // Note: realpathSync() resolves symlinks, so a symlink pointing outside libopencorDir resolves to a path
90
+ // outside it, which the relative check below rejects. It also throws if the path doesn't exist.
91
+
92
+ let filePath;
93
+
94
+ try {
95
+ filePath = fs.realpathSync(
96
+ path.resolve(
97
+ libopencorDir,
98
+ pathname.slice("/libopencor/".length),
99
+ ),
100
+ );
101
+ } catch {
102
+ next();
103
+
104
+ return;
105
+ }
106
+
107
+ // Reject paths escaping the directory: exactly "..", starting with "../", or absolute (e.g., a
108
+ // different drive on Windows).
109
+ // Note: a plain startsWith("..") check would wrongly reject legitimate files like "..foo.js".
110
+
111
+ const relative = path.relative(realLibopencorDir, filePath);
112
+
113
+ if (
114
+ relative === "" ||
115
+ relative === ".." ||
116
+ relative.startsWith(`..${path.sep}`) ||
117
+ path.isAbsolute(relative)
118
+ ) {
119
+ next();
120
+
121
+ return;
122
+ }
123
+
124
+ // Read the file and serve it with the correct headers.
125
+
126
+ let content;
127
+
128
+ try {
129
+ if (!fs.statSync(filePath).isFile()) {
130
+ next();
131
+
132
+ return;
133
+ }
98
134
 
99
- if (!fs.existsSync(filePath)) {
135
+ content = fs.readFileSync(filePath);
136
+ } catch {
100
137
  next();
101
138
 
102
139
  return;
@@ -107,22 +144,23 @@ export default defineConfig(({ command, mode }) => {
107
144
  "Cross-Origin-Embedder-Policy": "require-corp",
108
145
  "Cross-Origin-Resource-Policy": "same-origin",
109
146
  });
110
- res.end(fs.readFileSync(filePath));
147
+ res.end(content);
111
148
  });
112
149
  },
113
150
  closeBundle() {
114
- // Copy libopencor alongside the built JavaScript file so that the dynamic import (using import.meta.url as
115
- // base) resolves correctly. App mode puts the built JavaScript file in dist/assets while lib mode puts it in
116
- // dist.
151
+ // Copy liboOpenCOR to the dist root (dist/libopencor) so that OpenCOR's document-relative probe
152
+ // (`<baseURI>/libopencor/downloads/wasm/<version>/libopencor.js`) resolves: in app mode, dist is the
153
+ // deployment root, and in lib mode the probed files ship in the package for consumers that serve them at the
154
+ // page root (everyone else falls back to opencor.ws).
117
155
 
118
156
  if (!fs.existsSync(libopencorDir)) {
119
157
  return;
120
158
  }
121
159
 
122
- const distDir = path.resolve(__dirname, "dist");
123
- const targetDir = fs.existsSync(path.join(distDir, "assets"))
124
- ? path.join(distDir, "assets", "libopencor")
125
- : path.join(distDir, "libopencor");
160
+ const targetDir = path.join(
161
+ path.resolve(__dirname, "dist"),
162
+ "libopencor",
163
+ );
126
164
 
127
165
  fs.cpSync(libopencorDir, targetDir, { recursive: true, force: true });
128
166
  },