open a file with default program in node-webkit - javascript

I want to give the user any option he want to edit a file, how can I open a file with the default program of the specific file type? I need it to work with Windows and Linux but Mac option would be great too.

as PSkocik said, first detect the platform and get the command line :
function getCommandLine() {
switch (process.platform) {
case 'darwin' : return 'open';
case 'win32' : return 'start';
case 'win64' : return 'start';
default : return 'xdg-open';
}
}
second , execute the command line followed by the path
var exec = require('child_process').exec;
exec(getCommandLine() + ' ' + filePath);

You can use the open module:
npm install --save open
and then call it in your Node.js file:
const open = require('open');
open('my-file.txt');
This module already contains the logic to detect the operating system and it runs the default program that is associated to this file type by your system.

For file on a disk:
var nwGui = require('nw.gui');
nwGui.Shell.openItem("/path/to/my/file");
For remote files (eg web page):
var nwGui = require('nw.gui');
nwGui.Shell.openExternal("http://google.com/");

Detect the platform and use:
'start' on Windows
'open' on Macs
'xdg-open' on Linux

I am not sure if start used to work as is on earlier windows versions, however on windows 10 it doesn't work as indicated in the answer. It's first argument is the title of the window.
Furthermore the behavior between windows and linux is different. Windows "start" will exec and exit, under linux, xdg-open will wait.
This was the function that eventually worked for me on both platforms in a similar manner:
function getCommandLine() {
switch(process.platform) {
case 'darwin' :
return 'open';
default:
return 'xdg-open';
}
}
function openFileWithDefaultApp(file) {
/^win/.test(process.platform) ?
require("child_process").exec('start "" "' + file + '"') :
require("child_process").spawn(getCommandLine(), [file],
{detached: true, stdio: 'ignore'}).unref();
}

If you aim to script some kind of prompt with a default editor or simply chain files opening, you will have to wait until the program ends or fail.
Inspired from PSkocik and Khalid answers.
const {exec} = require('child_process');
let openFile=function(filePath,mute){
let command=(function() {
switch (process.platform) {
case 'darwin' : return 'open '+filePath+' && lsof -p $! +r 1 &>/dev/null';
case 'win32' :
case 'win64' : return 'start /wait '+filePath;
default : return 'xdg-open '+filePath+' && tail --pid=$! -f /dev/null';
}
})();
if(!mute)console.log(command);
let child=exec(command);
if(!mute)child.stdout.pipe(process.stdout);
return new function(){
this.on=function(type,callback){
if(type==='data')child.stdout.on('data',callback);
else if(type==='error')child.stderr.on('data',callback);
else child.on('exit',callback);
return this;
};
this.toPromise=function(){
return new Promise((then,fail)=>{
let out=[];
this.on('data',d=>out.push(d))
.on('error',err=>fail(err))
.on('exit',()=>then(out));
});
};
}();
};
use :
openFile('path/to/some_text.txt')
.on('data',data=>{
console.log('output :'+data);
})
.on('error',err=>{
console.log('error :'+err);
})
.on('exit',()=>{
console.log('done');
});
or :
openFile('path/to/some_text.txt').toPromise()
.then(output=>{
console.log('done output :'+output.join('\n'));
}).catch(err=>{
console.log('error :'+err);
});
PS : Let me know if it waits for other sytems than winXX ( Inspired from Rauno Palosaari post but not tested yet ).

Related

nodeJS - Communicate with an electron app running in the background through a CLI

