@lotics/cli 0.30.0 → 0.30.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.
@@ -2,11 +2,16 @@
2
2
  * Builds the wrapper HTML for `lotics app dev`.
3
3
  *
4
4
  * The wrapper page is served at http://localhost:<port>/ and embeds the
5
- * project's Vite dev server in a sandboxed iframe with the same
6
- * sandbox="allow-scripts" attribute production uses. The iframe sends
7
- * postMessage RPCs to this wrapper, which forwards them to the local
8
- * /_rpc endpoint, which dispatches to api.lotics.ai with the CLI's API
9
- * key.
5
+ * project's Vite dev server in an iframe with the same
6
+ * sandbox="allow-scripts allow-same-origin" attribute production uses
7
+ * safe because the Vite iframe is cross-origin to this wrapper (different
8
+ * port), so the app gets its own real origin (and Web Storage) without
9
+ * being able to reach the wrapper. The iframe sends postMessage RPCs to
10
+ * this wrapper, which forwards them to the local /_rpc endpoint, which
11
+ * dispatches to api.lotics.ai with the CLI's API key.
12
+ *
13
+ * postMessage is origin-locked both ways — the wrapper passes its origin
14
+ * via `?lotics_host=` and talks only to the Vite origin.
10
15
  *
11
16
  * Protocol matches `frontend/features/app_ui/app_iframe_host.tsx` exactly:
12
17
  * iframe → wrapper: { id: number, op: string, payload: unknown }
@@ -2,11 +2,16 @@
2
2
  * Builds the wrapper HTML for `lotics app dev`.
3
3
  *
4
4
  * The wrapper page is served at http://localhost:<port>/ and embeds the
5
- * project's Vite dev server in a sandboxed iframe with the same
6
- * sandbox="allow-scripts" attribute production uses. The iframe sends
7
- * postMessage RPCs to this wrapper, which forwards them to the local
8
- * /_rpc endpoint, which dispatches to api.lotics.ai with the CLI's API
9
- * key.
5
+ * project's Vite dev server in an iframe with the same
6
+ * sandbox="allow-scripts allow-same-origin" attribute production uses
7
+ * safe because the Vite iframe is cross-origin to this wrapper (different
8
+ * port), so the app gets its own real origin (and Web Storage) without
9
+ * being able to reach the wrapper. The iframe sends postMessage RPCs to
10
+ * this wrapper, which forwards them to the local /_rpc endpoint, which
11
+ * dispatches to api.lotics.ai with the CLI's API key.
12
+ *
13
+ * postMessage is origin-locked both ways — the wrapper passes its origin
14
+ * via `?lotics_host=` and talks only to the Vite origin.
10
15
  *
11
16
  * Protocol matches `frontend/features/app_ui/app_iframe_host.tsx` exactly:
12
17
  * iframe → wrapper: { id: number, op: string, payload: unknown }
@@ -53,12 +58,19 @@ export function buildWrapperPage(args) {
53
58
  <span>workspace <code>${escapeHtml(workspace_id)}</code></span>
54
59
  <span>RPC → <code>${escapeHtml(api_url)}</code></span>
55
60
  </header>
56
- <iframe id="app" sandbox="allow-scripts" allow="clipboard-write" src="${escapeAttr(vite_url)}"></iframe>
61
+ <iframe id="app" sandbox="allow-scripts allow-same-origin" allow="clipboard-write"></iframe>
57
62
  <script>
58
63
  (function () {
59
64
  const APP_ID = ${JSON.stringify(app_id)};
65
+ const VITE_URL = ${JSON.stringify(vite_url)};
66
+ const VITE_ORIGIN = new URL(VITE_URL).origin;
60
67
  const iframe = document.getElementById("app");
61
68
 
69
+ // Pass the wrapper's own origin to the app via ?lotics_host= so the app
70
+ // SDK can origin-lock its postMessage bridge.
71
+ iframe.src = VITE_URL + (VITE_URL.indexOf("?") >= 0 ? "&" : "?")
72
+ + "lotics_host=" + encodeURIComponent(window.location.origin);
73
+
62
74
  async function rpc(op, payload) {
63
75
  const res = await fetch("/_rpc", {
64
76
  method: "POST",
@@ -104,7 +116,7 @@ export function buildWrapperPage(args) {
104
116
  }
105
117
 
106
118
  window.addEventListener("message", async function (event) {
107
- if (event.source !== iframe.contentWindow) return;
119
+ if (event.source !== iframe.contentWindow || event.origin !== VITE_ORIGIN) return;
108
120
  const msg = event.data;
109
121
  if (!msg || typeof msg.id !== "number" || typeof msg.op !== "string") return;
110
122
  const startedAt = performance.now();
@@ -114,13 +126,16 @@ export function buildWrapperPage(args) {
114
126
  : await rpc(msg.op, msg.payload);
115
127
  const ms = Math.round(performance.now() - startedAt);
116
128
  console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
117
- iframe.contentWindow.postMessage({ id: msg.id, type: "result", data: data }, "*");
129
+ iframe.contentWindow.postMessage(
130
+ { id: msg.id, type: "result", data: data },
131
+ VITE_ORIGIN
132
+ );
118
133
  } catch (err) {
119
134
  const message = err && err.message ? err.message : String(err);
120
135
  console.error("[lotics-dev] " + msg.op + " failed:", message);
121
136
  iframe.contentWindow.postMessage(
122
137
  { id: msg.id, type: "error", message: message },
123
- "*"
138
+ VITE_ORIGIN
124
139
  );
125
140
  }
126
141
  });
@@ -136,6 +151,3 @@ function escapeHtml(s) {
136
151
  .replace(/</g, "&lt;")
137
152
  .replace(/>/g, "&gt;");
138
153
  }
139
- function escapeAttr(s) {
140
- return escapeHtml(s).replace(/"/g, "&quot;");
141
- }
@@ -50,7 +50,7 @@ export function buildStarterTemplate(args) {
50
50
  test: "vitest run",
51
51
  },
52
52
  dependencies: {
53
- "@lotics/app-sdk": "^0.6.0",
53
+ "@lotics/app-sdk": "^0.7.0",
54
54
  "@lotics/ui": "^1.3.0",
55
55
  "@react-native-picker/picker": "^2.7.0",
56
56
  "expo-image": "~3.0.9",
@@ -155,6 +155,11 @@ export default defineConfig({
155
155
  // iframe loads modules from api.lotics.ai which already permits null
156
156
  // origin via CORS.
157
157
  cors: { origin: "*" },
158
+ // @lotics/ui/fonts.css references the API's /iframe/fonts/*.woff2 files
159
+ // by root-relative URL. A deployed app is served from the API origin so
160
+ // they resolve directly; under \`lotics app dev\` the app runs on
161
+ // localhost, so proxy /iframe to the API to load the real fonts.
162
+ proxy: { "/iframe": { target: "https://api.lotics.ai", changeOrigin: true } },
158
163
  },
159
164
  test: {
160
165
  environment: "jsdom",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {