onbuzz 3.3.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.
- package/LICENSE +267 -0
- package/README.md +425 -0
- package/bin/cli.js +556 -0
- package/bin/loxia-terminal-v2.js +162 -0
- package/bin/loxia-terminal.js +90 -0
- package/bin/start-with-terminal.js +200 -0
- package/node_modules/@isaacs/balanced-match/LICENSE.md +23 -0
- package/node_modules/@isaacs/balanced-match/README.md +60 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts +9 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js +59 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/package.json +3 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts +9 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.js +54 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.js.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/package.json +3 -0
- package/node_modules/@isaacs/balanced-match/package.json +79 -0
- package/node_modules/@isaacs/brace-expansion/LICENSE +23 -0
- package/node_modules/@isaacs/brace-expansion/README.md +97 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts +6 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js +199 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/package.json +3 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts +6 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js +195 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/package.json +3 -0
- package/node_modules/@isaacs/brace-expansion/package.json +60 -0
- package/node_modules/glob/LICENSE.md +63 -0
- package/node_modules/glob/README.md +1177 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts +388 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/glob.js +247 -0
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -0
- package/node_modules/glob/dist/commonjs/has-magic.d.ts +14 -0
- package/node_modules/glob/dist/commonjs/has-magic.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
- package/node_modules/glob/dist/commonjs/has-magic.js.map +1 -0
- package/node_modules/glob/dist/commonjs/ignore.d.ts +24 -0
- package/node_modules/glob/dist/commonjs/ignore.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/ignore.js +119 -0
- package/node_modules/glob/dist/commonjs/ignore.js.map +1 -0
- package/node_modules/glob/dist/commonjs/index.d.ts +97 -0
- package/node_modules/glob/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/index.js +68 -0
- package/node_modules/glob/dist/commonjs/index.js.map +1 -0
- package/node_modules/glob/dist/commonjs/index.min.js +4 -0
- package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
- package/node_modules/glob/dist/commonjs/package.json +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts +76 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/pattern.js +219 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -0
- package/node_modules/glob/dist/commonjs/processor.d.ts +59 -0
- package/node_modules/glob/dist/commonjs/processor.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/processor.js +301 -0
- package/node_modules/glob/dist/commonjs/processor.js.map +1 -0
- package/node_modules/glob/dist/commonjs/walker.d.ts +97 -0
- package/node_modules/glob/dist/commonjs/walker.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/walker.js +387 -0
- package/node_modules/glob/dist/commonjs/walker.js.map +1 -0
- package/node_modules/glob/dist/esm/glob.d.ts +388 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/glob.js +243 -0
- package/node_modules/glob/dist/esm/glob.js.map +1 -0
- package/node_modules/glob/dist/esm/has-magic.d.ts +14 -0
- package/node_modules/glob/dist/esm/has-magic.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/has-magic.js +23 -0
- package/node_modules/glob/dist/esm/has-magic.js.map +1 -0
- package/node_modules/glob/dist/esm/ignore.d.ts +24 -0
- package/node_modules/glob/dist/esm/ignore.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/ignore.js +115 -0
- package/node_modules/glob/dist/esm/ignore.js.map +1 -0
- package/node_modules/glob/dist/esm/index.d.ts +97 -0
- package/node_modules/glob/dist/esm/index.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/index.js +55 -0
- package/node_modules/glob/dist/esm/index.js.map +1 -0
- package/node_modules/glob/dist/esm/index.min.js +4 -0
- package/node_modules/glob/dist/esm/index.min.js.map +7 -0
- package/node_modules/glob/dist/esm/package.json +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts +76 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/pattern.js +215 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -0
- package/node_modules/glob/dist/esm/processor.d.ts +59 -0
- package/node_modules/glob/dist/esm/processor.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/processor.js +294 -0
- package/node_modules/glob/dist/esm/processor.js.map +1 -0
- package/node_modules/glob/dist/esm/walker.d.ts +97 -0
- package/node_modules/glob/dist/esm/walker.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/walker.js +381 -0
- package/node_modules/glob/dist/esm/walker.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/LICENSE.md +55 -0
- package/node_modules/glob/node_modules/minimatch/README.md +453 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +2 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +20 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +591 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts +8 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +152 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +15 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +30 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +94 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +1029 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/package.json +3 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +22 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +2 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +20 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +587 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts +8 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +148 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +15 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +26 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +94 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +1016 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/package.json +3 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +22 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +34 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/package.json +67 -0
- package/node_modules/glob/package.json +101 -0
- package/node_modules/minipass/LICENSE +15 -0
- package/node_modules/minipass/README.md +825 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts +549 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/minipass/dist/commonjs/index.js +1028 -0
- package/node_modules/minipass/dist/commonjs/index.js.map +1 -0
- package/node_modules/minipass/dist/commonjs/package.json +3 -0
- package/node_modules/minipass/dist/esm/index.d.ts +549 -0
- package/node_modules/minipass/dist/esm/index.d.ts.map +1 -0
- package/node_modules/minipass/dist/esm/index.js +1018 -0
- package/node_modules/minipass/dist/esm/index.js.map +1 -0
- package/node_modules/minipass/dist/esm/package.json +3 -0
- package/node_modules/minipass/package.json +82 -0
- package/node_modules/package-json-from-dist/LICENSE.md +63 -0
- package/node_modules/package-json-from-dist/README.md +110 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +89 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.js +134 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +1 -0
- package/node_modules/package-json-from-dist/dist/commonjs/package.json +3 -0
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts +89 -0
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +1 -0
- package/node_modules/package-json-from-dist/dist/esm/index.js +129 -0
- package/node_modules/package-json-from-dist/dist/esm/index.js.map +1 -0
- package/node_modules/package-json-from-dist/dist/esm/package.json +3 -0
- package/node_modules/package-json-from-dist/package.json +68 -0
- package/node_modules/path-scurry/LICENSE.md +55 -0
- package/node_modules/path-scurry/README.md +636 -0
- package/node_modules/path-scurry/dist/commonjs/index.d.ts +1115 -0
- package/node_modules/path-scurry/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
- package/node_modules/path-scurry/dist/commonjs/index.js.map +1 -0
- package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
- package/node_modules/path-scurry/dist/esm/index.d.ts +1115 -0
- package/node_modules/path-scurry/dist/esm/index.d.ts.map +1 -0
- package/node_modules/path-scurry/dist/esm/index.js +1983 -0
- package/node_modules/path-scurry/dist/esm/index.js.map +1 -0
- package/node_modules/path-scurry/dist/esm/package.json +3 -0
- package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
- package/node_modules/path-scurry/node_modules/lru-cache/README.md +383 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +1323 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1589 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +1323 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1585 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
- package/node_modules/path-scurry/node_modules/lru-cache/package.json +101 -0
- package/node_modules/path-scurry/package.json +88 -0
- package/node_modules/rimraf/LICENSE.md +55 -0
- package/node_modules/rimraf/README.md +226 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.js +58 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/error.d.ts +6 -0
- package/node_modules/rimraf/dist/commonjs/error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/error.js +10 -0
- package/node_modules/rimraf/dist/commonjs/error.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.js +38 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fs.d.ts +15 -0
- package/node_modules/rimraf/dist/commonjs/fs.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fs.js +33 -0
- package/node_modules/rimraf/dist/commonjs/fs.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.js +24 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/index.d.ts +50 -0
- package/node_modules/rimraf/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/index.js +78 -0
- package/node_modules/rimraf/dist/commonjs/index.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts +34 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.js +53 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/package.json +3 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.js +48 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.js +19 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts +8 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.js +65 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.js +8 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js +138 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.js +24 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.js +103 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.js +159 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/use-native.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/use-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/use-native.js +18 -0
- package/node_modules/rimraf/dist/commonjs/use-native.js.map +1 -0
- package/node_modules/rimraf/dist/esm/bin.d.mts +3 -0
- package/node_modules/rimraf/dist/esm/bin.d.mts.map +1 -0
- package/node_modules/rimraf/dist/esm/bin.mjs +250 -0
- package/node_modules/rimraf/dist/esm/bin.mjs.map +1 -0
- package/node_modules/rimraf/dist/esm/default-tmp.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/default-tmp.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/default-tmp.js +55 -0
- package/node_modules/rimraf/dist/esm/default-tmp.js.map +1 -0
- package/node_modules/rimraf/dist/esm/error.d.ts +6 -0
- package/node_modules/rimraf/dist/esm/error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/error.js +5 -0
- package/node_modules/rimraf/dist/esm/error.js.map +1 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.js +33 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.js.map +1 -0
- package/node_modules/rimraf/dist/esm/fs.d.ts +15 -0
- package/node_modules/rimraf/dist/esm/fs.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/fs.js +18 -0
- package/node_modules/rimraf/dist/esm/fs.js.map +1 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.js +19 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.js.map +1 -0
- package/node_modules/rimraf/dist/esm/index.d.ts +50 -0
- package/node_modules/rimraf/dist/esm/index.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/index.js +70 -0
- package/node_modules/rimraf/dist/esm/index.js.map +1 -0
- package/node_modules/rimraf/dist/esm/opt-arg.d.ts +34 -0
- package/node_modules/rimraf/dist/esm/opt-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/opt-arg.js +46 -0
- package/node_modules/rimraf/dist/esm/opt-arg.js.map +1 -0
- package/node_modules/rimraf/dist/esm/package.json +3 -0
- package/node_modules/rimraf/dist/esm/path-arg.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/path-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/path-arg.js +46 -0
- package/node_modules/rimraf/dist/esm/path-arg.js.map +1 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.js +14 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.js.map +1 -0
- package/node_modules/rimraf/dist/esm/retry-busy.d.ts +8 -0
- package/node_modules/rimraf/dist/esm/retry-busy.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/retry-busy.js +60 -0
- package/node_modules/rimraf/dist/esm/retry-busy.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.js +5 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.js +133 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.js +19 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.js +98 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.js +154 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.js.map +1 -0
- package/node_modules/rimraf/dist/esm/use-native.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/use-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/use-native.js +15 -0
- package/node_modules/rimraf/dist/esm/use-native.js.map +1 -0
- package/node_modules/rimraf/package.json +92 -0
- package/package.json +152 -0
- package/scripts/install-scanners.js +258 -0
- package/scripts/watchdog.js +147 -0
- package/src/analyzers/CSSAnalyzer.js +297 -0
- package/src/analyzers/ConfigValidator.js +690 -0
- package/src/analyzers/ESLintAnalyzer.js +320 -0
- package/src/analyzers/JavaScriptAnalyzer.js +261 -0
- package/src/analyzers/PrettierFormatter.js +247 -0
- package/src/analyzers/PythonAnalyzer.js +283 -0
- package/src/analyzers/SecurityAnalyzer.js +729 -0
- package/src/analyzers/SparrowAnalyzer.js +341 -0
- package/src/analyzers/TypeScriptAnalyzer.js +247 -0
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
- package/src/analyzers/codeCloneDetector/detector.js +250 -0
- package/src/analyzers/codeCloneDetector/index.js +192 -0
- package/src/analyzers/codeCloneDetector/parser.js +199 -0
- package/src/analyzers/codeCloneDetector/reporter.js +148 -0
- package/src/analyzers/codeCloneDetector/scanner.js +88 -0
- package/src/core/agentPool.js +1957 -0
- package/src/core/agentScheduler.js +3212 -0
- package/src/core/contextManager.js +709 -0
- package/src/core/flowExecutor.js +928 -0
- package/src/core/messageProcessor.js +808 -0
- package/src/core/orchestrator.js +584 -0
- package/src/core/stateManager.js +1500 -0
- package/src/index.js +972 -0
- package/src/interfaces/cli.js +553 -0
- package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +208 -0
- package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +236 -0
- package/src/interfaces/terminal/__tests__/smoke/agents.test.js +138 -0
- package/src/interfaces/terminal/__tests__/smoke/components.test.js +137 -0
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +350 -0
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -0
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +332 -0
- package/src/interfaces/terminal/__tests__/smoke/messages.test.js +256 -0
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +388 -0
- package/src/interfaces/terminal/api/apiClient.js +299 -0
- package/src/interfaces/terminal/api/messageRouter.js +262 -0
- package/src/interfaces/terminal/api/session.js +266 -0
- package/src/interfaces/terminal/api/websocket.js +497 -0
- package/src/interfaces/terminal/components/AgentCreator.js +705 -0
- package/src/interfaces/terminal/components/AgentEditor.js +678 -0
- package/src/interfaces/terminal/components/AgentSwitcher.js +330 -0
- package/src/interfaces/terminal/components/ErrorBoundary.js +92 -0
- package/src/interfaces/terminal/components/ErrorPanel.js +264 -0
- package/src/interfaces/terminal/components/Header.js +28 -0
- package/src/interfaces/terminal/components/HelpPanel.js +231 -0
- package/src/interfaces/terminal/components/InputBox.js +118 -0
- package/src/interfaces/terminal/components/Layout.js +603 -0
- package/src/interfaces/terminal/components/LoadingSpinner.js +71 -0
- package/src/interfaces/terminal/components/MessageList.js +281 -0
- package/src/interfaces/terminal/components/MultilineTextInput.js +251 -0
- package/src/interfaces/terminal/components/SearchPanel.js +265 -0
- package/src/interfaces/terminal/components/SettingsPanel.js +415 -0
- package/src/interfaces/terminal/components/StatusBar.js +65 -0
- package/src/interfaces/terminal/components/TextInput.js +127 -0
- package/src/interfaces/terminal/config/agentEditorConstants.js +227 -0
- package/src/interfaces/terminal/config/constants.js +393 -0
- package/src/interfaces/terminal/index.js +168 -0
- package/src/interfaces/terminal/state/useAgentControl.js +496 -0
- package/src/interfaces/terminal/state/useAgents.js +537 -0
- package/src/interfaces/terminal/state/useConnection.js +444 -0
- package/src/interfaces/terminal/state/useMessages.js +630 -0
- package/src/interfaces/terminal/state/useTools.js +554 -0
- package/src/interfaces/terminal/utils/debugLogger.js +44 -0
- package/src/interfaces/terminal/utils/settingsStorage.js +232 -0
- package/src/interfaces/terminal/utils/theme.js +85 -0
- package/src/interfaces/webServer.js +5457 -0
- package/src/modules/fileExplorer/controller.js +413 -0
- package/src/modules/fileExplorer/index.js +37 -0
- package/src/modules/fileExplorer/middleware.js +92 -0
- package/src/modules/fileExplorer/routes.js +158 -0
- package/src/modules/fileExplorer/types.js +44 -0
- package/src/services/agentActivityService.js +399 -0
- package/src/services/aiService.js +2618 -0
- package/src/services/apiKeyManager.js +334 -0
- package/src/services/benchmarkService.js +196 -0
- package/src/services/budgetService.js +565 -0
- package/src/services/contextInjectionService.js +268 -0
- package/src/services/conversationCompactionService.js +1103 -0
- package/src/services/credentialVault.js +685 -0
- package/src/services/errorHandler.js +810 -0
- package/src/services/fileAttachmentService.js +547 -0
- package/src/services/flowContextService.js +189 -0
- package/src/services/memoryService.js +521 -0
- package/src/services/modelRouterService.js +365 -0
- package/src/services/modelsService.js +323 -0
- package/src/services/ollamaService.js +452 -0
- package/src/services/portRegistry.js +336 -0
- package/src/services/portTracker.js +223 -0
- package/src/services/projectDetector.js +404 -0
- package/src/services/promptService.js +372 -0
- package/src/services/qualityInspector.js +796 -0
- package/src/services/scheduleService.js +725 -0
- package/src/services/serviceRegistry.js +386 -0
- package/src/services/skillsService.js +486 -0
- package/src/services/telegramService.js +920 -0
- package/src/services/tokenCountingService.js +316 -0
- package/src/services/visualEditorBridge.js +1033 -0
- package/src/services/visualEditorServer.js +1727 -0
- package/src/services/whatsappService.js +663 -0
- package/src/tools/__tests__/webTool.e2e.test.js +569 -0
- package/src/tools/__tests__/webTool.unit.test.js +195 -0
- package/src/tools/agentCommunicationTool.js +1343 -0
- package/src/tools/agentDelayTool.js +498 -0
- package/src/tools/asyncToolManager.js +604 -0
- package/src/tools/baseTool.js +887 -0
- package/src/tools/browserTool.js +897 -0
- package/src/tools/cloneDetectionTool.js +581 -0
- package/src/tools/codeMapTool.js +857 -0
- package/src/tools/dependencyResolverTool.js +1212 -0
- package/src/tools/docxTool.js +623 -0
- package/src/tools/excelTool.js +636 -0
- package/src/tools/fileContentReplaceTool.js +840 -0
- package/src/tools/fileTreeTool.js +833 -0
- package/src/tools/filesystemTool.js +1217 -0
- package/src/tools/helpTool.js +198 -0
- package/src/tools/imageTool.js +1034 -0
- package/src/tools/importAnalyzerTool.js +1056 -0
- package/src/tools/jobDoneTool.js +388 -0
- package/src/tools/memoryTool.js +554 -0
- package/src/tools/pdfTool.js +627 -0
- package/src/tools/seekTool.js +883 -0
- package/src/tools/skillsTool.js +276 -0
- package/src/tools/staticAnalysisTool.js +2146 -0
- package/src/tools/taskManagerTool.js +2836 -0
- package/src/tools/terminalTool.js +2486 -0
- package/src/tools/userPromptTool.js +474 -0
- package/src/tools/videoTool.js +1139 -0
- package/src/tools/visionTool.js +507 -0
- package/src/tools/visualEditorTool.js +1175 -0
- package/src/tools/webTool.js +3114 -0
- package/src/tools/whatsappTool.js +457 -0
- package/src/types/agent.js +519 -0
- package/src/types/contextReference.js +972 -0
- package/src/types/conversation.js +730 -0
- package/src/types/toolCommand.js +747 -0
- package/src/utilities/attachmentValidator.js +288 -0
- package/src/utilities/browserStealth.js +630 -0
- package/src/utilities/configManager.js +618 -0
- package/src/utilities/constants.js +870 -0
- package/src/utilities/directoryAccessManager.js +566 -0
- package/src/utilities/fileProcessor.js +307 -0
- package/src/utilities/humanBehavior.js +453 -0
- package/src/utilities/jsonRepair.js +242 -0
- package/src/utilities/logger.js +436 -0
- package/src/utilities/platformUtils.js +255 -0
- package/src/utilities/platformUtils.test.js +98 -0
- package/src/utilities/stealthConstants.js +377 -0
- package/src/utilities/structuredFileValidator.js +699 -0
- package/src/utilities/tagParser.js +878 -0
- package/src/utilities/toolConstants.js +415 -0
- package/src/utilities/userDataDir.js +300 -0
- package/web-ui/build/brands/autopilot/favicon.svg +1 -0
- package/web-ui/build/brands/autopilot/logo.webp +0 -0
- package/web-ui/build/brands/onbuzz/favicon.svg +1 -0
- package/web-ui/build/brands/onbuzz/logo-text.webp +0 -0
- package/web-ui/build/brands/onbuzz/logo.webp +0 -0
- package/web-ui/build/index.html +15 -0
- package/web-ui/build/logo.png +0 -0
- package/web-ui/build/logo2.png +0 -0
- package/web-ui/build/static/index-SmQFfvBs.js +746 -0
- package/web-ui/build/static/index-V2ySwjHp.css +1 -0
|
@@ -0,0 +1,1727 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual Editor Server
|
|
3
|
+
*
|
|
4
|
+
* Runs on port 4000 and provides:
|
|
5
|
+
* - Health check endpoint
|
|
6
|
+
* - Editor HTML page for iframe embedding
|
|
7
|
+
* - Proxy to user's running app
|
|
8
|
+
* - WebSocket for backend bridge communication
|
|
9
|
+
* - Element picker overlay injection
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import express from 'express';
|
|
13
|
+
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
14
|
+
import { WebSocketServer } from 'ws';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { Transform } from 'stream';
|
|
18
|
+
import https from 'https';
|
|
19
|
+
import http from 'http';
|
|
20
|
+
import zlib from 'zlib';
|
|
21
|
+
|
|
22
|
+
// Import service registry for port allocation and registration
|
|
23
|
+
import registry, { findFreePort } from './serviceRegistry.js';
|
|
24
|
+
|
|
25
|
+
// Lazy getter for bridge to avoid circular dependency
|
|
26
|
+
// (visualEditorBridge imports from this file)
|
|
27
|
+
let bridgeGetter = null;
|
|
28
|
+
function setBridgeGetter(getter) {
|
|
29
|
+
bridgeGetter = getter;
|
|
30
|
+
}
|
|
31
|
+
function getBridge() {
|
|
32
|
+
if (!bridgeGetter) {
|
|
33
|
+
// Fallback: try dynamic import (async, only for initialization)
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return bridgeGetter();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
40
|
+
const __dirname = path.dirname(__filename);
|
|
41
|
+
|
|
42
|
+
// Service name for registry
|
|
43
|
+
const SERVICE_NAME = 'visualEditor';
|
|
44
|
+
|
|
45
|
+
// Config manager reference (set when initialized from main app)
|
|
46
|
+
let configManagerRef = null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set the config manager reference for reading configuration
|
|
50
|
+
* @param {ConfigManager} configManager - The config manager instance
|
|
51
|
+
*/
|
|
52
|
+
export function setConfigManager(configManager) {
|
|
53
|
+
configManagerRef = configManager;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get configuration value with fallback chain:
|
|
58
|
+
* 1. Config manager (from config file)
|
|
59
|
+
* 2. Environment variable
|
|
60
|
+
* 3. Default value
|
|
61
|
+
*/
|
|
62
|
+
function getConfigValue(configPath, envVar, defaultValue) {
|
|
63
|
+
// Try config manager first
|
|
64
|
+
if (configManagerRef) {
|
|
65
|
+
const configValue = configManagerRef.get(configPath);
|
|
66
|
+
if (configValue !== undefined) {
|
|
67
|
+
return configValue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Try environment variable
|
|
72
|
+
const envValue = process.env[envVar];
|
|
73
|
+
if (envValue !== undefined) {
|
|
74
|
+
// Parse numbers
|
|
75
|
+
if (typeof defaultValue === 'number') {
|
|
76
|
+
const parsed = parseInt(envValue, 10);
|
|
77
|
+
if (!isNaN(parsed)) return parsed;
|
|
78
|
+
}
|
|
79
|
+
return envValue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Return default
|
|
83
|
+
return defaultValue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Hard-coded fallback defaults (used only when nothing else is configured)
|
|
87
|
+
const FALLBACK_PORT = 4000;
|
|
88
|
+
const FALLBACK_APP_URL = 'http://localhost:3000';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the configured port (evaluated at runtime, not module load)
|
|
92
|
+
* @returns {number}
|
|
93
|
+
*/
|
|
94
|
+
function getDefaultPort() {
|
|
95
|
+
return getConfigValue('visualEditor.port', 'LOXIA_VISUAL_EDITOR_PORT', FALLBACK_PORT);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the configured default app URL (evaluated at runtime)
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
function getDefaultAppUrl() {
|
|
103
|
+
return getConfigValue('visualEditor.defaultAppUrl', 'LOXIA_DEFAULT_APP_URL', FALLBACK_APP_URL);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Visual Editor Server class
|
|
108
|
+
*/
|
|
109
|
+
class VisualEditorServer {
|
|
110
|
+
/**
|
|
111
|
+
* @param {Object} config - Configuration options
|
|
112
|
+
* @param {number} config.port - Server port (default: 4000)
|
|
113
|
+
* @param {Object} config.logger - Logger instance
|
|
114
|
+
*/
|
|
115
|
+
constructor(config = {}) {
|
|
116
|
+
this.port = config.port || getDefaultPort();
|
|
117
|
+
this.logger = config.logger || console;
|
|
118
|
+
this.server = null;
|
|
119
|
+
this.wss = null;
|
|
120
|
+
this.app = null;
|
|
121
|
+
this.isRunning = false;
|
|
122
|
+
|
|
123
|
+
// Track active connections
|
|
124
|
+
this.wsConnections = new Map(); // agentId -> WebSocket
|
|
125
|
+
this.activeAppUrls = new Map(); // agentId -> appUrl
|
|
126
|
+
this.staticDirs = new Map(); // agentId -> directory path for static serving
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Register a static directory to serve for an agent
|
|
131
|
+
* @param {string} agentId - Agent identifier
|
|
132
|
+
* @param {string} directory - Directory path to serve
|
|
133
|
+
*/
|
|
134
|
+
registerStaticDir(agentId, directory) {
|
|
135
|
+
this.staticDirs.set(agentId, directory);
|
|
136
|
+
this.logger.info?.(`[VisualEditorServer] Registered static dir for ${agentId}: ${directory}`) ||
|
|
137
|
+
console.log(`[VisualEditorServer] Registered static dir for ${agentId}: ${directory}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Unregister a static directory
|
|
142
|
+
* @param {string} agentId - Agent identifier
|
|
143
|
+
*/
|
|
144
|
+
unregisterStaticDir(agentId) {
|
|
145
|
+
this.staticDirs.delete(agentId);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Start the Visual Editor Server
|
|
150
|
+
* Uses findFreePort to handle port conflicts and registers with service registry
|
|
151
|
+
* @returns {Promise<Object>} Start result
|
|
152
|
+
*/
|
|
153
|
+
async start() {
|
|
154
|
+
if (this.isRunning) {
|
|
155
|
+
return { success: true, port: this.port, message: 'Already running' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Find a free port starting from the preferred port
|
|
159
|
+
const preferredPort = this.port;
|
|
160
|
+
try {
|
|
161
|
+
const actualPort = await findFreePort(preferredPort);
|
|
162
|
+
|
|
163
|
+
if (actualPort !== preferredPort) {
|
|
164
|
+
this.logger.info?.(`[VisualEditorServer] Port ${preferredPort} taken, using ${actualPort}`) ||
|
|
165
|
+
console.log(`[VisualEditorServer] Port ${preferredPort} taken, using ${actualPort}`);
|
|
166
|
+
this.port = actualPort;
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
this.logger.error?.(`[VisualEditorServer] Could not find free port: ${err.message}`);
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.app = express();
|
|
174
|
+
this._setupMiddleware();
|
|
175
|
+
this._setupRoutes();
|
|
176
|
+
|
|
177
|
+
return new Promise((resolve, reject) => {
|
|
178
|
+
this.server = this.app.listen(this.port, '0.0.0.0', () => {
|
|
179
|
+
this.isRunning = true;
|
|
180
|
+
this._setupWebSocketServer();
|
|
181
|
+
|
|
182
|
+
// Register with service registry
|
|
183
|
+
registry.register(SERVICE_NAME, {
|
|
184
|
+
port: this.port,
|
|
185
|
+
host: 'localhost',
|
|
186
|
+
protocol: 'http',
|
|
187
|
+
metadata: {
|
|
188
|
+
wsPath: '/ws',
|
|
189
|
+
startedAt: Date.now()
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
this.logger.info?.(`[VisualEditorServer] Running on port ${this.port}`) ||
|
|
194
|
+
console.log(`[VisualEditorServer] Running on port ${this.port}`);
|
|
195
|
+
|
|
196
|
+
resolve({ success: true, port: this.port });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.server.on('error', (err) => {
|
|
200
|
+
// This shouldn't happen since we checked with findFreePort, but handle anyway
|
|
201
|
+
this.logger.error?.(`[VisualEditorServer] Server error: ${err.message}`);
|
|
202
|
+
reject(err);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Stop the Visual Editor Server
|
|
209
|
+
* @returns {Promise<void>}
|
|
210
|
+
*/
|
|
211
|
+
async stop() {
|
|
212
|
+
if (!this.isRunning) return;
|
|
213
|
+
|
|
214
|
+
// Unregister from service registry
|
|
215
|
+
registry.unregister(SERVICE_NAME);
|
|
216
|
+
|
|
217
|
+
// Close all WebSocket connections
|
|
218
|
+
for (const [agentId, ws] of this.wsConnections.entries()) {
|
|
219
|
+
try {
|
|
220
|
+
ws.close(1000, 'Server shutting down');
|
|
221
|
+
} catch (err) {
|
|
222
|
+
// Ignore
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
this.wsConnections.clear();
|
|
226
|
+
this.activeAppUrls.clear();
|
|
227
|
+
|
|
228
|
+
// Close WebSocket server
|
|
229
|
+
if (this.wss) {
|
|
230
|
+
this.wss.close();
|
|
231
|
+
this.wss = null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Close HTTP server — force-close keep-alive connections
|
|
235
|
+
return new Promise((resolve) => {
|
|
236
|
+
if (this.server) {
|
|
237
|
+
if (typeof this.server.closeAllConnections === 'function') {
|
|
238
|
+
this.server.closeAllConnections();
|
|
239
|
+
}
|
|
240
|
+
this.server.close(() => {
|
|
241
|
+
this.isRunning = false;
|
|
242
|
+
this.server = null;
|
|
243
|
+
this.logger.info?.('[VisualEditorServer] Stopped') ||
|
|
244
|
+
console.log('[VisualEditorServer] Stopped');
|
|
245
|
+
resolve();
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
resolve();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get server status
|
|
255
|
+
* @returns {Object} Status info
|
|
256
|
+
*/
|
|
257
|
+
getStatus() {
|
|
258
|
+
return {
|
|
259
|
+
isRunning: this.isRunning,
|
|
260
|
+
port: this.port,
|
|
261
|
+
activeConnections: this.wsConnections.size,
|
|
262
|
+
connectedAgents: Array.from(this.wsConnections.keys())
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Register an app URL for an agent
|
|
268
|
+
* @param {string} agentId - Agent identifier
|
|
269
|
+
* @param {string} appUrl - User's app URL
|
|
270
|
+
*/
|
|
271
|
+
registerAppUrl(agentId, appUrl) {
|
|
272
|
+
this.activeAppUrls.set(agentId, appUrl);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Unregister an agent's app URL
|
|
277
|
+
* @param {string} agentId - Agent identifier
|
|
278
|
+
*/
|
|
279
|
+
unregisterAppUrl(agentId) {
|
|
280
|
+
this.activeAppUrls.delete(agentId);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Send message to a specific agent's WebSocket
|
|
285
|
+
* @param {string} agentId - Agent identifier
|
|
286
|
+
* @param {Object} message - Message to send
|
|
287
|
+
* @returns {boolean} Success
|
|
288
|
+
*/
|
|
289
|
+
sendToAgent(agentId, message) {
|
|
290
|
+
const ws = this.wsConnections.get(agentId);
|
|
291
|
+
if (ws && ws.readyState === 1) { // WebSocket.OPEN
|
|
292
|
+
try {
|
|
293
|
+
ws.send(JSON.stringify(message));
|
|
294
|
+
return true;
|
|
295
|
+
} catch (err) {
|
|
296
|
+
this.logger.error?.(`[VisualEditorServer] Failed to send to agent ${agentId}:`, err);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Set up Express middleware
|
|
304
|
+
* @private
|
|
305
|
+
*/
|
|
306
|
+
_setupMiddleware() {
|
|
307
|
+
// Request logging for debugging
|
|
308
|
+
this.app.use((req, res, next) => {
|
|
309
|
+
this.logger.debug?.(`[VisualEditorServer] ${req.method} ${req.url}`);
|
|
310
|
+
next();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// CORS for cross-origin requests
|
|
314
|
+
this.app.use((req, res, next) => {
|
|
315
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
316
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
317
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
318
|
+
if (req.method === 'OPTIONS') {
|
|
319
|
+
return res.sendStatus(200);
|
|
320
|
+
}
|
|
321
|
+
next();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Parse JSON bodies
|
|
325
|
+
this.app.use(express.json());
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Set up Express routes
|
|
330
|
+
* @private
|
|
331
|
+
*/
|
|
332
|
+
_setupRoutes() {
|
|
333
|
+
// Health check endpoint
|
|
334
|
+
this.app.get('/health', (req, res) => {
|
|
335
|
+
res.json({
|
|
336
|
+
status: 'ok',
|
|
337
|
+
timestamp: Date.now(),
|
|
338
|
+
connections: this.wsConnections.size
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Test proxy connectivity endpoint (for debugging)
|
|
343
|
+
this.app.get('/test-proxy', async (req, res) => {
|
|
344
|
+
const targetUrl = req.query.url || 'https://httpbin.org/html';
|
|
345
|
+
this.logger.info?.(`[VisualEditorServer] Testing connectivity to: ${targetUrl}`);
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const parsed = new URL(targetUrl);
|
|
349
|
+
const isHttps = parsed.protocol === 'https:';
|
|
350
|
+
const httpModule = isHttps ? https : http;
|
|
351
|
+
|
|
352
|
+
const result = await new Promise((resolve, reject) => {
|
|
353
|
+
const reqOptions = {
|
|
354
|
+
hostname: parsed.hostname,
|
|
355
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
356
|
+
path: parsed.pathname + parsed.search,
|
|
357
|
+
method: 'HEAD', // Just check connectivity, don't download content
|
|
358
|
+
timeout: 10000,
|
|
359
|
+
headers: {
|
|
360
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0'
|
|
361
|
+
},
|
|
362
|
+
rejectUnauthorized: false // Allow self-signed certs
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const request = httpModule.request(reqOptions, (response) => {
|
|
366
|
+
resolve({
|
|
367
|
+
success: true,
|
|
368
|
+
url: targetUrl,
|
|
369
|
+
status: response.statusCode,
|
|
370
|
+
statusText: response.statusMessage,
|
|
371
|
+
contentType: response.headers['content-type']
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
request.on('timeout', () => {
|
|
376
|
+
request.destroy();
|
|
377
|
+
reject(new Error('Connection timed out after 10s'));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
request.on('error', reject);
|
|
381
|
+
request.end();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
res.json(result);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
this.logger.error?.(`[VisualEditorServer] Test connectivity failed:`, err.message);
|
|
387
|
+
res.json({
|
|
388
|
+
success: false,
|
|
389
|
+
url: targetUrl,
|
|
390
|
+
error: err.message,
|
|
391
|
+
code: err.code || 'UNKNOWN'
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Main editor page (served in iframe)
|
|
397
|
+
this.app.get('/', (req, res) => {
|
|
398
|
+
const { agentId, appUrl } = req.query;
|
|
399
|
+
|
|
400
|
+
if (!agentId) {
|
|
401
|
+
return res.status(400).send('Missing agentId parameter');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const targetUrl = appUrl || this.activeAppUrls.get(agentId) || getDefaultAppUrl();
|
|
405
|
+
const html = this._generateEditorHtml(agentId, targetUrl);
|
|
406
|
+
res.type('html').send(html);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Serve overlay script
|
|
410
|
+
this.app.get('/overlay.js', (req, res) => {
|
|
411
|
+
const overlayScript = this._getOverlayScript();
|
|
412
|
+
res.type('application/javascript').send(overlayScript);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Serve static files for agents (for static HTML projects)
|
|
416
|
+
this.app.use('/static/:agentId', (req, res, next) => {
|
|
417
|
+
const { agentId } = req.params;
|
|
418
|
+
const staticDir = this.staticDirs.get(agentId);
|
|
419
|
+
|
|
420
|
+
if (!staticDir) {
|
|
421
|
+
return res.status(404).json({
|
|
422
|
+
error: 'No static directory registered for this agent',
|
|
423
|
+
agentId
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Create static middleware for this directory
|
|
428
|
+
const staticMiddleware = express.static(staticDir, {
|
|
429
|
+
index: ['index.html', 'index.htm'],
|
|
430
|
+
extensions: ['html', 'htm']
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Inject overlay script into HTML files
|
|
434
|
+
const originalSend = res.send.bind(res);
|
|
435
|
+
res.send = (body) => {
|
|
436
|
+
if (typeof body === 'string' && body.includes('</body>')) {
|
|
437
|
+
// Inject overlay script before </body>
|
|
438
|
+
const overlayScript = `<script src="/overlay.js"></script>`;
|
|
439
|
+
body = body.replace('</body>', `${overlayScript}</body>`);
|
|
440
|
+
}
|
|
441
|
+
return originalSend(body);
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
staticMiddleware(req, res, next);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Proxy to user's app with overlay injection
|
|
448
|
+
// Wrap in error handler to catch any proxy initialization errors
|
|
449
|
+
const proxyMiddleware = this._createProxyMiddleware();
|
|
450
|
+
this.app.use('/app', (req, res, next) => {
|
|
451
|
+
try {
|
|
452
|
+
proxyMiddleware(req, res, next);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
this.logger.error?.(`[VisualEditorServer] Proxy middleware error:`, err.message);
|
|
455
|
+
res.status(502).type('html').send(this._generateErrorHtml(
|
|
456
|
+
req.query.target || 'unknown',
|
|
457
|
+
`Proxy error: ${err.message}`
|
|
458
|
+
));
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// 404 catch-all - log and return helpful message
|
|
463
|
+
this.app.use((req, res) => {
|
|
464
|
+
this.logger.warn?.(`[VisualEditorServer] 404: ${req.method} ${req.url}`);
|
|
465
|
+
res.status(404).json({
|
|
466
|
+
error: 'Not found',
|
|
467
|
+
path: req.url,
|
|
468
|
+
hint: 'Use /app?target=URL to proxy to a website'
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Express error handler - catches uncaught errors
|
|
473
|
+
this.app.use((err, req, res, next) => {
|
|
474
|
+
this.logger.error?.(`[VisualEditorServer] Express error:`, err.message);
|
|
475
|
+
if (!res.headersSent) {
|
|
476
|
+
res.status(500).type('html').send(this._generateErrorHtml(
|
|
477
|
+
req.query?.target || req.url,
|
|
478
|
+
`Server error: ${err.message}`
|
|
479
|
+
));
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Create proxy middleware for user's app
|
|
486
|
+
* Uses router option for dynamic target selection based on query param
|
|
487
|
+
* @private
|
|
488
|
+
*/
|
|
489
|
+
_createProxyMiddleware() {
|
|
490
|
+
const self = this;
|
|
491
|
+
|
|
492
|
+
// Store current target URL for use in callbacks
|
|
493
|
+
let currentTargetUrl = getDefaultAppUrl();
|
|
494
|
+
|
|
495
|
+
// http-proxy-middleware v3.x uses 'on' property for event handlers
|
|
496
|
+
return createProxyMiddleware({
|
|
497
|
+
// Use router for dynamic target based on query parameter
|
|
498
|
+
router: (req) => {
|
|
499
|
+
const targetUrl = req.query.target || getDefaultAppUrl();
|
|
500
|
+
currentTargetUrl = targetUrl; // Store for use in callbacks
|
|
501
|
+
|
|
502
|
+
// Validate URL
|
|
503
|
+
try {
|
|
504
|
+
const parsed = new URL(targetUrl);
|
|
505
|
+
self.logger.info?.(`[VisualEditorServer] Proxying to: ${parsed.origin}`);
|
|
506
|
+
return parsed.origin; // Return just the origin (protocol + host + port)
|
|
507
|
+
} catch (err) {
|
|
508
|
+
self.logger.error?.(`[VisualEditorServer] Invalid target URL: ${targetUrl}`);
|
|
509
|
+
return getDefaultAppUrl(); // Fallback to default
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
changeOrigin: true,
|
|
513
|
+
selfHandleResponse: true, // We'll handle response to inject script
|
|
514
|
+
secure: false, // Don't validate SSL certificates (needed for dev servers)
|
|
515
|
+
followRedirects: true, // Follow redirects
|
|
516
|
+
proxyTimeout: 30000, // 30 second proxy timeout
|
|
517
|
+
timeout: 30000, // 30 second request timeout
|
|
518
|
+
pathRewrite: (path, req) => {
|
|
519
|
+
// Get the path from the target URL and append request path
|
|
520
|
+
const targetUrl = req.query.target || getDefaultAppUrl();
|
|
521
|
+
try {
|
|
522
|
+
const parsed = new URL(targetUrl);
|
|
523
|
+
// Start with the path from target URL
|
|
524
|
+
let newPath = parsed.pathname;
|
|
525
|
+
if (newPath === '/') newPath = '';
|
|
526
|
+
|
|
527
|
+
// Parse current request path and remove /app and query params
|
|
528
|
+
const reqUrl = new URL(path, 'http://localhost');
|
|
529
|
+
reqUrl.searchParams.delete('target');
|
|
530
|
+
const reqPath = reqUrl.pathname.replace(/^\/app\/?/, '/');
|
|
531
|
+
|
|
532
|
+
// Combine paths (avoid double slashes)
|
|
533
|
+
const finalPath = newPath + (reqPath === '/' ? '' : reqPath) + reqUrl.search;
|
|
534
|
+
self.logger.debug?.(`[VisualEditorServer] Path rewrite: ${path} -> ${finalPath || '/'}`);
|
|
535
|
+
return finalPath || '/';
|
|
536
|
+
} catch (e) {
|
|
537
|
+
return '/';
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
// v3.x event handlers using 'on' property
|
|
541
|
+
on: {
|
|
542
|
+
proxyReq: (proxyReq, req, res) => {
|
|
543
|
+
const targetUrl = req.query.target || getDefaultAppUrl();
|
|
544
|
+
self.logger.info?.(`[VisualEditorServer] Proxy request to: ${targetUrl}`);
|
|
545
|
+
|
|
546
|
+
// Set browser-like headers to avoid being blocked
|
|
547
|
+
// Wrap in try-catch because headers might already be sent on redirects
|
|
548
|
+
try {
|
|
549
|
+
if (!proxyReq.headersSent) {
|
|
550
|
+
proxyReq.setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
|
551
|
+
proxyReq.setHeader('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8');
|
|
552
|
+
proxyReq.setHeader('Accept-Language', 'en-US,en;q=0.9');
|
|
553
|
+
// IMPORTANT: Request uncompressed content - we need to read/modify HTML
|
|
554
|
+
// If we request gzip, we'd need to decompress before injecting the overlay script
|
|
555
|
+
proxyReq.setHeader('Accept-Encoding', 'identity');
|
|
556
|
+
// Remove headers that might cause issues
|
|
557
|
+
proxyReq.removeHeader('x-forwarded-for');
|
|
558
|
+
proxyReq.removeHeader('x-forwarded-host');
|
|
559
|
+
proxyReq.removeHeader('x-forwarded-proto');
|
|
560
|
+
}
|
|
561
|
+
} catch (e) {
|
|
562
|
+
// Headers already sent (e.g., during redirect) - ignore
|
|
563
|
+
self.logger.debug?.(`[VisualEditorServer] Could not set headers: ${e.message}`);
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
proxyRes: (proxyRes, req, res) => {
|
|
567
|
+
const targetUrl = req.query.target || currentTargetUrl;
|
|
568
|
+
self._handleProxyResponse(proxyRes, req, res, targetUrl);
|
|
569
|
+
},
|
|
570
|
+
error: (err, req, res) => {
|
|
571
|
+
try {
|
|
572
|
+
const targetUrl = req?.query?.target || currentTargetUrl || 'unknown';
|
|
573
|
+
|
|
574
|
+
// Log detailed error information
|
|
575
|
+
self.logger.error?.('[VisualEditorServer] Proxy error:', {
|
|
576
|
+
message: err?.message,
|
|
577
|
+
code: err?.code,
|
|
578
|
+
target: targetUrl,
|
|
579
|
+
url: req?.url
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// Provide more helpful error messages based on error type
|
|
583
|
+
let errorMessage = err?.message || 'Unknown proxy error';
|
|
584
|
+
if (err?.code === 'ECONNREFUSED') {
|
|
585
|
+
errorMessage = `Connection refused - the server at ${targetUrl} is not running or not accepting connections`;
|
|
586
|
+
} else if (err?.code === 'ENOTFOUND') {
|
|
587
|
+
errorMessage = `DNS lookup failed - could not resolve hostname for ${targetUrl}`;
|
|
588
|
+
} else if (err?.code === 'ETIMEDOUT' || err?.code === 'ESOCKETTIMEDOUT') {
|
|
589
|
+
errorMessage = `Connection timed out - the server at ${targetUrl} took too long to respond (30s limit)`;
|
|
590
|
+
} else if (err?.code === 'CERT_HAS_EXPIRED' || err?.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
591
|
+
errorMessage = `SSL certificate error for ${targetUrl}. This may be a self-signed certificate issue.`;
|
|
592
|
+
} else if (err?.code === 'ECONNRESET') {
|
|
593
|
+
errorMessage = `Connection was reset by the server at ${targetUrl}`;
|
|
594
|
+
} else if (err?.code === 'HPE_INVALID_CONSTANT') {
|
|
595
|
+
errorMessage = `Invalid response from ${targetUrl} - the server may not be an HTTP server`;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Return a user-friendly HTML error page
|
|
599
|
+
if (res && !res.headersSent) {
|
|
600
|
+
res.writeHead(502, { 'Content-Type': 'text/html' });
|
|
601
|
+
res.end(self._generateErrorHtml(targetUrl, errorMessage));
|
|
602
|
+
}
|
|
603
|
+
} catch (handlerErr) {
|
|
604
|
+
self.logger.error?.('[VisualEditorServer] Error in error handler:', handlerErr);
|
|
605
|
+
// Last resort - try to send a simple error
|
|
606
|
+
try {
|
|
607
|
+
if (res && !res.headersSent) {
|
|
608
|
+
res.writeHead(502, { 'Content-Type': 'text/plain' });
|
|
609
|
+
res.end('Proxy error: ' + (err?.message || 'Unknown error'));
|
|
610
|
+
}
|
|
611
|
+
} catch (e) {
|
|
612
|
+
// Nothing more we can do
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Generate error HTML page for proxy failures
|
|
622
|
+
* @private
|
|
623
|
+
*/
|
|
624
|
+
_generateErrorHtml(targetUrl, errorDetails) {
|
|
625
|
+
return `<!DOCTYPE html>
|
|
626
|
+
<html>
|
|
627
|
+
<head>
|
|
628
|
+
<title>Connection Error - Visual Editor</title>
|
|
629
|
+
<style>
|
|
630
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
631
|
+
body {
|
|
632
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
633
|
+
background: #f3f4f6;
|
|
634
|
+
min-height: 100vh;
|
|
635
|
+
display: flex;
|
|
636
|
+
align-items: center;
|
|
637
|
+
justify-content: center;
|
|
638
|
+
padding: 20px;
|
|
639
|
+
}
|
|
640
|
+
.error-container {
|
|
641
|
+
background: white;
|
|
642
|
+
border-radius: 12px;
|
|
643
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
644
|
+
padding: 32px;
|
|
645
|
+
max-width: 500px;
|
|
646
|
+
text-align: center;
|
|
647
|
+
}
|
|
648
|
+
.error-icon {
|
|
649
|
+
width: 64px;
|
|
650
|
+
height: 64px;
|
|
651
|
+
margin: 0 auto 16px;
|
|
652
|
+
background: #fef2f2;
|
|
653
|
+
border-radius: 50%;
|
|
654
|
+
display: flex;
|
|
655
|
+
align-items: center;
|
|
656
|
+
justify-content: center;
|
|
657
|
+
}
|
|
658
|
+
.error-icon svg {
|
|
659
|
+
width: 32px;
|
|
660
|
+
height: 32px;
|
|
661
|
+
color: #ef4444;
|
|
662
|
+
}
|
|
663
|
+
h1 {
|
|
664
|
+
color: #1f2937;
|
|
665
|
+
font-size: 20px;
|
|
666
|
+
margin-bottom: 8px;
|
|
667
|
+
}
|
|
668
|
+
.target-url {
|
|
669
|
+
color: #3b82f6;
|
|
670
|
+
font-family: monospace;
|
|
671
|
+
background: #eff6ff;
|
|
672
|
+
padding: 8px 12px;
|
|
673
|
+
border-radius: 6px;
|
|
674
|
+
margin: 16px 0;
|
|
675
|
+
word-break: break-all;
|
|
676
|
+
}
|
|
677
|
+
.instructions {
|
|
678
|
+
color: #6b7280;
|
|
679
|
+
font-size: 14px;
|
|
680
|
+
line-height: 1.6;
|
|
681
|
+
margin-top: 16px;
|
|
682
|
+
}
|
|
683
|
+
.instructions ol {
|
|
684
|
+
text-align: left;
|
|
685
|
+
padding-left: 20px;
|
|
686
|
+
margin-top: 12px;
|
|
687
|
+
}
|
|
688
|
+
.instructions li {
|
|
689
|
+
margin-bottom: 8px;
|
|
690
|
+
}
|
|
691
|
+
.retry-btn {
|
|
692
|
+
margin-top: 20px;
|
|
693
|
+
padding: 10px 24px;
|
|
694
|
+
background: #3b82f6;
|
|
695
|
+
color: white;
|
|
696
|
+
border: none;
|
|
697
|
+
border-radius: 6px;
|
|
698
|
+
font-size: 14px;
|
|
699
|
+
cursor: pointer;
|
|
700
|
+
transition: background 0.2s;
|
|
701
|
+
}
|
|
702
|
+
.retry-btn:hover {
|
|
703
|
+
background: #2563eb;
|
|
704
|
+
}
|
|
705
|
+
.error-details {
|
|
706
|
+
margin-top: 16px;
|
|
707
|
+
padding: 12px;
|
|
708
|
+
background: #fef2f2;
|
|
709
|
+
border-radius: 6px;
|
|
710
|
+
color: #991b1b;
|
|
711
|
+
font-size: 12px;
|
|
712
|
+
font-family: monospace;
|
|
713
|
+
}
|
|
714
|
+
</style>
|
|
715
|
+
</head>
|
|
716
|
+
<body>
|
|
717
|
+
<div class="error-container">
|
|
718
|
+
<div class="error-icon">
|
|
719
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
720
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
721
|
+
</svg>
|
|
722
|
+
</div>
|
|
723
|
+
<h1>Cannot Connect to Your App</h1>
|
|
724
|
+
<div class="target-url">${targetUrl}</div>
|
|
725
|
+
<div class="instructions">
|
|
726
|
+
<p>Make sure your app is running at this address.</p>
|
|
727
|
+
<ol>
|
|
728
|
+
<li>Start your development server (e.g., <code>npm run dev</code>)</li>
|
|
729
|
+
<li>Enter the correct URL in the address bar above</li>
|
|
730
|
+
<li>Click "Go" or retry below</li>
|
|
731
|
+
</ol>
|
|
732
|
+
</div>
|
|
733
|
+
<button class="retry-btn" onclick="location.reload()">Retry Connection</button>
|
|
734
|
+
<div class="error-details">${errorDetails}</div>
|
|
735
|
+
</div>
|
|
736
|
+
</body>
|
|
737
|
+
</html>`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Handle proxy response - inject overlay script into HTML
|
|
742
|
+
* @private
|
|
743
|
+
*/
|
|
744
|
+
_handleProxyResponse(proxyRes, req, res, targetUrl) {
|
|
745
|
+
const contentType = proxyRes.headers['content-type'] || '';
|
|
746
|
+
const contentEncoding = proxyRes.headers['content-encoding'] || '';
|
|
747
|
+
|
|
748
|
+
// Copy headers (skip content-length and content-encoding as we'll modify content)
|
|
749
|
+
Object.keys(proxyRes.headers).forEach(key => {
|
|
750
|
+
const lowerKey = key.toLowerCase();
|
|
751
|
+
if (lowerKey !== 'content-length' && lowerKey !== 'content-encoding') {
|
|
752
|
+
res.setHeader(key, proxyRes.headers[key]);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
res.status(proxyRes.statusCode);
|
|
757
|
+
|
|
758
|
+
// Only inject into HTML responses
|
|
759
|
+
if (contentType.includes('text/html')) {
|
|
760
|
+
const chunks = [];
|
|
761
|
+
|
|
762
|
+
// Collect all data chunks
|
|
763
|
+
proxyRes.on('data', (chunk) => {
|
|
764
|
+
chunks.push(chunk);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
proxyRes.on('end', () => {
|
|
768
|
+
// Combine chunks into a single buffer
|
|
769
|
+
const buffer = Buffer.concat(chunks);
|
|
770
|
+
|
|
771
|
+
// Decompress if needed (some servers ignore Accept-Encoding: identity)
|
|
772
|
+
this._decompressBuffer(buffer, contentEncoding)
|
|
773
|
+
.then(decompressed => {
|
|
774
|
+
const body = decompressed.toString('utf-8');
|
|
775
|
+
// Inject overlay script before </body>
|
|
776
|
+
const injectedHtml = this._injectOverlayScript(body, targetUrl);
|
|
777
|
+
res.send(injectedHtml);
|
|
778
|
+
})
|
|
779
|
+
.catch(err => {
|
|
780
|
+
this.logger.error?.(`[VisualEditorServer] Decompression error: ${err.message}`);
|
|
781
|
+
// Try to send as-is (might be uncompressed despite header)
|
|
782
|
+
try {
|
|
783
|
+
const body = buffer.toString('utf-8');
|
|
784
|
+
const injectedHtml = this._injectOverlayScript(body, targetUrl);
|
|
785
|
+
res.send(injectedHtml);
|
|
786
|
+
} catch (e) {
|
|
787
|
+
res.status(500).send('Error processing response');
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
} else {
|
|
792
|
+
// Pass through non-HTML responses
|
|
793
|
+
proxyRes.pipe(res);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Decompress buffer based on content-encoding
|
|
799
|
+
* @private
|
|
800
|
+
*/
|
|
801
|
+
async _decompressBuffer(buffer, encoding) {
|
|
802
|
+
if (!encoding || encoding === 'identity') {
|
|
803
|
+
return buffer;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return new Promise((resolve, reject) => {
|
|
807
|
+
if (encoding === 'gzip') {
|
|
808
|
+
zlib.gunzip(buffer, (err, result) => {
|
|
809
|
+
if (err) reject(err);
|
|
810
|
+
else resolve(result);
|
|
811
|
+
});
|
|
812
|
+
} else if (encoding === 'deflate') {
|
|
813
|
+
zlib.inflate(buffer, (err, result) => {
|
|
814
|
+
if (err) reject(err);
|
|
815
|
+
else resolve(result);
|
|
816
|
+
});
|
|
817
|
+
} else if (encoding === 'br') {
|
|
818
|
+
zlib.brotliDecompress(buffer, (err, result) => {
|
|
819
|
+
if (err) reject(err);
|
|
820
|
+
else resolve(result);
|
|
821
|
+
});
|
|
822
|
+
} else {
|
|
823
|
+
// Unknown encoding, try to use as-is
|
|
824
|
+
resolve(buffer);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Inject overlay script into HTML
|
|
831
|
+
* @private
|
|
832
|
+
*/
|
|
833
|
+
_injectOverlayScript(html, targetUrl) {
|
|
834
|
+
// Use ABSOLUTE URL for overlay.js since we inject a <base> tag that would redirect relative paths
|
|
835
|
+
const overlayUrl = `http://localhost:${this.port}/overlay.js`;
|
|
836
|
+
const scriptTag = `
|
|
837
|
+
<!-- Loxia Visual Editor Overlay -->
|
|
838
|
+
<script src="${overlayUrl}"></script>
|
|
839
|
+
`;
|
|
840
|
+
|
|
841
|
+
// Add a <base> tag to make relative URLs resolve to the original site
|
|
842
|
+
// This prevents assets (scripts, styles, images) from being requested through our server
|
|
843
|
+
let baseTag = '';
|
|
844
|
+
try {
|
|
845
|
+
const parsed = new URL(targetUrl);
|
|
846
|
+
baseTag = `<base href="${parsed.origin}/">`;
|
|
847
|
+
} catch (e) {
|
|
848
|
+
// Invalid URL, skip base tag
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
let modifiedHtml = html;
|
|
852
|
+
|
|
853
|
+
// Inject base tag after <head> (if not already present)
|
|
854
|
+
if (baseTag && !html.toLowerCase().includes('<base ')) {
|
|
855
|
+
if (html.includes('<head>')) {
|
|
856
|
+
modifiedHtml = modifiedHtml.replace('<head>', `<head>${baseTag}`);
|
|
857
|
+
} else if (html.includes('<HEAD>')) {
|
|
858
|
+
modifiedHtml = modifiedHtml.replace('<HEAD>', `<HEAD>${baseTag}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Inject overlay script before </body> or at end
|
|
863
|
+
if (modifiedHtml.includes('</body>')) {
|
|
864
|
+
return modifiedHtml.replace('</body>', `${scriptTag}</body>`);
|
|
865
|
+
} else if (modifiedHtml.includes('</html>')) {
|
|
866
|
+
return modifiedHtml.replace('</html>', `${scriptTag}</html>`);
|
|
867
|
+
} else {
|
|
868
|
+
return modifiedHtml + scriptTag;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Set up WebSocket server for backend bridge communication
|
|
874
|
+
* @private
|
|
875
|
+
*/
|
|
876
|
+
_setupWebSocketServer() {
|
|
877
|
+
this.wss = new WebSocketServer({
|
|
878
|
+
server: this.server,
|
|
879
|
+
path: '/ws'
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
this.wss.on('connection', (ws, req) => {
|
|
883
|
+
const url = new URL(req.url, `http://localhost:${this.port}`);
|
|
884
|
+
const agentId = url.searchParams.get('agentId');
|
|
885
|
+
|
|
886
|
+
if (!agentId) {
|
|
887
|
+
ws.close(1008, 'Missing agentId');
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
this.logger.info?.(`[VisualEditorServer] WebSocket connected: ${agentId}`);
|
|
892
|
+
|
|
893
|
+
// Store connection
|
|
894
|
+
this.wsConnections.set(agentId, ws);
|
|
895
|
+
|
|
896
|
+
// Send ready message
|
|
897
|
+
ws.send(JSON.stringify({
|
|
898
|
+
type: 'editor-ready',
|
|
899
|
+
agentId,
|
|
900
|
+
timestamp: Date.now()
|
|
901
|
+
}));
|
|
902
|
+
|
|
903
|
+
// Handle incoming messages
|
|
904
|
+
ws.on('message', (data) => {
|
|
905
|
+
this._handleWebSocketMessage(agentId, data, ws);
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// Handle close
|
|
909
|
+
ws.on('close', (code, reason) => {
|
|
910
|
+
this.logger.info?.(`[VisualEditorServer] WebSocket closed: ${agentId} (${code})`);
|
|
911
|
+
this.wsConnections.delete(agentId);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
// Handle errors
|
|
915
|
+
ws.on('error', (err) => {
|
|
916
|
+
this.logger.error?.(`[VisualEditorServer] WebSocket error for ${agentId}:`, err);
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Handle incoming WebSocket messages
|
|
923
|
+
* @private
|
|
924
|
+
*/
|
|
925
|
+
_handleWebSocketMessage(agentId, data, ws) {
|
|
926
|
+
try {
|
|
927
|
+
const message = JSON.parse(data.toString());
|
|
928
|
+
|
|
929
|
+
this.logger.debug?.(`[VisualEditorServer] Message from ${agentId}:`, message.type);
|
|
930
|
+
|
|
931
|
+
switch (message.type) {
|
|
932
|
+
case 'element-selected':
|
|
933
|
+
// Forward element selection (already handled by postMessage to web-ui)
|
|
934
|
+
// This is for backend bridge to receive selections
|
|
935
|
+
this._emitElementSelected(agentId, message);
|
|
936
|
+
break;
|
|
937
|
+
|
|
938
|
+
case 'ping':
|
|
939
|
+
ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
|
|
940
|
+
break;
|
|
941
|
+
|
|
942
|
+
case 'highlight':
|
|
943
|
+
case 'scroll-to':
|
|
944
|
+
case 'reload':
|
|
945
|
+
// These commands come from backend, forward to any connected editor pages
|
|
946
|
+
// (handled via the editor page's WebSocket connection)
|
|
947
|
+
break;
|
|
948
|
+
|
|
949
|
+
case 'subscribe':
|
|
950
|
+
// Agent subscribing to editor events
|
|
951
|
+
if (message.appUrl) {
|
|
952
|
+
this.activeAppUrls.set(agentId, message.appUrl);
|
|
953
|
+
}
|
|
954
|
+
break;
|
|
955
|
+
|
|
956
|
+
case 'unsubscribe':
|
|
957
|
+
this.activeAppUrls.delete(agentId);
|
|
958
|
+
break;
|
|
959
|
+
|
|
960
|
+
default:
|
|
961
|
+
this.logger.debug?.(`[VisualEditorServer] Unknown message type: ${message.type}`);
|
|
962
|
+
}
|
|
963
|
+
} catch (err) {
|
|
964
|
+
this.logger.error?.('[VisualEditorServer] Invalid WebSocket message:', err);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Emit element selected event (for external listeners)
|
|
970
|
+
* @private
|
|
971
|
+
*/
|
|
972
|
+
_emitElementSelected(agentId, message) {
|
|
973
|
+
const elementData = message.data || message;
|
|
974
|
+
|
|
975
|
+
this.logger.info?.(`[VisualEditorServer] Element selected for ${agentId}:`, {
|
|
976
|
+
selector: elementData.selector,
|
|
977
|
+
component: elementData.sourceHint?.component
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
// Forward to visualEditorBridge so context is available for message injection
|
|
981
|
+
const bridge = getBridge();
|
|
982
|
+
if (bridge && bridge.hasInstance(agentId)) {
|
|
983
|
+
const success = bridge.setVisualContext(agentId, {
|
|
984
|
+
selector: elementData.selector,
|
|
985
|
+
tagName: elementData.tagName,
|
|
986
|
+
text: elementData.text,
|
|
987
|
+
attributes: elementData.attributes,
|
|
988
|
+
boundingRect: elementData.boundingRect,
|
|
989
|
+
sourceHint: elementData.sourceHint,
|
|
990
|
+
computedStyle: elementData.computedStyle
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
if (success) {
|
|
994
|
+
this.logger.info?.(`[VisualEditorServer] Visual context synced to bridge for ${agentId}`);
|
|
995
|
+
} else {
|
|
996
|
+
this.logger.warn?.(`[VisualEditorServer] Failed to sync visual context for ${agentId}`);
|
|
997
|
+
}
|
|
998
|
+
} else {
|
|
999
|
+
this.logger.debug?.(`[VisualEditorServer] No bridge instance for ${agentId}, context not synced`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Generate editor HTML page
|
|
1005
|
+
* @private
|
|
1006
|
+
*/
|
|
1007
|
+
_generateEditorHtml(agentId, appUrl) {
|
|
1008
|
+
const encodedAppUrl = encodeURIComponent(appUrl);
|
|
1009
|
+
|
|
1010
|
+
return `<!DOCTYPE html>
|
|
1011
|
+
<html>
|
|
1012
|
+
<head>
|
|
1013
|
+
<meta charset="UTF-8">
|
|
1014
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1015
|
+
<title>Loxia Visual Editor</title>
|
|
1016
|
+
<style>
|
|
1017
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1018
|
+
html, body { height: 100%; overflow: hidden; }
|
|
1019
|
+
body { font-family: system-ui, -apple-system, sans-serif; }
|
|
1020
|
+
|
|
1021
|
+
#app-frame {
|
|
1022
|
+
width: 100%;
|
|
1023
|
+
height: 100%;
|
|
1024
|
+
border: none;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
#loading-overlay {
|
|
1028
|
+
position: fixed;
|
|
1029
|
+
top: 0;
|
|
1030
|
+
left: 0;
|
|
1031
|
+
right: 0;
|
|
1032
|
+
bottom: 0;
|
|
1033
|
+
background: #f3f4f6;
|
|
1034
|
+
display: flex;
|
|
1035
|
+
flex-direction: column;
|
|
1036
|
+
align-items: center;
|
|
1037
|
+
justify-content: center;
|
|
1038
|
+
z-index: 1000;
|
|
1039
|
+
transition: opacity 0.3s ease;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
#loading-overlay.hidden {
|
|
1043
|
+
opacity: 0;
|
|
1044
|
+
pointer-events: none;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
.spinner {
|
|
1048
|
+
width: 40px;
|
|
1049
|
+
height: 40px;
|
|
1050
|
+
border: 3px solid #e5e7eb;
|
|
1051
|
+
border-top-color: #3b82f6;
|
|
1052
|
+
border-radius: 50%;
|
|
1053
|
+
animation: spin 1s linear infinite;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
@keyframes spin {
|
|
1057
|
+
to { transform: rotate(360deg); }
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.loading-text {
|
|
1061
|
+
margin-top: 16px;
|
|
1062
|
+
color: #6b7280;
|
|
1063
|
+
font-size: 14px;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
#error-message {
|
|
1067
|
+
display: none;
|
|
1068
|
+
position: fixed;
|
|
1069
|
+
top: 50%;
|
|
1070
|
+
left: 50%;
|
|
1071
|
+
transform: translate(-50%, -50%);
|
|
1072
|
+
background: white;
|
|
1073
|
+
padding: 24px;
|
|
1074
|
+
border-radius: 8px;
|
|
1075
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
1076
|
+
text-align: center;
|
|
1077
|
+
max-width: 400px;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
#error-message h3 {
|
|
1081
|
+
color: #ef4444;
|
|
1082
|
+
margin-bottom: 8px;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
#error-message p {
|
|
1086
|
+
color: #6b7280;
|
|
1087
|
+
font-size: 14px;
|
|
1088
|
+
}
|
|
1089
|
+
</style>
|
|
1090
|
+
</head>
|
|
1091
|
+
<body>
|
|
1092
|
+
<div id="loading-overlay">
|
|
1093
|
+
<div class="spinner"></div>
|
|
1094
|
+
<p class="loading-text">Loading preview...</p>
|
|
1095
|
+
</div>
|
|
1096
|
+
|
|
1097
|
+
<div id="error-message">
|
|
1098
|
+
<h3>Connection Error</h3>
|
|
1099
|
+
<p id="error-text">Could not load the preview.</p>
|
|
1100
|
+
</div>
|
|
1101
|
+
|
|
1102
|
+
<iframe
|
|
1103
|
+
id="app-frame"
|
|
1104
|
+
src="/app?target=${encodedAppUrl}"
|
|
1105
|
+
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals"
|
|
1106
|
+
></iframe>
|
|
1107
|
+
|
|
1108
|
+
<script>
|
|
1109
|
+
const agentId = '${agentId}';
|
|
1110
|
+
const appUrl = '${appUrl}';
|
|
1111
|
+
|
|
1112
|
+
// Hide loading overlay when iframe loads
|
|
1113
|
+
const iframe = document.getElementById('app-frame');
|
|
1114
|
+
const loadingOverlay = document.getElementById('loading-overlay');
|
|
1115
|
+
const errorMessage = document.getElementById('error-message');
|
|
1116
|
+
const errorText = document.getElementById('error-text');
|
|
1117
|
+
|
|
1118
|
+
iframe.onload = () => {
|
|
1119
|
+
loadingOverlay.classList.add('hidden');
|
|
1120
|
+
|
|
1121
|
+
// Check if the iframe loaded our error page (proxy failure)
|
|
1122
|
+
try {
|
|
1123
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
|
1124
|
+
const title = iframeDoc.title || '';
|
|
1125
|
+
if (title.includes('Connection Error') || title.includes('Cannot Connect')) {
|
|
1126
|
+
// Proxy returned an error page - show error message
|
|
1127
|
+
errorText.textContent = 'Could not proxy to ' + appUrl + '. See the error details in the preview.';
|
|
1128
|
+
errorMessage.style.display = 'block';
|
|
1129
|
+
}
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
// Cross-origin - can't check, but that usually means it loaded successfully
|
|
1132
|
+
console.log('[Visual Editor] Cross-origin iframe loaded - assuming success');
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
iframe.onerror = () => {
|
|
1137
|
+
loadingOverlay.classList.add('hidden');
|
|
1138
|
+
errorText.textContent = 'Could not connect to ' + appUrl + '. Make sure your app is running.';
|
|
1139
|
+
errorMessage.style.display = 'block';
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// Forward messages from app iframe to parent (Loxia Web-UI)
|
|
1143
|
+
window.addEventListener('message', (e) => {
|
|
1144
|
+
// Only forward element-selected messages
|
|
1145
|
+
if (e.data && e.data.type === 'element-selected') {
|
|
1146
|
+
// Forward to parent window (Loxia Web-UI)
|
|
1147
|
+
window.parent.postMessage(e.data, '*');
|
|
1148
|
+
|
|
1149
|
+
// Also send via WebSocket to backend
|
|
1150
|
+
if (window.wsConnection && window.wsConnection.readyState === 1) {
|
|
1151
|
+
window.wsConnection.send(JSON.stringify({
|
|
1152
|
+
...e.data,
|
|
1153
|
+
agentId
|
|
1154
|
+
}));
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// Listen for commands from parent (Loxia Web-UI) or WebSocket
|
|
1160
|
+
window.addEventListener('message', (e) => {
|
|
1161
|
+
if (e.data && (e.data.type === 'highlight' || e.data.type === 'scroll-to')) {
|
|
1162
|
+
// Forward to app iframe
|
|
1163
|
+
iframe.contentWindow.postMessage(e.data, '*');
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
// Connect to WebSocket for backend communication
|
|
1168
|
+
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1169
|
+
const wsUrl = wsProtocol + '//' + location.host + '/ws?agentId=' + agentId;
|
|
1170
|
+
|
|
1171
|
+
function connectWebSocket() {
|
|
1172
|
+
window.wsConnection = new WebSocket(wsUrl);
|
|
1173
|
+
|
|
1174
|
+
window.wsConnection.onopen = () => {
|
|
1175
|
+
console.log('[Visual Editor] WebSocket connected');
|
|
1176
|
+
// Notify parent that editor is ready
|
|
1177
|
+
window.parent.postMessage({ type: 'editor-ready' }, '*');
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
window.wsConnection.onmessage = (e) => {
|
|
1181
|
+
try {
|
|
1182
|
+
const msg = JSON.parse(e.data);
|
|
1183
|
+
|
|
1184
|
+
// Forward commands to app iframe
|
|
1185
|
+
if (msg.type === 'highlight' || msg.type === 'scroll-to' || msg.type === 'reload') {
|
|
1186
|
+
iframe.contentWindow.postMessage(msg, '*');
|
|
1187
|
+
}
|
|
1188
|
+
} catch (err) {
|
|
1189
|
+
console.error('[Visual Editor] Invalid message:', err);
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
window.wsConnection.onerror = (e) => {
|
|
1194
|
+
console.error('[Visual Editor] WebSocket error');
|
|
1195
|
+
window.parent.postMessage({
|
|
1196
|
+
type: 'editor-error',
|
|
1197
|
+
data: { message: 'WebSocket connection error' }
|
|
1198
|
+
}, '*');
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
window.wsConnection.onclose = () => {
|
|
1202
|
+
console.log('[Visual Editor] WebSocket closed, reconnecting in 3s...');
|
|
1203
|
+
setTimeout(connectWebSocket, 3000);
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
connectWebSocket();
|
|
1208
|
+
|
|
1209
|
+
// Handle iframe load timeout (35s to allow proxy's 30s timeout to report actual error)
|
|
1210
|
+
setTimeout(() => {
|
|
1211
|
+
if (!loadingOverlay.classList.contains('hidden')) {
|
|
1212
|
+
loadingOverlay.classList.add('hidden');
|
|
1213
|
+
errorText.textContent = 'Preview is taking too long to load. Check if ' + appUrl + ' is accessible and responding.';
|
|
1214
|
+
errorMessage.style.display = 'block';
|
|
1215
|
+
}
|
|
1216
|
+
}, 35000);
|
|
1217
|
+
</script>
|
|
1218
|
+
</body>
|
|
1219
|
+
</html>`;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Get overlay script for element selection
|
|
1224
|
+
* @private
|
|
1225
|
+
*/
|
|
1226
|
+
_getOverlayScript() {
|
|
1227
|
+
return `/**
|
|
1228
|
+
* Loxia Visual Editor Overlay Script
|
|
1229
|
+
* Injected into user's app for element selection
|
|
1230
|
+
*/
|
|
1231
|
+
(function() {
|
|
1232
|
+
'use strict';
|
|
1233
|
+
|
|
1234
|
+
// Prevent double injection
|
|
1235
|
+
if (window.__LOXIA_VISUAL_EDITOR_LOADED__) return;
|
|
1236
|
+
window.__LOXIA_VISUAL_EDITOR_LOADED__ = true;
|
|
1237
|
+
|
|
1238
|
+
let isEnabled = true;
|
|
1239
|
+
let hoveredElement = null;
|
|
1240
|
+
let selectedElement = null;
|
|
1241
|
+
let highlightOverlay = null;
|
|
1242
|
+
let selectionOverlay = null;
|
|
1243
|
+
let tooltip = null;
|
|
1244
|
+
|
|
1245
|
+
// Apply cursor style based on mode
|
|
1246
|
+
function updateCursorStyle(enabled) {
|
|
1247
|
+
document.body.style.cursor = enabled ? 'crosshair' : '';
|
|
1248
|
+
}
|
|
1249
|
+
updateCursorStyle(true);
|
|
1250
|
+
|
|
1251
|
+
// Create highlight overlay element (hover)
|
|
1252
|
+
function createHighlightOverlay() {
|
|
1253
|
+
const overlay = document.createElement('div');
|
|
1254
|
+
overlay.id = 'loxia-highlight-overlay';
|
|
1255
|
+
overlay.style.cssText = \`
|
|
1256
|
+
position: fixed;
|
|
1257
|
+
pointer-events: none;
|
|
1258
|
+
background: rgba(59, 130, 246, 0.1);
|
|
1259
|
+
border: 2px solid #3b82f6;
|
|
1260
|
+
border-radius: 4px;
|
|
1261
|
+
z-index: 999998;
|
|
1262
|
+
transition: all 0.1s ease;
|
|
1263
|
+
display: none;
|
|
1264
|
+
\`;
|
|
1265
|
+
document.body.appendChild(overlay);
|
|
1266
|
+
return overlay;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Create selection overlay element (click)
|
|
1270
|
+
function createSelectionOverlay() {
|
|
1271
|
+
const overlay = document.createElement('div');
|
|
1272
|
+
overlay.id = 'loxia-selection-overlay';
|
|
1273
|
+
overlay.style.cssText = \`
|
|
1274
|
+
position: fixed;
|
|
1275
|
+
pointer-events: none;
|
|
1276
|
+
background: rgba(34, 197, 94, 0.15);
|
|
1277
|
+
border: 2px solid #22c55e;
|
|
1278
|
+
border-radius: 4px;
|
|
1279
|
+
z-index: 999999;
|
|
1280
|
+
display: none;
|
|
1281
|
+
\`;
|
|
1282
|
+
document.body.appendChild(overlay);
|
|
1283
|
+
return overlay;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// Create tooltip for element info
|
|
1287
|
+
function createTooltip() {
|
|
1288
|
+
const tip = document.createElement('div');
|
|
1289
|
+
tip.id = 'loxia-tooltip';
|
|
1290
|
+
tip.style.cssText = \`
|
|
1291
|
+
position: fixed;
|
|
1292
|
+
background: #1f2937;
|
|
1293
|
+
color: white;
|
|
1294
|
+
padding: 4px 8px;
|
|
1295
|
+
border-radius: 4px;
|
|
1296
|
+
font-size: 12px;
|
|
1297
|
+
font-family: ui-monospace, monospace;
|
|
1298
|
+
z-index: 1000000;
|
|
1299
|
+
pointer-events: none;
|
|
1300
|
+
display: none;
|
|
1301
|
+
max-width: 300px;
|
|
1302
|
+
overflow: hidden;
|
|
1303
|
+
text-overflow: ellipsis;
|
|
1304
|
+
white-space: nowrap;
|
|
1305
|
+
\`;
|
|
1306
|
+
document.body.appendChild(tip);
|
|
1307
|
+
return tip;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Generate CSS selector for element
|
|
1311
|
+
function getSelector(el) {
|
|
1312
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
1313
|
+
return el ? el.tagName.toLowerCase() : '';
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// Try ID first
|
|
1317
|
+
if (el.id && /^[a-zA-Z][\\w-]*$/.test(el.id)) {
|
|
1318
|
+
return '#' + el.id;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
let path = [];
|
|
1322
|
+
let current = el;
|
|
1323
|
+
|
|
1324
|
+
while (current && current !== document.body && path.length < 5) {
|
|
1325
|
+
let selector = current.tagName.toLowerCase();
|
|
1326
|
+
|
|
1327
|
+
// Add id if available
|
|
1328
|
+
if (current.id && /^[a-zA-Z][\\w-]*$/.test(current.id)) {
|
|
1329
|
+
path.unshift('#' + current.id);
|
|
1330
|
+
break;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Add meaningful classes (skip utility classes)
|
|
1334
|
+
if (current.className && typeof current.className === 'string') {
|
|
1335
|
+
const classes = current.className
|
|
1336
|
+
.split(/\\s+/)
|
|
1337
|
+
.filter(c => c && c.length > 2 && !c.match(/^(w-|h-|p-|m-|text-|bg-|flex|grid|block|inline)/))
|
|
1338
|
+
.slice(0, 2);
|
|
1339
|
+
if (classes.length) {
|
|
1340
|
+
selector += '.' + classes.join('.');
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Add nth-child if needed for uniqueness
|
|
1345
|
+
const siblings = current.parentElement ?
|
|
1346
|
+
Array.from(current.parentElement.children).filter(s => s.tagName === current.tagName) : [];
|
|
1347
|
+
if (siblings.length > 1) {
|
|
1348
|
+
const index = siblings.indexOf(current) + 1;
|
|
1349
|
+
selector += ':nth-child(' + index + ')';
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
path.unshift(selector);
|
|
1353
|
+
current = current.parentElement;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
return path.join(' > ');
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Try to get React component info
|
|
1360
|
+
function getReactInfo(el) {
|
|
1361
|
+
// Look for React fiber
|
|
1362
|
+
const fiberKey = Object.keys(el).find(k =>
|
|
1363
|
+
k.startsWith('__reactFiber') || k.startsWith('__reactInternalInstance')
|
|
1364
|
+
);
|
|
1365
|
+
|
|
1366
|
+
if (!fiberKey) return null;
|
|
1367
|
+
|
|
1368
|
+
let fiber = el[fiberKey];
|
|
1369
|
+
let depth = 0;
|
|
1370
|
+
const maxDepth = 20;
|
|
1371
|
+
|
|
1372
|
+
while (fiber && depth < maxDepth) {
|
|
1373
|
+
if (fiber.type && typeof fiber.type === 'function') {
|
|
1374
|
+
const name = fiber.type.displayName || fiber.type.name;
|
|
1375
|
+
if (name && name !== 'Anonymous' && !name.startsWith('_')) {
|
|
1376
|
+
return {
|
|
1377
|
+
component: name,
|
|
1378
|
+
source: fiber._debugSource || null
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
fiber = fiber.return;
|
|
1383
|
+
depth++;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// Extract a useful relative path from absolute file path
|
|
1390
|
+
function getRelativePath(fullPath) {
|
|
1391
|
+
if (!fullPath) return null;
|
|
1392
|
+
|
|
1393
|
+
// Common source folder markers (prioritized)
|
|
1394
|
+
const sourceMarkers = ['/src/', '/app/', '/pages/', '/components/', '/lib/', '/utils/'];
|
|
1395
|
+
|
|
1396
|
+
for (const marker of sourceMarkers) {
|
|
1397
|
+
const index = fullPath.indexOf(marker);
|
|
1398
|
+
if (index !== -1) {
|
|
1399
|
+
// Return path starting from the marker (e.g., 'src/components/Button.tsx')
|
|
1400
|
+
return fullPath.substring(index + 1);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// Fallback: return last 3 path segments
|
|
1405
|
+
const parts = fullPath.split('/').filter(Boolean);
|
|
1406
|
+
if (parts.length <= 3) {
|
|
1407
|
+
return parts.join('/');
|
|
1408
|
+
}
|
|
1409
|
+
return parts.slice(-3).join('/');
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// Get element info for selection
|
|
1413
|
+
function getElementInfo(el) {
|
|
1414
|
+
const rect = el.getBoundingClientRect();
|
|
1415
|
+
const reactInfo = getReactInfo(el);
|
|
1416
|
+
const computedStyle = window.getComputedStyle(el);
|
|
1417
|
+
|
|
1418
|
+
return {
|
|
1419
|
+
selector: getSelector(el),
|
|
1420
|
+
tagName: el.tagName.toLowerCase(),
|
|
1421
|
+
text: (el.textContent || '').trim().slice(0, 100),
|
|
1422
|
+
attributes: {
|
|
1423
|
+
id: el.id || null,
|
|
1424
|
+
class: el.className || null,
|
|
1425
|
+
href: el.href || null,
|
|
1426
|
+
src: el.src || null,
|
|
1427
|
+
type: el.type || null,
|
|
1428
|
+
name: el.name || null
|
|
1429
|
+
},
|
|
1430
|
+
boundingRect: {
|
|
1431
|
+
top: Math.round(rect.top),
|
|
1432
|
+
left: Math.round(rect.left),
|
|
1433
|
+
width: Math.round(rect.width),
|
|
1434
|
+
height: Math.round(rect.height)
|
|
1435
|
+
},
|
|
1436
|
+
computedStyle: {
|
|
1437
|
+
display: computedStyle.display,
|
|
1438
|
+
position: computedStyle.position,
|
|
1439
|
+
color: computedStyle.color,
|
|
1440
|
+
backgroundColor: computedStyle.backgroundColor,
|
|
1441
|
+
fontSize: computedStyle.fontSize
|
|
1442
|
+
},
|
|
1443
|
+
sourceHint: reactInfo ? {
|
|
1444
|
+
component: reactInfo.component,
|
|
1445
|
+
file: getRelativePath(reactInfo.source?.fileName),
|
|
1446
|
+
fullPath: reactInfo.source?.fileName,
|
|
1447
|
+
line: reactInfo.source?.lineNumber,
|
|
1448
|
+
confidence: reactInfo.source ? 'high' : 'low'
|
|
1449
|
+
} : null
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Position overlay on element
|
|
1454
|
+
function positionOverlay(overlay, el) {
|
|
1455
|
+
const rect = el.getBoundingClientRect();
|
|
1456
|
+
overlay.style.top = rect.top + 'px';
|
|
1457
|
+
overlay.style.left = rect.left + 'px';
|
|
1458
|
+
overlay.style.width = rect.width + 'px';
|
|
1459
|
+
overlay.style.height = rect.height + 'px';
|
|
1460
|
+
overlay.style.display = 'block';
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// Handle element selection
|
|
1464
|
+
function selectElement(el, event) {
|
|
1465
|
+
event.preventDefault();
|
|
1466
|
+
event.stopPropagation();
|
|
1467
|
+
|
|
1468
|
+
selectedElement = el;
|
|
1469
|
+
const info = getElementInfo(el);
|
|
1470
|
+
|
|
1471
|
+
// Show selection overlay
|
|
1472
|
+
if (!selectionOverlay) selectionOverlay = createSelectionOverlay();
|
|
1473
|
+
positionOverlay(selectionOverlay, el);
|
|
1474
|
+
|
|
1475
|
+
// Send selection to parent
|
|
1476
|
+
const message = {
|
|
1477
|
+
type: 'element-selected',
|
|
1478
|
+
data: info
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
window.parent.postMessage(message, '*');
|
|
1482
|
+
|
|
1483
|
+
console.log('[Loxia] Element selected:', info.selector);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// Mouse move - highlight hovered element
|
|
1487
|
+
document.addEventListener('mousemove', (e) => {
|
|
1488
|
+
if (!isEnabled) return;
|
|
1489
|
+
|
|
1490
|
+
const el = e.target;
|
|
1491
|
+
if (el === hoveredElement) return;
|
|
1492
|
+
if (el.id?.startsWith('loxia-')) return;
|
|
1493
|
+
|
|
1494
|
+
hoveredElement = el;
|
|
1495
|
+
|
|
1496
|
+
if (!highlightOverlay) highlightOverlay = createHighlightOverlay();
|
|
1497
|
+
if (!tooltip) tooltip = createTooltip();
|
|
1498
|
+
|
|
1499
|
+
positionOverlay(highlightOverlay, el);
|
|
1500
|
+
|
|
1501
|
+
// Update tooltip
|
|
1502
|
+
const tagName = el.tagName.toLowerCase();
|
|
1503
|
+
const id = el.id ? '#' + el.id : '';
|
|
1504
|
+
const classes = el.className && typeof el.className === 'string' ?
|
|
1505
|
+
'.' + el.className.split(' ').slice(0, 2).join('.') : '';
|
|
1506
|
+
|
|
1507
|
+
tooltip.textContent = tagName + id + classes;
|
|
1508
|
+
tooltip.style.left = (e.clientX + 10) + 'px';
|
|
1509
|
+
tooltip.style.top = (e.clientY + 10) + 'px';
|
|
1510
|
+
tooltip.style.display = 'block';
|
|
1511
|
+
}, true);
|
|
1512
|
+
|
|
1513
|
+
// Mouse leave - hide highlight
|
|
1514
|
+
document.addEventListener('mouseleave', () => {
|
|
1515
|
+
if (highlightOverlay) highlightOverlay.style.display = 'none';
|
|
1516
|
+
if (tooltip) tooltip.style.display = 'none';
|
|
1517
|
+
}, true);
|
|
1518
|
+
|
|
1519
|
+
// Click - select element
|
|
1520
|
+
document.addEventListener('click', (e) => {
|
|
1521
|
+
if (!isEnabled) return;
|
|
1522
|
+
if (e.target.id?.startsWith('loxia-')) return;
|
|
1523
|
+
|
|
1524
|
+
selectElement(e.target, e);
|
|
1525
|
+
}, true);
|
|
1526
|
+
|
|
1527
|
+
// Keyboard shortcuts
|
|
1528
|
+
document.addEventListener('keydown', (e) => {
|
|
1529
|
+
// Escape - toggle overlay
|
|
1530
|
+
if (e.key === 'Escape') {
|
|
1531
|
+
isEnabled = !isEnabled;
|
|
1532
|
+
updateCursorStyle(isEnabled);
|
|
1533
|
+
if (!isEnabled) {
|
|
1534
|
+
if (highlightOverlay) highlightOverlay.style.display = 'none';
|
|
1535
|
+
if (selectionOverlay) selectionOverlay.style.display = 'none';
|
|
1536
|
+
if (tooltip) tooltip.style.display = 'none';
|
|
1537
|
+
hoveredElement = null;
|
|
1538
|
+
}
|
|
1539
|
+
// Notify parent of mode change
|
|
1540
|
+
window.parent.postMessage({ type: 'mode-toggled', enabled: isEnabled }, '*');
|
|
1541
|
+
console.log('[Loxia] Visual editor ' + (isEnabled ? 'enabled' : 'disabled'));
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
// Listen for commands from editor
|
|
1546
|
+
window.addEventListener('message', (e) => {
|
|
1547
|
+
if (!e.data || !e.data.type) return;
|
|
1548
|
+
|
|
1549
|
+
switch (e.data.type) {
|
|
1550
|
+
case 'highlight':
|
|
1551
|
+
const highlightEl = document.querySelector(e.data.selector);
|
|
1552
|
+
if (highlightEl) {
|
|
1553
|
+
if (!selectionOverlay) selectionOverlay = createSelectionOverlay();
|
|
1554
|
+
positionOverlay(selectionOverlay, highlightEl);
|
|
1555
|
+
|
|
1556
|
+
// Auto-hide after duration
|
|
1557
|
+
setTimeout(() => {
|
|
1558
|
+
if (selectionOverlay) selectionOverlay.style.display = 'none';
|
|
1559
|
+
}, e.data.duration || 2000);
|
|
1560
|
+
}
|
|
1561
|
+
break;
|
|
1562
|
+
|
|
1563
|
+
case 'scroll-to':
|
|
1564
|
+
const scrollEl = document.querySelector(e.data.selector);
|
|
1565
|
+
if (scrollEl) {
|
|
1566
|
+
scrollEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1567
|
+
}
|
|
1568
|
+
break;
|
|
1569
|
+
|
|
1570
|
+
case 'reload':
|
|
1571
|
+
location.reload();
|
|
1572
|
+
break;
|
|
1573
|
+
|
|
1574
|
+
case 'toggle':
|
|
1575
|
+
isEnabled = e.data.enabled !== undefined ? e.data.enabled : !isEnabled;
|
|
1576
|
+
updateCursorStyle(isEnabled);
|
|
1577
|
+
// Hide/show overlays based on new state
|
|
1578
|
+
if (!isEnabled) {
|
|
1579
|
+
if (highlightOverlay) highlightOverlay.style.display = 'none';
|
|
1580
|
+
if (selectionOverlay) selectionOverlay.style.display = 'none';
|
|
1581
|
+
if (tooltip) tooltip.style.display = 'none';
|
|
1582
|
+
hoveredElement = null;
|
|
1583
|
+
}
|
|
1584
|
+
console.log('[Loxia] Visual editor ' + (isEnabled ? 'enabled (Select mode)' : 'disabled (Preview mode)'));
|
|
1585
|
+
break;
|
|
1586
|
+
|
|
1587
|
+
case 'set-error-reporting':
|
|
1588
|
+
errorReportingEnabled = !!e.data.enabled;
|
|
1589
|
+
console.log('[Loxia] Error reporting ' + (errorReportingEnabled ? 'enabled' : 'disabled'));
|
|
1590
|
+
break;
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
// === Console Error Capture ===
|
|
1595
|
+
let errorReportingEnabled = true;
|
|
1596
|
+
const capturedErrors = [];
|
|
1597
|
+
const MAX_CAPTURED = 20;
|
|
1598
|
+
|
|
1599
|
+
function reportError(error) {
|
|
1600
|
+
if (!errorReportingEnabled) return;
|
|
1601
|
+
if (capturedErrors.length >= MAX_CAPTURED) return;
|
|
1602
|
+
// Skip Loxia's own logs
|
|
1603
|
+
if (typeof error.message === 'string' && error.message.startsWith('[Loxia]')) return;
|
|
1604
|
+
|
|
1605
|
+
const entry = {
|
|
1606
|
+
type: error.type || 'error',
|
|
1607
|
+
message: String(error.message || error).slice(0, 500),
|
|
1608
|
+
source: error.source || null,
|
|
1609
|
+
line: error.line || null,
|
|
1610
|
+
col: error.col || null,
|
|
1611
|
+
timestamp: Date.now()
|
|
1612
|
+
};
|
|
1613
|
+
capturedErrors.push(entry);
|
|
1614
|
+
|
|
1615
|
+
window.parent.postMessage({
|
|
1616
|
+
type: 'console-error',
|
|
1617
|
+
data: entry
|
|
1618
|
+
}, '*');
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// Capture unhandled errors
|
|
1622
|
+
window.addEventListener('error', (e) => {
|
|
1623
|
+
reportError({
|
|
1624
|
+
type: 'runtime-error',
|
|
1625
|
+
message: e.message,
|
|
1626
|
+
source: e.filename,
|
|
1627
|
+
line: e.lineno,
|
|
1628
|
+
col: e.colno
|
|
1629
|
+
});
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
// Capture unhandled promise rejections
|
|
1633
|
+
window.addEventListener('unhandledrejection', (e) => {
|
|
1634
|
+
reportError({
|
|
1635
|
+
type: 'unhandled-rejection',
|
|
1636
|
+
message: e.reason ? (e.reason.message || String(e.reason)) : 'Unknown rejection'
|
|
1637
|
+
});
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
// Intercept console.error
|
|
1641
|
+
const originalConsoleError = console.error;
|
|
1642
|
+
console.error = function() {
|
|
1643
|
+
const msg = Array.from(arguments).map(a => {
|
|
1644
|
+
if (a instanceof Error) return a.message + (a.stack ? '\\n' + a.stack.split('\\n').slice(0, 3).join('\\n') : '');
|
|
1645
|
+
if (typeof a === 'object') try { return JSON.stringify(a).slice(0, 300); } catch { return String(a); }
|
|
1646
|
+
return String(a);
|
|
1647
|
+
}).join(' ');
|
|
1648
|
+
|
|
1649
|
+
reportError({ type: 'console-error', message: msg });
|
|
1650
|
+
originalConsoleError.apply(console, arguments);
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
// Intercept console.warn for build warnings
|
|
1654
|
+
const originalConsoleWarn = console.warn;
|
|
1655
|
+
console.warn = function() {
|
|
1656
|
+
const msg = Array.from(arguments).map(a => typeof a === 'string' ? a : String(a)).join(' ');
|
|
1657
|
+
// Only capture warnings that look like build/framework issues
|
|
1658
|
+
if (/deprecat|warning|failed|error|cannot|invalid/i.test(msg)) {
|
|
1659
|
+
reportError({ type: 'console-warning', message: msg });
|
|
1660
|
+
}
|
|
1661
|
+
originalConsoleWarn.apply(console, arguments);
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
console.log('[Loxia] Visual Editor overlay loaded - Click elements to select, ESC to toggle');
|
|
1665
|
+
})();`;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// Singleton instance
|
|
1670
|
+
let serverInstance = null;
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Get or create the Visual Editor Server singleton
|
|
1674
|
+
* @param {Object} config - Configuration (only used on first call)
|
|
1675
|
+
* @returns {VisualEditorServer}
|
|
1676
|
+
*/
|
|
1677
|
+
export function getVisualEditorServer(config = {}) {
|
|
1678
|
+
if (!serverInstance) {
|
|
1679
|
+
serverInstance = new VisualEditorServer(config);
|
|
1680
|
+
}
|
|
1681
|
+
return serverInstance;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* Reset the singleton (for testing)
|
|
1686
|
+
*/
|
|
1687
|
+
export async function resetVisualEditorServer() {
|
|
1688
|
+
if (serverInstance) {
|
|
1689
|
+
await serverInstance.stop();
|
|
1690
|
+
serverInstance = null;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
/**
|
|
1695
|
+
* Set the bridge getter function to enable element selection forwarding
|
|
1696
|
+
* This avoids circular dependencies between visualEditorServer and visualEditorBridge
|
|
1697
|
+
* @param {Function} getter - Function that returns the visualEditorBridge instance
|
|
1698
|
+
*/
|
|
1699
|
+
export { setBridgeGetter };
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* Get the Visual Editor port from service registry (source of truth)
|
|
1703
|
+
* Falls back to server instance port or default if not registered
|
|
1704
|
+
* @returns {number} The port number
|
|
1705
|
+
*/
|
|
1706
|
+
export function getVisualEditorPort() {
|
|
1707
|
+
// Check service registry first (source of truth)
|
|
1708
|
+
const service = registry.get(SERVICE_NAME);
|
|
1709
|
+
if (service) {
|
|
1710
|
+
return service.port;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// Fall back to server instance or default
|
|
1714
|
+
return serverInstance?.port || getDefaultPort();
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Get the Visual Editor base URL from service registry
|
|
1719
|
+
* @returns {string} The base URL (e.g., http://localhost:4000)
|
|
1720
|
+
*/
|
|
1721
|
+
export function getVisualEditorBaseUrl() {
|
|
1722
|
+
const port = getVisualEditorPort();
|
|
1723
|
+
return `http://localhost:${port}`;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
export { FALLBACK_PORT as VISUAL_EDITOR_DEFAULT_PORT };
|
|
1727
|
+
export default VisualEditorServer;
|