as an example of what I'm trying to achieve, consider launching VS Code from the terminal. The code <file-name> command opens an instance of vs code if not only running, or tells it to open a file otherwise. Also, once opened, the user can use the terminal session for other tasks again (as if the process was disowned).
My script needs to interact with my electron app in the same way, with the only difference being that my app will be in the tray and not visible in the dock.
.
The solution only needs to work on linux
Use a unix socket server for inter-process-communication.
In electron
const handleIpc = (conn) => {
conn.setEncoding('utf8');
conn.on('data',(line) => {
let args = line.split(' ');
switch(args[0]) {
case 'hey':
conn.write('whatsup\n');
break;
default: conn.write('new phone who this?\n');
}
conn.end();
})
}
const server = net.createServer(handleIpc);
server.listen('/tmp/my-app.sock');
Then your CLI is:
#!/usr/bin/node
const net = require('net');
let args = process.argv;
args.shift(); // Drop /usr/bin/node
args.shift(); // Drop script path
let line = args.join(' ');
net.connect('/tmp/my-app.sock',(conn)=>{
conn.setEncoding('utf8');
conn.on('data',(response)=>{
console.log(response);
process.exit(0);
});
conn.write(line+'\n');
}).on('error',(err)=>{
console.error(err);
process.exit(1);
});
If I understand correctly, you want to keep only one instance of your app and to handle attempts to launch another instance. In old versions of Electron, app.makeSingleInstance(callback) was used to achieve this. As for Electron ...v13 - v15, app.requestSingleInstanceLock() with second-instance event is used. Here is an example how to use it:
const { app } = require('electron');
let myWindow = null;
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Someone tried to run a second instance
// Do the stuff, for example, focus the window
if (myWindow) {
if (myWindow.isMinimized()) myWindow.restore()
myWindow.focus()
}
})
// Create myWindow, load the rest of the app, etc...
app.whenReady().then(() => {
myWindow = createWindow();
})
}
So when someone will launch ./app arg1 arg2 at the second time, the callback will be called. By the way, this solution is cross-platform.

How to detect if running in the new Windows Terminal?

An upcoming feature of the Windows Terminal preview is that it has full emoji support:
Compared to:
In Node.js, how do I detect if I'm running in a terminal wrapped by the Windows Terminal instead of its "naked" variation? Is there an environmental variable I can extract or a synchronous test I can do?
You can check for the WT_SESSION environmental variable which is set to a v4 UUID: https://github.com/microsoft/terminal/issues/1040
If you're looking for a quick and dirty way to check, this should work:
!!process.env.WT_SESSION
There's also a more elaborate method you can use, taking advantage of is-uuid, is-wsl and process.platform:
import isUUID from 'is-uuid';
import isWsl from 'is-wsl';
const isWindowsTerminal = (process.platform === "win32" || isWsl) && isUUID.v4(process.env.WT_SESSION);
I prefer this approach from https://github.com/microsoft/terminal/issues/6269 (in PowerShell):
function IsWindowsTerminal ($childProcess) {
if (!$childProcess) {
return $false
} elseif ($childProcess.ProcessName -eq 'WindowsTerminal') {
return $true
} else {
return IsWindowsTerminal -childProcess $childProcess.Parent
}
}
which I then use in my profile to turn on e.g. oh-my-posh.
$IsWindowsTerminal = IsWindowsTerminal -childProcess (Get-Process -Id $PID)
if($IsWindowsTerminal) {
oh-my-posh --init --shell pwsh --config $HOME\Documents\mytheme.omp.json | Invoke-Expression
}

Work around to unsolved npm link symlink requires?

When developing an NPM package, it's common to use:
npm link
It allows to modify <myPackage> under development without the need of publishing and unpublishing all the time! The developer can make any changes locally and see it immediately.
It's installed into a project by using:
npm link <myPackage>
It's great, but there's a problem if the <myPackage> have a require(path).
It'll use the real location of <myPackage> as __dirname, for example, instead of the expected location of the symlink, that should be local to the project, like a regular node_module.
The solution I found so far, for my particular case works fine:
module.exports = {
loadImage: function (filename) {
var img
if (typeof window !== 'undefined' && ({}).toString.call(window) === '[object Window]') {
try {
img = require('../../src/images/' + filename)
} catch (e) {
// Development only
img = require('./template/src/images/' + filename)
}
} else {
img = '/assets/images/' + filename
}
return img
}
}
But as you can imagine, this cause Warning messages in the Browser.
While I'm aware of the reason why of this problem, ideally, I'd like to suppress the error.
I believe it won't be a very popular question, in that case, here's a nice option that won't cause any warning messages and that is quite specific to the NPM Package development stage.
The following modules.exports expose some code snippets you'd like to import into your application. You'll find the loadImage method, with a fallback for the require(path):
module.exports = {
loadImage: function (filename) {
if (typeof window !== 'undefined' && ({}).toString.call(window) === '[object Window]') {
return (process.env.NPM_PACKAGE_DEV && require('./template/src/images/' + filename) ||
require('./template/src/images/' + filename))
} else {
return '/assets/images/' + filename
}
},
isBrowser: function () {
return (typeof window !== 'undefined' && ({}).toString.call(window) === '[object Window]')
}
}
What's good about this, is that you can set the NPM_PACKAGE_DEV by running the command, than initialize the node server (osx terminal syntax):
export NPM_PACKAGE_DEV=1 && node server.js
If the NPM_PACKAGE_DEV is omitted, the require() fallback to the end use path, that is relative to the project node_modules directory.
Hope this helps someone else in the future!

