neutralinojs
neutralinojs copied to clipboard
Not running WebSocket on HotReloading
Describe the bug Hot relaoding works, but without Neutralino API
Uncaught DOMException: Failed to construct 'WebSocket': The URL 'ws://localhost:undefined' is invalid.
at a (http://localhost:3000/neutralino.js:1:580)
at Object.e.init (http://localhost:3000/neutralino.js:1:11526)
at http://localhost:3000/src/main.js?t=1653880987265:6:19
I think is something related to the port to websocket but I not find anything about in the docs.
To Reproduce Steps to reproduce the behavior:
- I follow the instructions in https://neutralino.js.org/docs/how-to/use-a-frontend-library/
- But using Vue + Vite
- When run
vite build && neu run
Websocket works but not hot reloading - When run
vite
+neu run --frontend-lib-dev
hot relaoding works but not Websocket
Expected behavior Work hot reloading e Neutralino API.
Specifications
- OS: Windows 10 x64
- Neutralinojs version: v4.6.0
- Neutralinojs client library version: v3.5.0
- Neutralinojs CLI version: v9.3.1
Additional context neutralino.config.json
{
"applicationId": "js.neutralino.zero",
"version": "1.0.0",
"defaultMode": "window",
"documentRoot": "/output",
"url": "/",
"enableServer": true,
"enableNativeAPI": true,
"nativeAllowList": [
"app.*",
"window.*"
],
"modes": {
"window": {
"title": "myapp",
"width": 800,
"height": 500,
"minWidth": 400,
"minHeight": 200,
"icon": "/output/icon.png"
}
},
"cli": {
"binaryName": "myapp",
"resourcesPath": "/output",
"extensionsPath": "/extensions/",
"clientLibrary": "/public/neutralino.js",
"binaryVersion": "4.6.0",
"clientVersion": "3.5.0",
"frontendLibrary": {
"patchFile": "/output/index.html",
"devUrl": "http://localhost:3000"
}
}
}
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
outDir: './output'
},
})
neutralinojs.log
INFO 2022-05-30 00:53:56,683 Auth info was exported to ./.tmp/auth_info.json api\debug\debug.cpp:14 jbwanderley2@STFSAOC045831-L
/.tmp/auth_info.json
{"accessToken":"Ovg9BLuujZi1b3vNgRu0x2wQGTSSG7SV4tgTCS3uGCw-wUNh","port":54428}
Inspecting further I found that the problem is that the window variables are not being set (NL_PORT, NL_TOKEN, NL_ARGS) I tried setting it up myself verifying if is in DEV mode and running the Neutralino API and it worked. For NL_PORT and NL_TOKEN I used the values in /.tmp/auth_info.json, for NL_ARGS I ran it once in the build and got it.
// main.js
...
import authInfo from '../.tmp/auth_info.json'
if (import.meta.env.DEV) {
const {accessToken, port} = authInfo
window.NL_PORT = port
window.NL_TOKEN = accessToken
window.NL_ARGS = [
'bin\\neutralino-win_x64.exe',
'',
'--load-dir-res',
'--path=.',
'--export-auth-info',
'--neu-dev-extension',
'--neu-dev-auto-reload',
'--window-enable-inspector'
]
}
window.Neutralino.init()
Using this code running on hot reload works.
I've just run into the same issue - was trying to uncover a method in Neutralino to generate the globals, but will pinch your fix insteaad. Thanks a bunch! Changed it to a dynamic import though, as a static import will fail when trying to build without launching from neu.
To prevent including the code during Vite build (and also prevent TS errors when the file doesn't exists), I did a virtual module with Vite that apply this little piece of code
vite.config.ts
import type { Plugin, ResolvedConfig } from "vite";
import { defineConfig } from 'vite';
import path from "node:path";
// Re-define constants in `development` mode.
// <https://github.com/neutralinojs/neutralinojs/issues/909>.
const neutralino_dev = (): Plugin => {
let config: ResolvedConfig;
const virtualModuleId = "virtual:neutralino-dev"
const resolvedVirtualModuleId = "\0" + virtualModuleId;
return {
name: "neutralino-dev",
configResolved (resolvedConfig) {
// store the resolved config
config = resolvedConfig
},
resolveId (id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
},
load (id) {
if (id === resolvedVirtualModuleId) {
if (config.mode !== "development") {
console.info("[neutralino] don't apply dev code.");
return "";
}
console.info("[neutralino] apply dev code.");
return `
const authInfo = await import("${path.join(__dirname, ".tmp", "auth_info.json")}");
window.NL_PORT = authInfo.port;
window.NL_TOKEN = authInfo.accessToken;
`
}
}
}
};
export default defineConfig({
plugins: [
// Will only apply development code on development mode.
neutralino_dev()
]
});
index.tsx
import "virtual:neutralino-dev";
// ...render(...);
Neutralino.init();
EDIT: I made a new version of my virtual plugin to actually import the patched neutralino.js
file !
const neutralino_dev = (): Plugin => {
let config: ResolvedConfig;
const virtualModuleId = "virtual:neutralino-dev";
const resolvedVirtualModuleId = "\0" + virtualModuleId;
return {
name: "neutralino-dev",
configResolved (resolvedConfig) {
config = resolvedConfig;
},
resolveId (id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
},
load (id) {
if (id === resolvedVirtualModuleId) {
let code: string;
if (config.mode === "development") {
code = `
const authInfo = await import("${path.join(__dirname, ".tmp", "auth_info.json")}");
const neutralinoJsFileUrl = \`http://localhost:\${authInfo.port}/neutralino.js\`;
`;
}
else {
code = `
const neutralinoJsFileUrl = "/neutralino.js";
`;
}
return code + `
const neutralinoScript = document.createElement("script");
neutralinoScript.setAttribute("src", neutralinoJsFileUrl);
document.body.appendChild(neutralinoScript);
`;
}
}
};
};
Note that you need to remove
<script src="neutralino.js"></script>
from yourindex.html
since this would be added dynamically by the virtual plugin. Maybe I should write a build plugin so it directly imports it in theindex.html
file.
Also, you need to keep
"frontendLibrary": {
"patchFile": "/index.html",
"devUrl": "http://localhost:3000"
}
in your Neutralino configuration (here my Vite development server port was on 3000
, change it depending to yours)
Enjoy !
Maybe I should write a build plugin so it directly imports it in the index.html file.
So I did it, because it's a little bit cleaner and even for the build, it's better.
Also keep in mind that the neutralino.js
file is in /public/neutralino.js
. If it's anywhere else, you'll need to change the paths.
index.html
...
<body>
...
<script src="neutralino.js"></script>
</body>
neutralino.config.json
{
"applicationId": "...",
"version": "...",
"defaultMode": "window",
"port": 0,
"documentRoot": "/dist/",
"url": "/",
"enableServer": true,
"enableNativeAPI": true,
"exportAuthInfo": true,
"modes": {
"window": {
"..."
}
},
"cli": {
"binaryName": "...",
"resourcesPath": "/dist/",
"extensionsPath": "/extensions/",
"clientLibrary": "/public/neutralino.js",
"binaryVersion": "...",
"clientVersion": "...",
"frontendLibrary": {
"patchFile": "/index.html",
"devUrl": "http://localhost:3000"
}
}
}
vite.config.ts
import type { Plugin, ResolvedConfig } from "vite";
import { defineConfig } from "vite";
import fs from "node:fs/promises";
import path from "node:path";
// <https://github.com/neutralinojs/neutralinojs/issues/909>.
const neutralino = (): Plugin => {
let config: ResolvedConfig;
return {
name: "neutralino",
configResolved (resolvedConfig) {
config = resolvedConfig;
},
async transformIndexHtml (html) {
if (config.mode === "development") {
const auth_info_file = await fs.readFile(path.join(__dirname, ".tmp", "auth_info.json"), {
encoding: "utf-8"
});
const auth_info = JSON.parse(auth_info_file);
const port = auth_info.port;
return html.replace(
"<script src=\"neutralino.js\"></script>",
`<script src="http://localhost:${port}/neutralino.js"></script>`
);
}
return html;
}
};
};
export default defineConfig({
plugins: [
neutralino()
],
server: {
port: 3000
}
});
Parts of my package.json
, here I use concurrently to run Neutralino and Vite at the same time.
{
"type": "module",
"scripts": {
"neu": "neu",
"dev": "concurrently \"vite\" \"neu run --frontend-lib-dev -- --window-enable-inspector=true\"",
"build": "vite build && neu build --release",
"preview": "vite build && neu run"
}
}
You don't have to touch your source files, the neutralino.js
file will be updated directly inside the index.html
.
Hope it helps some of you, now you have every variables available in your environment (NL_*
) !
I've faced the same issue with my nuxtjs client. Have you have any help for about nuxtjs?
Add <script src="__neutralino_globals.js"></script>
in index.html
file fixed the problem for me. The neu
process will automatically know to serve the globals there.
NL_PORT as well as other globals are sent to the browser window via __neutralino_globals.js
. After a few tests, I fount it out how it works:
- When you run in "HMR" mode,
neu run --frontend-lib-dev
, theneu
process searches the html filecli.frontendLibrary.patchFile
for<script src="(somepath)__neutralino_globals.js"></script>
and modify it into<script src="http://localhost:(anotherport)/(somepath)__neutralino_globals.js"></script>
. The program then host the globals there. Notice(anotherport)
is different from other assets. If you force to quit in "HMR" modeneu run --frontend-lib-dev
, like ctrl+C at neu cli command line, the restoration will not happen and a brokenindex.html
file will be left there. - When you run
neu run
, theneu
process serve__neutralino_globals.js
at the same port from other static assets. So no port replacement would happen.
I had to write a vite plugin like vexcited to add localhost/port to __neutralino_globals.js script tag. Not sure why neu isn't patching it in the patch file like bumprat explained. But after fighting it for too long at least it works.
Thanks everyone.
// <https://github.com/neutralinojs/neutralinojs/issues/909>.
const neutralino = () => {
let config;
return {
name: "neutralino",
configResolved (resolvedConfig) {
config = resolvedConfig;
},
async transformIndexHtml (html) {
if (config.mode === "development") {
const auth_info_file = await fs.readFile(path.join(__dirname, "../.tmp", "auth_info.json"), {
encoding: "utf-8"
});
const auth_info = JSON.parse(auth_info_file);
const port = auth_info.port;
return html.replace(
"<script src=\"__neutralino_globals.js\"></script>",
`<script src="http://localhost:${port}/__neutralino_globals.js"></script>`
);
}
return html;
}
};
};
Okay after struggling to get extensions websocket to connect I found that localhost was coming back to ipv6 loopback of ::1
. This was causes the neu hotload patch to fail as well.
I was getting:
[1] neu: INFO Hot reload patch was reverted.
[1] neu: INFO Hot reload patch was reverted.
[1] neu: INFO Hot reload patch was reverted.
[1] neu: INFO Hot reload patch was reverted.
...
I commented out ipv6 loopback ::1
in /etc/hosts and all is well now. No need for vite plugin.
Probably just need to restart my computer. Sigh. I'm on osx monterey if someone else happens to have this obscure issue.
I have a similar problem that after manually reloading the page (by pressing F5 in the developer tools) the NL_TOKEN is an empty string and neutralino native api is no longer authentified.. When i restart the whole Neutralino App, the token is set again. Is this intended for security reasons or is it a bug?
After looking at the neutralino js lib source code, I found a solution:
const storedToken = sessionStorage.getItem('NL_TOKEN');
if (storedToken) window.NL_TOKEN = storedToken;
Neutralino.init();
PS: I'm using a quiet hacky setup for angular, so maybe the issue exist because of that in the first place
Add
<script src="__neutralino_globals.js"></script>
inindex.html
file fixed the problem for me. Theneu
process will automatically know to serve the globals there.NL_PORT as well as other globals are sent to the browser window via
__neutralino_globals.js
. After a few tests, I fount it out how it works:
- When you run in "HMR" mode,
neu run --frontend-lib-dev
, theneu
process searches the html filecli.frontendLibrary.patchFile
for<script src="(somepath)__neutralino_globals.js"></script>
and modify it into<script src="http://localhost:(anotherport)/(somepath)__neutralino_globals.js"></script>
. The program then host the globals there. Notice(anotherport)
is different from other assets. If you force to quit in "HMR" modeneu run --frontend-lib-dev
, like ctrl+C at neu cli command line, the restoration will not happen and a brokenindex.html
file will be left there.- When you run
neu run
, theneu
process serve__neutralino_globals.js
at the same port from other static assets. So no port replacement would happen.
How to quit HMR mode without ctrl+c?
Closing this issue since the latest framework loads global variables separately via a dedicated JavaScript snippet. Please check this tutorial for more details: https://neutralino.js.org/docs/getting-started/using-frontend-libraries
Thanks :tada:
src in index.html is updated slower than page is loaded. Therefore, the page always tries to load ws-url with previous port
I wonder if the guide is outdated. I'm trying to set up vite with React, but I have two issues:
- The neutralino window doesn't open, even though the server is running and I can access it in my browser, it just keeps waiting, then it timeouts but doesn't kill the vite server, so it stays as a zombie. If I try to run again, it will spawn a new instance in a new port.
- The neutralino.js fails with some kind of parsing error:
Uncaught DOMException: An invalid or illegal string was specified
d index.js:1
node_modules @neutralinojs_lib.js:413
<anonymous> main.tsx:13
I made template with Neutralinojs + React + TS + Vite + HMR: https://github.com/JustPilz/neu-react-ts-vite-template It works for me