I'm trying to find out whether it's possible to invoke node.js functions from a web page. Is there any way to make node.js functions accessible from Google Chrome (so that they are run on the node.js server), as shown here?
(I'm aware that it's possible to do this using node-webkit (a non-standard Chromium implementation) without modifying the code, but I'd prefer to do this using an unmodified browser, which will require the code shown below to be modified in some way.)
<html>
<body>
<script type = "text/javascript">
var exec = require('child_process').exec; //node.js function
</script>
<p onclick = "exec('firefox')">
Click here to launch the Firefox web browser.
</p>
</body>
</html>
No, this is not possible, for clear security reasons.
You only have available to you what the browser gives you. node-webkit is the closest thing available, and does not meet your requirements.
NW has own method like node exec :
var gui = require('nw.gui');
gui.Shell.openItem('firefox', ,function(error, stdout, stderr) { });
Related
I want to build a chrome extension for personal use.
The extension will scrape some webpages and it will render some information.
So I think puppeteer can help me with that.
I understand that I need to run node inside a chrome extension.
Is it possible?
I have found some answers but they are old.
I know this is 9 months late but I had the same use case at work on Window machines but you can make it work with Mac.
The trick is to use puppeteer-web
https://github.com/puppeteer/puppeteer/tree/master/utils/browser#bundling-for-web-browsers
Bundle the repository and place it in your chrome extension folder and then reference it in your popup.html with something like
<script src="./puppeteer/utils/browser/puppeteer-web.js"></script>
You'll then need to take advantage of Chrome's remote debugging functionality as puppeteer-web can't start its own instance via puppeteer.launch() and can only use puppeteer.connect() to connect to an already existing chrome instance.
On windows add --remote-debugging-port=9222 to the end of the target field of the chrome short cut as per How to make Chrome always launch with remote-debugging-port flag
Or on Mac /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --no-first-run --no-default-browser-check --user-data-dir=$(mktemp -d -t 'chrome-remote_data_dir')
Once remote debugging is activated you'll be able to see the webSocketDebuggerUrl property by visiting http://127.0.0.1:9222/json/version on your browser. This is the browserWSEndpoint the connect method will invoke.
You will also need to add the port address to the permissions array in the manifest.json file otherwise ajax requests won't work in the chrome extension.
Eg:
"permissions": [ "tabs" , "identity", "http://127.0.0.1:9222/*"],
Example popup.html file
<!DOCTYPE html>
<html>
<head>
<title>Example popup</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div>
<button id='puppeteer-button'>Do Puppeteer Things</button>
<script src="./puppeteer/utils/browser/puppeteer-web.js"></script>
<script type="module" src="popup.js"></script>
</div>
</body>
</html>
Example popup.js file
let browserWSEndpoint = '';
const puppeteer = require("puppeteer");
async function initiatePuppeteer() {
await fetch("http://127.0.0.1:9222/json/version")
.then(response => response.json())
.then(function(data) {
browserWSEndpoint = data.webSocketDebuggerUrl;
})
.catch(error => console.log(error));
}
initiatePuppeteer();
// Assign button to puppeteer function
document
.getElementById("puppeteer-button")
.addEventListener("click", doPuppeteerThings);
async function doPuppeteerThings() {
const browser = await puppeteer.connect({
browserWSEndpoint: browserWSEndpoint
});
const page = await browser.newPage();
// Your puppeteer code goes here
}
Hope that helps, I haven't had any issues by appending remote debugging to my target field on my work window machines, despite feeling a bit hacky. I wrote a short blog post on it with better syntax highlighting here.
Actually it is possible but with some limitations. Puppeteer use devtools-protocol (https://chromedevtools.github.io/devtools-protocol/) which is available inside chrome extension when you enable deubgger in your extension manifest https://developer.chrome.com/extensions/debugger. But inside extension is available only latest, stable version of protocol (for now is 1.3 https://chromedevtools.github.io/devtools-protocol/1-3).
But in my opinion you don't need devtools-protocol to handle your problem. Just use standard extension API https://developer.chrome.com/extensions/api_index to open any URL you need (chrome.tabs.update), parse page inside content.js and do with that data whatever you want.
So I have here this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Oh no!</title>
</head>
<body>
<label>Oh dear. A serious error occurred and the app needs to restart. Press the button below to restart.</label>
<br>
<button onclick="restart()">Restart</button>
<script>
const { app } = require("electron")
function restart() {
app.relaunch()
app.exit()
}
</script>
</body>
</html>
And now, when the app receives an unhandled error this will show... but when the user clicks the button, the app doesn't restart so how would I make the app restart?
You can't get the app object without using preload.js and neither is directly getting the app object safe. There is a method to do the above using preload.js and ipcRenderer which are pure Electon APIs
In electron (even in web development), there is server-side code and browser-side code. The code written in between the script tags in your snippet is server side code which will fail to execute in browser side.
Server-side code in your case is in NodeJS Backend and browser-side code is the one which is the HTML Page and its own javascript.
So to close the window (which only NodeJS can do, i.e., the backend) you need to use Electron's ipcRenderer which helps string based communication between the browser-side javascript and the server-side javascript.
While creating a browser window in electron using new BrowserWindow(options) where options is an object. Define the object as:
options = {
webPreferences: {
preload: preload.js, //You need to create a file named preload.js (or any name) in your code
nodeIntegration: true,
contextIsolation: false,
}
}
Now in a new file called preload.js:
window.ipcRenderer = require('electron').ipcRenderer;
In your snippet you added const { app } ... which should be done this way to inject the javascript using a preload property in the object.
Now in the main app.js file (whatever you named maybe index.js) where you created the browser window:
const ipc = require('electron').ipcMain; //Add to your pre-existing code
ipc.on("close-app", (event, message) => { //"close-app" can be anything but, you need to use the same key in the send message side (later in this answer)
browserWindow.close(); //If you named the browserwindow as browserWindow
});
Now in your HTML (i.e., send message side)
...
<script>
window.ipcRenderer("close-app", ""); //Second parameter is used if you want to send some extra message. The extra message can be viewed in the server side from the message parameter in the app.js code (just above this paragraph)
</script>
This is a bit difficult if you are doing it for the first time.
I've added more articles which will help you clear your confusions:
Highlight about server-side and browser-side code
Relation with socket.io communication in NodeJS
My project is using: Node, Coffeescript, SocketIO, Browserify and Mocha. (mocha for standard server-side unit tests)
I would like to automate some client-side interface testing using a headless browser. PhantomJS looked like the ideal choice (picked over Zombie due to web socket support).
The PhantomJS pages warn it is not a test runner, which I understand, and they recommend using the mocha-phantomjs project to drive your tests.
So I've been able to get the sample tests running (e.g. mocha-phantomjs tests/mixed.html), but my current problem is actually using PHANTOM within the tests. All the sample tests in the mocha-phantomjs repo seem to use standard mocha server-side unit test.
e.g. I can easily run mocha-phantomjs tests/mixed.html to view boring old unit tests. Or I can run phantomjs tests/login.coffee to load up my login screen... but how do I combine the two to make assertions on what I should expect to see on my login screen?
I can't find any examples of this on the web, and I'm struggling with understanding the best way to go about this.
Hope this all makes sense. Thanks in advance for any assistance.
UPDATE: I found the following suggestion by the author (here), but I don't really understand exactly what to do with it: phantomjs lib/mocha-phantomjs.coffee test/mixed.html
There's a fairly nice tutorial for testing with Mocha and Phantom.JS here.
The section on Mocha and PhantomJS is short, but the basic idea is to put DOM assertions and interactions into your Mocha test suite, run it a la client-side via a testrunner.html file, and then point mocha-phantomjs at the testrunner.html file.
To paraphrase, your Mocha test might look like this:
describe("DOM Test", function () {
var el = document.createElement("div");
el.id = "myDiv";
el.innerHTML = "Hello World!";
document.body.appendChild(el);
var myEl = document.getElementById('myDiv');
it("has the right text", function () {
(myEl.innerHTML).should.equal("Hello World!");
});
});
And the testrunner.html file would be the normal setup:
<html>
<head>
<title> Tests </title>
<link rel="stylesheet" href="./node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="./node_modules/mocha/mocha.js"></script>
<script src="./node_modules/chai/chai.js"></script>
<script>
mocha.ui('bdd');
mocha.reporter('html');
var should = chai.should();
</script>
<script src="test/test.js"></script>
<script>
if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
else { mocha.run(); }
</script>
</body>
</html>
If you'd prefer a solution run entirely from the node.js ecosystem, it's worth considering Zombie.JS. This Stack Overflow question provides a basic example.
The tradeoff is that while Zombie.JS can be used simply by requiring the node module, and is extremely fast, it's not a "real" web browser. PhantomJS is closer, as its based on webkit. Also, the first approach with mocha-phantomjs would allow you to run the client-side Mocha tests in different browsers of your choice, PhantomJS being just one of them.
Goal: After user saves data to my mysql DB, a JSON teaser of the newly saved content is broadcasted to all users.
What are some quick and dirty solutions to achieve this? I am working with php/mysql. Got a taste of Socket.io, and a node.js server listening on the side....
Any good links would be greatly appreciated.
Background--------
After many years of working with php, I have decided to jump onto the NodeJS Bandwagon- and have no clue to do anything other than the vanilla "Hello World".... This is my starting point. Thanks again!!
If you would like to broadcast to all users including yourself, then you should use:
io.sockets.emit('global', {data: 'this will be received by everyone'});
If you would like to broadcast to all users without yourself being notified, then you should use:
socket.broadcast.emit('Hello everyone!');
Hope this helps!
Can you define what you mean by "broadcast" in this context?
Do you want to display an alert on a web page?
If so, do you wish to alert only those users who are currently viewing pages on your site or those who come to your site during a period of time? If the later, do you care if the alert appears to a single user more than once?
Socket.IO is one way to transmit/receive messages to/from a remote browser, but without a client-side handler that has some way of displaying your message to the user, it's kind of moot.
I've found now.js to be a far better abstraction of browser<-->server communications as shown in this possible solution that will display an alert on pages currently viewed by your users:
Step 1: Install now.js (remove the -g if you don't want now installed globally):
npm install -g now
Step 2: Create a HTTP server and cause it to listen on a port 80
var PORT=80,
http=require('http'),
nowjs=require('now'),
app=http.createServer(requestHandler).listen(PORT), // create your server
everyone=nowjs.initialize(app); // initialize now on your listening server
function alertUsers(msg){ // sends msg to showAlert() callback on clients
everyone.now.showAlert(msg);
}
function requestHandler(req,res) {
...
...
/*
* Ok, something has happened you want to inform your currently
* connected users of...
*
* if the thing can happen as a result of a request, do it here
*/
var msg="Whoohoo! Something's happened!";
alertUsers(msg);
...
...
}
// or if it happens somewhere else, just call alertUsers() there.
Step 3: And then in the browser-side HTML:
<html>
<head>
<style type="text/css">
.alert { background-color:red; color:white; font:24pt bold sans-serif; }
.hidden { display:hidden; visibility:none; }
</style>
</head>
<body>
<div id="alert" class="alert hidden">
<div class="main-content">
<!-- main page content -->
</div>
<script type="text/javascript" src="...load jQuery here..."></script>
<script src="/nowjs/now.js"></script> <!-- *SEE NOTE BELOW -->
<script type="text/javascript>
$(document).ready(function(){
// after the document has finished loading
now.showAlert=function(msg){ // define your callback function
$('#alert').removeClass('hidden').text(msg);
};
});
</script>
</body>
</html>
* Note that the <script src="/nowjs/now.js"></script> request is magic in that the referenced file doesn't really exist server-side at that location. By running nowjs.initialize() on your http server instance, you're setting things up so that nowjs will intercept the request for /nowjs/now.js and serve it back to the client without invoking your requestHandler().
Also, this solution does not display your alert to anyone who connects after the alert is sent from the server. If you want to do that, you'll need to do something different.
Is there a way to check iOS to see if another app has been installed and then launched? If memory serves me this was not possible in early versions but has this been changed?
Doable, but tricky.
Launching installed apps, like the FB or Twitter apps, is done using the Custom URL Scheme. These can be used both in other apps as well as on web sites.
Here's an article about how to do this with your own app.
Seeing if the URL is there, though, can be tricky. A good example of an app that detects installed apps is Boxcar. The thing here is that Boxcar has advanced knowledge of the custom URL's. I'm fairly (99%) certain that there is a canOpenURL:, so knowing the custom scheme of the app you want to target ahead of time makes this simple to implement.
Here's a partial list of some of the more popular URL's you can check against.
There is a way to find out the custom app URL : https://www.amerhukic.com/finding-the-custom-url-scheme-of-an-ios-app
But if you want to scan for apps and deduce their URL's, it can't be done on a non-JB device.
Here's a blog post talking about how the folks at Bump handled the problem.
There is a script like the following.
<script type="text/javascript">
function startMyApp()
{
document.location = 'yourAppScheme://';
setTimeout( function()
{
if( confirm( 'You do not seem to have Your App installed, do you want to go download it now?'))
{
document.location = 'http://itunes.apple.com/us/app/yourAppId';
}
}, 300);
}
</script>
Calling this script from the web (Try to start MyApp), you can determine if your app with scheme "yourAppScheme" is installed on the device or not.
The App will launch if it is installed on the device and "yourAppScheme" is registered in it.
If the app is not installed you can suggest the user to install this app from iTunes.
To check if an app is installed (e.g. Clear):
BOOL installed = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"clearapp://"]];
To open that app:
BOOL success = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"clearapp://"]];
Hides the error message if the app is not installed
At Branch we use a form of the code below--note that the iframe works on more browsers. Simply substitute in your app's URI and your App Store link.
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript">
window.onload = function() {
// Deep link to your app goes here
document.getElementById("l").src = "my_app://";
setTimeout(function() {
// Link to the App Store should go here -- only fires if deep link fails
window.location = "https://itunes.apple.com/us/app/my.app/id123456789?ls=1&mt=8";
}, 500);
};
</script>
<iframe id="l" width="1" height="1" style="visibility:hidden"></iframe>
</body>
</html>
There's a second possibility that relies on cookies first and the javascript redirect only as a fallback. Here's the logic:
When a user without the app first taps on a link to your app, he or she is redirected straight to the App Store. This is accomplished by a link to your app actually being a dynamically-generated page on your servers with the redirect. You create a cookie and log a "digital fingerprint" of IP address, OS, OS version, etc. on your backend.
When the user installs the app and opens it, you collect and send another "digital fingerprint" to your backend. Now your backend knows the link is installed On any subsequent visits to links associated with your app, your servers make sure that the dynamically-generated redirect page leads to the app, not the App Store, based on the cookie sent up with the request.
This avoids the ugly redirect but involves a ton more work.
To my understanding, because of privacy issues, you can't see if an app is installed on the device. The way around this is to try and launch the app and if it doesn't launch to have the user hit the fall back url. To prevent the mobile safari error from occurring I found that placing it in an iframe helps resolve the issue.
Here's a snippet of code that I used.
<form name="mobileForm" action="mobile_landing.php" method="post">
<input type="hidden" name="url" value="<?=$web_client_url?>">
<input type="hidden" name="mobile_app" value="<?=$mobile_app?>">
<input type="hidden" name="device_os" value="<?=$device_os?>">
</form>
<script type="text/javascript">
var device_os = '<? echo $device_os; ?>';
if (device_os == 'ios'){
var now = new Date().valueOf();
setTimeout(function () {
if (new Date().valueOf() - now > 100)
return;
document.forms[0].submit(); }, 5);
var redirect = function (location) {
var iframe = document.createElement('iframe');
iframe.setAttribute('src', location);
iframe.setAttribute('width', '1px');
iframe.setAttribute('height', '1px');
iframe.setAttribute('position', 'absolute');
iframe.setAttribute('top', '0');
iframe.setAttribute('left', '0');
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
};
setTimeout(function(){
window.close()
}, 150 );
redirect("AppScheme");
I struggled with this recently, and here is the solution I came up with. Notice that there is still no surefire way to detect whether the app launched or not.
I serve a page from my server which redirects to an iPhone-specific variant upon detecting the User-Agent. Links to that page can only be shared via email / SMS or Facebook.
The page renders a minimal version of the referenced document, but then automatically tries to open the app as soon as it loads, using a hidden <iframe> (AJAX always fails in this situation -- you can't use jQuery or XMLHttpRequest for this).
If the URL scheme is registered, the app will open and the user will be able to do everything they need. Either way, the page displays a message like this at the bottom: "Did the app launch? If not, you probably haven't installed it yet .... " with a link to the store.