Check when WebKit context is available in NW.js - node.js

When executed in Node context (node-main),
setTimeout(function () {
console.log(nw);
}, 20);
throws
nw is not defined
because WebKit context is not ready (right from the start window is unavailable in NW.js <= 0.12, window.nw in NW.js >= 0.13). And
setTimeout(function () {
console.log(nw);
}, 200);
works just fine but setTimeout looks like a hack, setting it to safe delay value may cause undesirable lag.
How can the availability of WebKit context and nw be checked from Node context? Is there a reasonable way, like an event that could be handled?

The following achieves the same thing but does it the other way around.
In your html file:
<body onload="process.mainModule.exports.init()">
In your node-main JS file:
exports.init = function() {
console.log(nw);
}
Here, init function is only called when Webkit context/DOM is available.

You could use pollit :) ...
var pit = require("pollit");
foo = function(data) {
console.log(nw);
};
pit.nw('nw', foo);
I've tested it and it works for me :). This modularizes the solution that I give near the end of this.
The nw object does not exist until webkit is up and running ie the browser
window has been created. This happens after Node starts up which is why you're
getting this error. To use the nw api you either create events that can be
listened to or call global functions the former being preferable. The following code will demonstrate both and should give you a good idea of how Node and WebKit are interfacing with each other.
This example creates a Window, opens devtools and allows you to toggle the
screen. It also displays the mouse location in the console. It also demonstrates how to send events using the DOM ie body.onclick() and attaching events from within Node ie we're going to catch minimize events and write them to the console.
For this to work you need to be using the SDK version of NW. This is my package.json
{
"name": "hello",
"node-main": "index.js",
"main": "index.html",
"window": {
"toolbar": true,
"width": 800,
"height": 600
},
"dependencies" : {
"robotjs" : "*",
"markdown" : "*"
}
}
The two files you need are index.html
<!DOCTYPE html>
<html>
<head>
<script>
var win = nw.Window.get();
global.win = win;
global.console = console;
global.main(nw);
global.mouse();
var markdown = require('markdown').markdown;
document.write(markdown.toHTML("-->Click between the arrows to toggle full screen<---"));
</script>
</head>
<body onclick="global.mouse();">
</body>
</html>
and index.js.
var robot = require("robotjs");
global.mouse = function() {
var mouse = robot.getMousePos();
console.log("Mouse is at x:" + mouse.x + " y:" + mouse.y);
global.win.toggleFullscreen();
}
global.main = function(nw_passed_in) {
global.win.showDevTools();
console.log("Starting main");
console.log(nw_passed_in);
console.log(nw);
global.win.on('minimize', function() {
console.log('n: Window is minimized from Node');
});
}
When running this I used
nwjs --enable-logging --remote-debugging-port=1729 ./
You can then open up the browser using
http://localhost:1729/
for debugging if needed.
If you want to do something as soon as the nw object exists you can poll it. I'd use eventEmitter, if you don't want to use event emitter you can just as easily wrap this in a function and call it recursively. The following will display how many milliseconds it took before the nw object was setup. On my system this ranged between 43 - 48 milliseconds. Using a recursive function was no different. If you add this to the code above you'll see everything logged to the console.
var start = new Date().getTime();
var events = require('events');
var e = new events.EventEmitter();
var stop = 0;
e.on('foo', function() {
if(typeof nw === 'undefined') {
setTimeout(function () {
e.emit('is_nw_defined');
}, 1);
}
else {
if(stop === 0) {
stop = new Date().getTime();
}
setTimeout(function () {
console.log(stop - start);
console.log(nw);
e.emit('is_nw_defined');
}, 2000);
}
});
e.emit('is_nw_defined');

Solution 1:
You can use onload, see reference.
main.js:
var gui = require("nw.gui"),
win = gui.Window.get();
onload = function() {
console.log("loaded");
console.log(win.nw);
};
index.html:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="main.js"></script>
</head>
<body></body>
</html>
package.json:
{
"name": "Freebox",
"main": "index.html"
}
Solution 2:
(To prevent issue, but it is not necessary).
var gui = require("nw.gui"),
win = gui.Window.get();
onload = function() {
console.log("loaded");
var a = function () {
if (!win.nw) return setTimeout(a, 10);
console.log(win.nw);
};
};

