@skyvexsoftware/stratos-sdk 0.2.2 → 0.3.1

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.
@@ -98,6 +98,7 @@ export type FlightData = {
98
98
  simulationRate: number;
99
99
  isInMenu: boolean;
100
100
  isXPlane: boolean;
101
+ isHelicopter: boolean;
101
102
  stallWarning: boolean;
102
103
  overspeedWarning: boolean;
103
104
  crashed: boolean;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Vite plugin that fixes cross-origin HMR for plugins loaded inside Stratos.
3
+ *
4
+ * Three problems when loading plugins via cross-origin import():
5
+ *
6
+ * 1. Absolute-path imports (/@vite/client, /@react-refresh) resolve to the
7
+ * PAGE origin (the shell), not the plugin's dev server. This includes
8
+ * imports injected by Vite's importAnalysis (which runs AFTER all user
9
+ * transform plugins).
10
+ *
11
+ * 2. The HMR client's __BASE__ defaults to "/" so module re-fetches during
12
+ * HMR go to the shell's origin.
13
+ *
14
+ * 3. @vitejs/plugin-react's preamble (which initializes React Fast Refresh)
15
+ * is injected via transformIndexHtml — but plugins don't have HTML files,
16
+ * so the preamble never runs. Without it, Fast Refresh silently fails.
17
+ *
18
+ * This plugin fixes all three using two hooks:
19
+ * - transform (enforce: post): patches __BASE__, rewrites imports our
20
+ * transform can see, and injects the React Refresh preamble
21
+ * - configureServer middleware: intercepts FINAL module responses to rewrite
22
+ * /@vite/client imports added by importAnalysis (which runs after all
23
+ * user transforms)
24
+ */
25
+ import type { Plugin } from "vite";
26
+ export declare function hmrBaseRewrite(): Plugin;
27
+ //# sourceMappingURL=hmr-base-rewrite.d.ts.map
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Vite plugin that fixes cross-origin HMR for plugins loaded inside Stratos.
3
+ *
4
+ * Three problems when loading plugins via cross-origin import():
5
+ *
6
+ * 1. Absolute-path imports (/@vite/client, /@react-refresh) resolve to the
7
+ * PAGE origin (the shell), not the plugin's dev server. This includes
8
+ * imports injected by Vite's importAnalysis (which runs AFTER all user
9
+ * transform plugins).
10
+ *
11
+ * 2. The HMR client's __BASE__ defaults to "/" so module re-fetches during
12
+ * HMR go to the shell's origin.
13
+ *
14
+ * 3. @vitejs/plugin-react's preamble (which initializes React Fast Refresh)
15
+ * is injected via transformIndexHtml — but plugins don't have HTML files,
16
+ * so the preamble never runs. Without it, Fast Refresh silently fails.
17
+ *
18
+ * This plugin fixes all three using two hooks:
19
+ * - transform (enforce: post): patches __BASE__, rewrites imports our
20
+ * transform can see, and injects the React Refresh preamble
21
+ * - configureServer middleware: intercepts FINAL module responses to rewrite
22
+ * /@vite/client imports added by importAnalysis (which runs after all
23
+ * user transforms)
24
+ */
25
+ export function hmrBaseRewrite() {
26
+ let devOrigin = "";
27
+ return {
28
+ name: "stratos-hmr-base-rewrite",
29
+ apply: "serve",
30
+ enforce: "post",
31
+ configureServer(server) {
32
+ server.httpServer?.once("listening", () => {
33
+ const addr = server.httpServer?.address();
34
+ if (addr && typeof addr === "object") {
35
+ const host = addr.address === "::" ||
36
+ addr.address === "::1" ||
37
+ addr.address === "0.0.0.0" ||
38
+ addr.address === "127.0.0.1"
39
+ ? "localhost"
40
+ : addr.address;
41
+ devOrigin = `http://${host}:${addr.port}`;
42
+ }
43
+ });
44
+ // Intercept module responses AFTER Vite's transformMiddleware
45
+ // (which includes importAnalysis) to rewrite /@vite/client imports
46
+ // that were injected post-transform. We wrap res.end() on every
47
+ // request so we can modify the final response body.
48
+ // Prevent Electron from caching /@vite/client across page loads.
49
+ // Stale cached modules break HMR because the __BASE__ and __WS_TOKEN__
50
+ // injections from the current server instance aren't in the cache.
51
+ server.middlewares.use((req, res, next) => {
52
+ if (req.url?.includes("@vite/client") ||
53
+ req.url?.includes("@react-refresh")) {
54
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
55
+ }
56
+ next();
57
+ });
58
+ // Rewrite /@vite/client imports in module responses. Vite's
59
+ // importAnalysis injects these AFTER all user transform plugins,
60
+ // so we intercept the final response via res.end().
61
+ server.middlewares.use((_req, res, next) => {
62
+ if (!devOrigin)
63
+ return next();
64
+ const originalEnd = res.end.bind(res);
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ res.end = function (chunk, encoding, cb) {
67
+ if (typeof chunk === "string" && chunk.includes("/@vite/client")) {
68
+ chunk = chunk.replace(/(?<=['"])(\/@vite\/client)(?=['"])/g, `${devOrigin}/@vite/client`);
69
+ }
70
+ return originalEnd(chunk, encoding, cb);
71
+ };
72
+ next();
73
+ });
74
+ },
75
+ transform(code, id) {
76
+ if (!devOrigin)
77
+ return null;
78
+ // 1. Patch Vite's HMR client: replace __BASE__ with full origin
79
+ if (id.includes("vite/dist/client/client.mjs")) {
80
+ const patched = code.replace(/__BASE__/g, `"${devOrigin}/"`);
81
+ if (patched !== code) {
82
+ return { code: patched, map: null };
83
+ }
84
+ return null;
85
+ }
86
+ // Skip node_modules
87
+ if (id.includes("/node_modules/"))
88
+ return null;
89
+ let patched = code;
90
+ // 2. Rewrite /@vite/* and /@react-refresh imports to full URLs
91
+ // (catches imports from @vitejs/plugin-react and CSS transforms)
92
+ patched = patched.replace(/(from\s+["'])(\/(?:@vite\/[^"']+|@react-refresh))(["'])/g, `$1${devOrigin}$2$3`);
93
+ patched = patched.replace(/(import\s*\(\s*["'])(\/(?:@vite\/[^"']+|@react-refresh))(["'])/g, `$1${devOrigin}$2$3`);
94
+ // 3. Inject React Fast Refresh preamble into the entry file.
95
+ if (id.endsWith("/src/ui/index.tsx") || id.endsWith("/src/ui/index.ts")) {
96
+ const preamble = [
97
+ `import __StratosRefresh__ from "${devOrigin}/@react-refresh";`,
98
+ `__StratosRefresh__.injectIntoGlobalHook(window);`,
99
+ `window.$RefreshReg$ = () => {};`,
100
+ `window.$RefreshSig$ = () => (type) => type;`,
101
+ `window.__vite_plugin_react_preamble_installed__ = true;`,
102
+ ].join("\n");
103
+ patched = preamble + "\n" + patched;
104
+ }
105
+ if (patched !== code) {
106
+ return { code: patched, map: null };
107
+ }
108
+ return null;
109
+ },
110
+ };
111
+ }
112
+ //# sourceMappingURL=hmr-base-rewrite.js.map
@@ -22,6 +22,7 @@ import { UI_EXTERNALS } from "./externals.js";
22
22
  import { serveExternals } from "./serve-externals.js";
23
23
  import { stratosDevServer } from "./stratos-dev-server.js";
24
24
  import { cssInject } from "./css-inject.js";
25
+ import { hmrBaseRewrite } from "./hmr-base-rewrite.js";
25
26
  /** Modules external in background builds (available in Electron main process) */
26
27
  const BG_EXTERNALS = ["electron", "socket.io-client"];
27
28
  /** Node.js built-in modules to externalize in background builds */
@@ -204,30 +205,27 @@ function createUIConfig(pluginDir, entry, extraConfig) {
204
205
  const isProduction = process.env.NODE_ENV === "production";
205
206
  return {
206
207
  root: pluginDir,
208
+ // Skip WebSocket token validation so /@vite/client can connect from
209
+ // a cross-origin Electron renderer. The renderer sends its own Origin
210
+ // header (e.g. http://localhost:5173), which triggers Vite's token
211
+ // check and rejects the connection. Safe for local dev servers.
212
+ legacy: { skipWebSocketTokenCheck: true },
207
213
  server: {
208
- cors: {
209
- origin: [
210
- "http://localhost:2066",
211
- "http://127.0.0.1:2066",
212
- /^http:\/\/localhost:\d+$/,
213
- ],
214
- },
214
+ cors: true,
215
215
  hmr: {
216
- // When modules are loaded via stratos-dev:// protocol, Vite's HMR
216
+ // When modules are loaded via cross-origin import(), Vite's HMR
217
217
  // client needs to connect back to THIS dev server's WebSocket directly.
218
- // Without this, it would try to derive the WS URL from the custom
219
- // protocol scheme, which doesn't work.
220
218
  protocol: "ws",
221
219
  host: "localhost",
222
220
  },
223
221
  },
224
222
  optimizeDeps: {
225
- exclude: [
226
- ...UI_EXTERNALS,
227
- "react/jsx-runtime",
228
- "react/jsx-dev-runtime",
229
- "react-dom/client",
230
- ],
223
+ // Exclude non-React externals from pre-bundling (they come from the shell).
224
+ // React packages are NOT excluded — they need to be pre-bundled so that
225
+ // @vitejs/plugin-react can resolve react-refresh for Fast Refresh.
226
+ // serveExternals() still rewrites all source-level React imports to
227
+ // window.__stratos_modules__, so the pre-bundled React is never loaded.
228
+ exclude: UI_EXTERNALS.filter((id) => !id.startsWith("react") && id !== "react-dom"),
231
229
  },
232
230
  build: {
233
231
  outDir: path.resolve(pluginDir, "dist/ui"),
@@ -252,6 +250,7 @@ function createUIConfig(pluginDir, entry, extraConfig) {
252
250
  ...extraConfig,
253
251
  plugins: [
254
252
  serveExternals(),
253
+ hmrBaseRewrite(),
255
254
  stratosDevServer({ pluginDir }),
256
255
  stratosExternals(),
257
256
  cssInject(),
@@ -20,12 +20,19 @@ import { io as ioClient } from "socket.io-client";
20
20
  */
21
21
  export function stratosDevServer(options) {
22
22
  const { pluginDir } = options;
23
- // Shared state between configureServer and handleHotUpdate hooks
23
+ // Shared state between hooks
24
24
  let socket = null;
25
25
  let pluginId = "unknown";
26
+ let wsToken = "";
26
27
  return {
27
28
  name: "stratos-dev-server",
28
29
  apply: "serve",
30
+ configResolved(config) {
31
+ // Capture the WebSocket token generated by Vite. The shell needs this
32
+ // to connect to the plugin's HMR WebSocket for live updates.
33
+ wsToken =
34
+ config.webSocketToken ?? "";
35
+ },
29
36
  configureServer(server) {
30
37
  const stratosPort = options.stratosPort ??
31
38
  (process.env.STRATOS_PORT
@@ -92,6 +99,8 @@ export function stratosDevServer(options) {
92
99
  devUrl,
93
100
  capabilities: ["ui", "hot-reload"],
94
101
  distPath,
102
+ sourceRoot: pluginDir,
103
+ wsToken,
95
104
  });
96
105
  });
97
106
  socket.on("dev:plugin-mounted", (data) => {
@@ -185,10 +194,10 @@ export function stratosDevServer(options) {
185
194
  process.once("SIGTERM", cleanup);
186
195
  // Start connecting after the HTTP server is listening
187
196
  server.httpServer?.once("listening", () => {
188
- // Set the HMR port dynamically so Vite's HMR client connects
189
- // directly to this dev server's WebSocket (cross-origin is OK for WS).
190
197
  const addr = server.httpServer?.address();
191
198
  if (addr && typeof addr === "object") {
199
+ // Set the HMR port dynamically so Vite's HMR client connects
200
+ // directly to this dev server's WebSocket.
192
201
  server.config.server.hmr =
193
202
  typeof server.config.server.hmr === "object"
194
203
  ? {
@@ -201,19 +210,13 @@ export function stratosDevServer(options) {
201
210
  connect();
202
211
  });
203
212
  },
204
- // Notify Stratos when UI source files change so it can reload the plugin
205
- handleHotUpdate({ file }) {
206
- // Only signal for UI source changes (not background, not config files)
207
- if (file.includes("/src/") && !file.includes("/src/background/")) {
208
- if (socket?.connected) {
209
- console.log(`[stratos-dev-server] UI source changed: ${path.basename(file)}`);
210
- socket.emit("dev:plugin-ui-updated", {
211
- pluginId,
212
- timestamp: Date.now(),
213
- });
214
- }
215
- }
216
- },
213
+ // Vite handles all HMR natively CSS via style injection, JS/TSX via
214
+ // React Fast Refresh (when @vitejs/plugin-react is configured).
215
+ // The hmrBaseRewrite plugin patches the HMR client's base URL so
216
+ // cross-origin module re-fetching works correctly.
217
+ //
218
+ // Background source changes are still handled separately via
219
+ // fs.watch + dev:plugin-bg-updated (see configureServer above).
217
220
  };
218
221
  }
219
222
  //# sourceMappingURL=stratos-dev-server.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyvexsoftware/stratos-sdk",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Plugin SDK for Stratos — types, hooks, and UI components",
5
5
  "author": {
6
6
  "name": "Skyvex Software",