Create factory for require node modules

I want to create factory/or similar approach based on the required OS windows/linux
for example if linux use
var isLinux = /^linux/.test(process.platform);
var isWin = /^win/.test(process.platform);
if(isLinux){
var spawn = require('child-process');
}{
elseif(isWin)
var spawn = require('cross-spawn')
}
module.export = spawn;
I know that the cross-spawn is also for windows...
my question is there a nicer way to write it in node (ver4.4) instead of just else if
switch(process.platform){
case 'windows':
// code
break;
case 'linux':
// code
break;
default:
return new Error('give us a real OS pls')
}
or
process.platform === 'windows' ?
//code windows :
//code linux
Here's some more food for thought. Alternatives to switch statements.

Setting a preference at startup in firefox

Thanks to everyone in advance -
I need to load a preference before any windows are loaded at startup. Below is some /component code I have been working with. The SetPreference method seems to fail when it is called (nothing executes afterwords either) - I am assuming because the resources that it needs are not available at the time of execution...or I am doing something wrong. Any suggestions with this code or another approach to setting a preference at startup?
Thanks again,
Sam
For some reason the code formatting for SO is not working properly - here is a link to the code as well - http://samingrassia.com/_FILES/startup.js
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
const Cc = Components.classes;
const Ci = Components.interfaces;
const ObserverService = Cc['#mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
function MyStartupService() {};
MyStartupService.prototype = {
observe : function(aSubject, aTopic, aData) {
switch (aTopic) {
case 'xpcom-startup':
this.SetPreference("my.extension.is_running", "false");
break;
case 'app-startup':
this.SetPreference("my.extension.is_running", "false");
ObserverService.addObserver(this, 'final-ui-startup', false);
break;
case 'final-ui-startup':
//make sure is_running is set to false
this.SetPreference("my.extension.is_running", "false");
ObserverService.removeObserver(this, 'final-ui-startup');
const WindowWatcher = Cc['#mozilla.org/embedcomp/window-watcher;1'].getService(Ci.nsIWindowWatcher);
WindowWatcher.registerNotification(this);
break;
case 'domwindowopened':
this.initWindow(aSubject);
break;
}
},
SetPreference : function(Token, Value) {
var prefs = Components.classes["#mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
var str = Components.classes["#mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
str.data = Value;
prefs.setComplexValue(Token, Components.interfaces.nsISupportsString, str);
//save preferences
var prefService = Components.classes["#mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
prefService.savePrefFile(null);
},
initWindow : function(aWindow) {
if (aWindow != '[object ChromeWindow]') return;
aWindow.addEventListener('load', function() {
aWindow.removeEventListener('load', arguments.callee, false);
aWindow.document.title = 'domwindowopened!';
// for browser windows
var root = aWindow.document.documentElement;
root.setAttribute('title', aWindow.document.title);
root.setAttribute('titlemodifier', aWindow.document.title);
}, false);
},
classDescription : 'My Startup Service',
contractID : '#mystartupservice.com/startup;1',
classID : Components.ID('{770825e7-b39c-4654-94bc-008e5d6d57b7}'),
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver]),
_xpcom_categories : [{ category : 'app-startup', service : true }]
};
function NSGetModule(aCompMgr, aFileSpec) {
return XPCOMUtils.generateModule([MyStartupService]);
}
To answer your real question, which is
I have code that loads on every window load and I need to make sure that only gets executed once every time firefox starts up.
..you should just use a module, in the load handler that you wish to execute once, check a flag on the object exported from (i.e. "living in") the module, then after running the code you need, set the flag.
Since the module is shared across all windows, the flag will remain set until you close Firefox.
As for your intermediate problem, I'd suggest wrapping the code inside observe() in a try { ... } catch(e) {dump(e)} (you'll need to set a pref and run Firefox in a special way in order to see the output) and check the error returned.
I guess xpcom-startup and app-startup is too early to mess with preferences (I think you need a profile for that), note that you don't register to get xpcom-startup notification anyway. You probably want to register for profile-after-change instead.

Categories

Resources