The solution I've initially come up looks like
app-node.js
process.once('webkit', () => {
console.log(nw);
});
app.html
<html>
<head>
<script>
global.process.emit('webkit');
</script>
...
I would be glad to know that there is already an event to listen, so cross-platform client scripts could omit NW-related code.

Related

Electron window.opener full support as in Browser

I am working on a multi-windows desktop Electron based application and I need to share a object instances from the main application process with the other windows of the application. Currently in browser (Chrome 52.0.2743.116) you can reference window.opener to achieve this. In browser window.opener (in child window) will give you the instance of the Window object from which window.opent(myURL) was called.
For example if you set in window.myData = {}; (Main window) same instance of myData will be available in the (Child Window) and will be accessed via (window.opener.myData).
In Electron when I open window via (window.opent(myURL)) window.opener is replaced with BrowserWindowProxy (Electron API) which does not expose the same window (Window object instance) from which the (window.open(myURL)) was called. Is there a way in Electron to access window.opener the same way it works in Browser?
Using remote.sharedObject (Electron API) is not an option since it only serialize/deserialize data and can be used for passing data from one window to others but does not give access to same object instances across windows.
To correct window.opener, and work facebook(and others) logins
You need use webPreferences.nativeWindowOpen=true, and set same webPreferences.affinity for mainWindow, and opened windows(hook on mainWindow.webContents.on('new-window'))
https://gist.github.com/Gvozd/2cec0c8c510a707854e439fb15c561b0
I don't think you are right about the remote api. From the doc:
Each object (including functions) returned by the remote module represents an object in the main process (we call it a remote object or remote function).
Try this:
'use strict'
let Electron = require('electron')
let mainWindow = null
class myObj {
constructor(d) {
console.log('constructor ' + d)
this.d = d
}
get data() {
console.log('get ' + this.d)
return this.d
}
set data(d) {
this.d = d
console.log('set ' + this.d)
}
}
global.someObj = new myObj(1)
Electron.app.on('ready', () => {
mainWindow = new Electron.BrowserWindow()
mainWindow.loadURL(`file://${__dirname}/index.html`)
mainWindow.webContents.openDevTools()
mainWindow.on('closed', () => {
mainWindow = null
})
})
Electron.app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
Electron.app.quit()
}
})
and
<!DOCTYPE html>
<html>
<head>
<script>
let obj = require('electron').remote.getGlobal('someObj')
console.log(obj)
console.log(obj.data)
obj.data = 2
console.log(obj.data)
</script>
</head>
<body></body>
</html>

Node webkit hotkey example not working

I'm trying to play around with node webkits hotkey example which can be viewed on their Shortcut's page here: https://github.com/nwjs/nw.js/wiki/Shortcut
Here's my code:
test.js
// Load native UI library.
var gui = window.require('nw.gui');
var option = {
key : "Ctrl+Shift+A",
active : function() {
console.log("Global desktop keyboard shortcut: " + this.key + " active.");
},
failed : function(msg) {
// :(, fail to register the |key| or couldn't parse the |key|.
console.log(msg);
}
};
// Create a shortcut with |option|.
var shortcut = new gui.Shortcut(option);
// Register global desktop shortcut, which can work without focus.
gui.App.registerGlobalHotKey(shortcut);
// If register |shortcut| successfully and user struck "Ctrl+Shift+A", |shortcut|
// will get an "active" event.
// You can also add listener to shortcut's active and failed event.
shortcut.on('active', function() {
console.log("Global desktop keyboard shortcut: " + this.key + " active.");
});
shortcut.on('failed', function(msg) {
console.log(msg);
});
// Unregister the global desktop shortcut.
gui.App.unregisterGlobalHotKey(shortcut);
index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
<script>
require("./test.js");
</script>
</head>
<body>
<h1>Hello World!</h1>
We are using node.js <script>document.write(process.version)</script>.
</body>
</html>
package.json
{
"name": "nw-demo",
"main": "index.html",
"dependencies": {
"nw": "^0.12.0"
},
"scripts": {
"start": "nw"
}
}
It breaks at this line on test.js saying undefined is not a function.
var shortcut = new gui.Shortcut(option);
Just remove this line:
gui.App.unregisterGlobalHotKey(shortcut);
In your code you register it, then delete. It working well for me (mac, nwjs 0.12)
As shaynem in (https://github.com/nwjs/nw.js/issues/3263#issuecomment-89679115) points out you should be running at least node-webkit >= 0.10.0
You should make sure your PATH (or the build tool you're using, e.g. nodebob) is not referencing any old leftover versions of node-webkit after you upgrade.
Also if you're on Ubuntu, you might stumble upon an open issue (https://github.com/nwjs/nw.js/issues/3199)
note webkit 0.13.0 has a bug where the the k is lowercase even though the method is documented with an uppercase K. Try:
registerGlobalHotkey(shortcut);
to verify this bug exists do:
var util = require('util');
console.log(util.inspect(nw.App));

jQuery UI dialog in Bookmarklet and GreaseMonkey

Thanks to some awesome answers in other threads, I learned how to:
load jQuery and jQuery-UI in a bookmarklet
load a CSS into a bookmarklet using jQuery
create a DIV in a bookmarklet using jQuery
create jQuery UI dialog (external link)
and I could manage to combine all four things into a single script which works both as a BookMarklet and as a GreaseMonkey script, which is absolutely awesome.
However, it doesn't work on all sites. Neither the bookmarklet nor the GreaseMonkey script work on Wikipedia.
I tried it on other sites and it works: amazon.com, msn.com, yahoo.com, news.google.com, stackoverflow.com and it works well.
Is it possible to make it work on wikipedia.org?
Also there is a minor issue: each second time I use the bookmarklet, the title of the dialog window doesnt show ('Basic dialog'). I wonder why, although it doesn't really matter - it's not important at all.
Here is the script:
javascript:(function () {
function getScript(url, success) {
var script = document.createElement('script');
script.src = url;
var head = document.getElementsByTagName('head')[0];
var completed = false;
script.onload = script.onreadystatechange = function () {
if (!completed && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
completed = true;
success();
script.onload = script.onreadystatechange = null;
head.removeChild(script);
}
};
head.appendChild(script);
}
getScript("https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js", function () {
getScript("https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.js", function () {
var myStylesLocation = "https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css";
$('<link rel="stylesheet" type="text/css" href="'+myStylesLocation+'" >').appendTo("head");
alert("It works");
var myMessage = "This is the default dialog which is useful for displaying information.";
$("<div id='dialog'; title='Basic dialog'; style='border:2px solid black; background-color:lightblue; font-size:80%'; <p>" + myMessage + "</p></div>").appendTo("body");
$( "#dialog" ).dialog();
});
});
})();

pipe child process stdout & stdin to browser in node.js & browserify

I am attempting to pipe the stdout & stdin of a child_process to a browser & display it in an html page. I am using browserify to get node.js to run on the browser. My code for spawning the child_process is like this.
var child = require('child_process');
var myREPL = child.spawn('myshell.exe', ['args']);
// myREPL.stdout.pipe(process.stdout, { end: false });
process.stdin.resume();
process.stdin.pipe(myREPL.stdin, { end: false });
myREPL.stdin.on('end', function() {
process.stdout.write('REPL stream ended.');
});
myREPL.on('exit', function (code) {
process.exit(code);
});
myREPL.stdout.on('data', function(data) {
console.log('\n\nSTDOUT: \n');
console.log('**************************');
console.log('' + data);
console.log('==========================');
});
I created a bundle.js using browserify and my html looks like this.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script src="bundle.js"></script>
<script src="main.js"></script>
</head>
<body>
</body>
</html>
I am trying to avoid running an http server and piping the results to it in the browser. Is there any other way where I can do it ?
Thanks
You should look into hyperwatch, which pipes the server side stdout/stderr to the browser and renders it exactly like it would appear in your terminal (including colors).
If it doesn't exactly solve your problem, reading through the code should at least help you. It uses hypernal under the hood in order to convert terminal output to html.
I dont know if this is to late but i managed to run a program from browser starting from this code that works only on linux (i use ubuntu). You will have to run interactive programs with stdbuf -o0 prefix.
var child = require('child_process');
var myREPL = child.spawn('bash');
process.stdin.pipe(myREPL.stdin);
myREPL.stdin.on("end", function() {
process.exit(0);
});
myREPL.stdout.on('data', function (data) {
console.log(data+'');
});
myREPL.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
An then to make it to work on browser you only need to add socket.io
var myREPL = child.spawn(program);
myREPL.stdin.on("end", function() {
socket.emit('consoleProgramEnded');
});
myREPL.stdout.on('data', function (data) {
socket.emit('consoleWrite',data+'');
});
myREPL.stderr.on('data', function (data) {
socket.emit('consoleWrite',data+'');
});
socket.on('consoleRead',function(message){
console.log("Writing to console:"+message);
myREPL.stdin.write(message.replace("<br>","")+"\n");
});
I hope that will help you.
I think the frontail NPM module can also do what you want to do -
https://github.com/mthenw/frontail
I have used it and it works

phantomjs and requirejs

codes in file main.js is like this:
phantom.injectJs("libs/require-1.0.7.js");
require.config(
{
baseUrl: ""
}
);
require([], function(){});
when i run "phantomjs main.js" in the commandline, requirejs doesn't work well in the main.js. I know how to use requirejs in the page running in the browser(including phantomjs' way: page.open(url, callback)), but not like above. I tries using requirejs like the main.js, it is a popular problem, i think. Thank you!
I just struggled for some time. My solution is not clean, but it works, and I'm happy with that due to the unfinished api documentation from phantomjs.
Wordy explanation
You need three files. One is your amd phantomjs test file which I'll call "amd.js". The second is your html page to load which I'll name "amd.html". Finally the browser test which I called "amdTestModule.js".
In amd.html, declare your script tag per normal:
<script data-main="amdTestModule.js" src="require.js"></script>
In your phantomjs test file, this is where it gets hacky. Create your page, and load in the 'fs' module. This allows you to open a relative file path.
var page = require('webpage').create();
var fs = require('fs');
page.open('file://' + fs.absolute('tests/amd.html'));
Now since requirejs loads files asynchronously, we can't just pass in a callback into page.open and expect things to go smoothly. We need some way to either
1) Test our module in the browser and communicate the result back to our phantomjs context. Or
2) Tell our phantomjs context that upon loading all the resources, to run a test.
#1 was simpler for my case. I accomplished this via:
page.onConsoleMessage = function(msg) {
msg = msg.split('=');
if (msg[1] === 'success') {
console.log('amd test successful');
} else {
console.log('amd test failed');
}
phantom.exit();
};
**See full code below for my console.log message.
Now phantomjs apparently has an event api built in but it is undocumented. I was also successfully able to get request/response messages from their page.onResourceReceived and page.onResourceRequested - meaning you can debug when all your required modules are loaded. To communicate my test result however, I just used console.log.
Now what happens if the console.log message is never ran? The only way I could think of resolving this was to use setTimeout
setTimeout(function() {
console.log('amd test failed - timeout');
phantom.exit();
}, 500);
That should do it!
Full Code
directory structure
/projectRoot
/tests
- amd.js
- amdTestModule.js
- amd.html
- require.js (which I symlinked)
- <dependencies> (also symlinked)
amd.js
'use strict';
var page = require('webpage').create();
var fs = require('fs');
/*
page.onResourceRequested = function(req) {
console.log('\n');
console.log('REQUEST');
console.log(JSON.stringify(req, null, 4));
console.log('\n');
};
page.onResourceReceived = function(response) {
console.log('\n');
console.log('RESPONSE');
console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response, null, 4));
console.log('\n');
};
*/
page.onConsoleMessage = function(msg) {
msg = msg.split('=');
if (msg[1] === 'success') {
console.log('amd test successful');
} else {
console.log('amd test failed');
}
phantom.exit();
};
page.open('file://' + fs.absolute('tests/amd.html'));
setTimeout(function() {
console.log('amd test failed - timeout');
phantom.exit();
}, 500);
amd.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script data-main='amdTestModule.js' src='require.js'></script>
</body>
</html>
amdTestModule.js
require([<dependencies>], function(<dependencies>) {
...
console.log(
(<test>) ? "test=success" : "test=failed"
);
});
console
$ phantomjs tests/amd.js
amd test successful
you are misunderstanding webpage.injectJs()
it's for injecting scripts into the page you are loading, not into the phantomjs runtime environment.
So using .injectJs() is making requirejs load up into your page, not into phantomjs.exe.
That said, phantomjs's runtime environment has an aproximation of commonjs. RequireJs will not run on there by default. If you felt especially (VERY) motivated, you could attempt porting the require-shim made for nodejs, but it doesn't work out of the box, and would require an incredibly deep understanding of the runtimes. for more details: http://requirejs.org/docs/node.html
a better idea:
probably you should make sure you have commonjs versions of your javascript you wish to run. i personally write my code in typescript so i can build for either commonjs or amd. i use commonjs for phantomjs code, and amd for nodejs and browser.

Resources